├── .github └── workflows │ ├── phpcsfixer.yml │ └── phpunit.yml ├── .gitignore ├── .php-cs-fixer.php ├── .ts3config ├── mysql.yml.dist └── teamspeak3.yml.dist ├── CHANGELOG ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── docker-compose.yml ├── images ├── flags │ ├── ad.png │ ├── ae.png │ ├── af.png │ ├── ag.png │ ├── ai.png │ ├── al.png │ ├── am.png │ ├── an.png │ ├── ao.png │ ├── ar.png │ ├── as.png │ ├── at.png │ ├── au.png │ ├── aw.png │ ├── ax.png │ ├── az.png │ ├── ba.png │ ├── bb.png │ ├── bd.png │ ├── be.png │ ├── bf.png │ ├── bg.png │ ├── bh.png │ ├── bi.png │ ├── bj.png │ ├── bl.png │ ├── bm.png │ ├── bn.png │ ├── bo.png │ ├── br.png │ ├── bs.png │ ├── bt.png │ ├── bv.png │ ├── bw.png │ ├── by.png │ ├── bz.png │ ├── ca.png │ ├── cc.png │ ├── cd.png │ ├── cf.png │ ├── cg.png │ ├── ch.png │ ├── ci.png │ ├── ck.png │ ├── cl.png │ ├── cm.png │ ├── cn.png │ ├── co.png │ ├── cr.png │ ├── cs.png │ ├── cu.png │ ├── cv.png │ ├── cw.png │ ├── cx.png │ ├── cy.png │ ├── cz.png │ ├── de.png │ ├── dj.png │ ├── dk.png │ ├── dm.png │ ├── do.png │ ├── dz.png │ ├── ec.png │ ├── ee.png │ ├── eg.png │ ├── eh.png │ ├── er.png │ ├── es.png │ ├── et.png │ ├── fi.png │ ├── fj.png │ ├── fk.png │ ├── fm.png │ ├── fo.png │ ├── fr.png │ ├── ga.png │ ├── gb.png │ ├── gd.png │ ├── ge.png │ ├── gf.png │ ├── gg.png │ ├── gh.png │ ├── gi.png │ ├── gl.png │ ├── gm.png │ ├── gn.png │ ├── gp.png │ ├── gq.png │ ├── gr.png │ ├── gs.png │ ├── gt.png │ ├── gu.png │ ├── gw.png │ ├── gy.png │ ├── hk.png │ ├── hm.png │ ├── hn.png │ ├── hr.png │ ├── ht.png │ ├── hu.png │ ├── id.png │ ├── ie.png │ ├── il.png │ ├── im.png │ ├── in.png │ ├── io.png │ ├── iq.png │ ├── ir.png │ ├── is.png │ ├── it.png │ ├── je.png │ ├── jm.png │ ├── jo.png │ ├── jp.png │ ├── ke.png │ ├── kg.png │ ├── kh.png │ ├── ki.png │ ├── km.png │ ├── kn.png │ ├── kp.png │ ├── kr.png │ ├── kw.png │ ├── ky.png │ ├── kz.png │ ├── la.png │ ├── lb.png │ ├── lc.png │ ├── li.png │ ├── lk.png │ ├── lr.png │ ├── ls.png │ ├── lt.png │ ├── lu.png │ ├── lv.png │ ├── ly.png │ ├── ma.png │ ├── mc.png │ ├── md.png │ ├── me.png │ ├── mg.png │ ├── mh.png │ ├── mk.png │ ├── ml.png │ ├── mm.png │ ├── mn.png │ ├── mo.png │ ├── mp.png │ ├── mq.png │ ├── mr.png │ ├── ms.png │ ├── mt.png │ ├── mu.png │ ├── mv.png │ ├── mw.png │ ├── mx.png │ ├── my.png │ ├── mz.png │ ├── na.png │ ├── nc.png │ ├── ne.png │ ├── nf.png │ ├── ng.png │ ├── ni.png │ ├── nl.png │ ├── no.png │ ├── np.png │ ├── nr.png │ ├── nu.png │ ├── nz.png │ ├── om.png │ ├── pa.png │ ├── pe.png │ ├── pf.png │ ├── pg.png │ ├── ph.png │ ├── pk.png │ ├── pl.png │ ├── pm.png │ ├── pn.png │ ├── pr.png │ ├── ps.png │ ├── pt.png │ ├── pw.png │ ├── py.png │ ├── qa.png │ ├── re.png │ ├── ro.png │ ├── rs.png │ ├── ru.png │ ├── rw.png │ ├── sa.png │ ├── sb.png │ ├── sc.png │ ├── sd.png │ ├── se.png │ ├── sg.png │ ├── sh.png │ ├── si.png │ ├── sj.png │ ├── sk.png │ ├── sl.png │ ├── sm.png │ ├── sn.png │ ├── so.png │ ├── sr.png │ ├── st.png │ ├── sv.png │ ├── sy.png │ ├── sz.png │ ├── tc.png │ ├── td.png │ ├── tf.png │ ├── tg.png │ ├── th.png │ ├── tj.png │ ├── tk.png │ ├── tl.png │ ├── tm.png │ ├── tn.png │ ├── to.png │ ├── tr.png │ ├── tt.png │ ├── tv.png │ ├── tw.png │ ├── tz.png │ ├── ua.png │ ├── ug.png │ ├── uk.png │ ├── um.png │ ├── us.png │ ├── uy.png │ ├── uz.png │ ├── va.png │ ├── vc.png │ ├── ve.png │ ├── vg.png │ ├── vi.png │ ├── vn.png │ ├── vu.png │ ├── wf.png │ ├── ws.png │ ├── ye.png │ ├── yt.png │ ├── za.png │ ├── zm.png │ └── zw.png ├── icons │ ├── ts3client.ico │ └── ts3server.ico └── viewer │ ├── channel_flag_default.png │ ├── channel_flag_moderated.png │ ├── channel_flag_music.png │ ├── channel_flag_password.png │ ├── channel_full.png │ ├── channel_open.png │ ├── channel_pass.png │ ├── client_away.png │ ├── client_cc.png │ ├── client_cc_idle.png │ ├── client_cc_talk.png │ ├── client_idle.png │ ├── client_mic_disabled.png │ ├── client_mic_muted.png │ ├── client_priority.png │ ├── client_query.png │ ├── client_snd_disabled.png │ ├── client_snd_muted.png │ ├── client_talk.png │ ├── client_talker.png │ ├── client_talker_request.png │ ├── group_channel.png │ ├── group_icon_0.png │ ├── group_icon_100.png │ ├── group_icon_200.png │ ├── group_icon_300.png │ ├── group_icon_400.png │ ├── group_icon_500.png │ ├── group_icon_600.png │ ├── group_server.png │ ├── host.png │ ├── server_full.png │ ├── server_open.png │ ├── server_pass.png │ ├── spacer_dashdotdotline.gif │ ├── spacer_dashdotline.gif │ ├── spacer_dashline.gif │ ├── spacer_dotline.gif │ ├── spacer_solidline.gif │ ├── tree.png │ ├── tree_blank.png │ ├── tree_end.gif │ ├── tree_line.gif │ └── tree_mid.gif ├── phpunit.xml.dist ├── src ├── Adapter │ ├── Adapter.php │ ├── FileTransfer.php │ ├── MockServerQuery.php │ ├── ServerQuery.php │ └── ServerQuery │ │ ├── Event.php │ │ └── Reply.php ├── Exception │ ├── AdapterException.php │ ├── FileTransferException.php │ ├── HelperException.php │ ├── NodeException.php │ ├── ProfilerException.php │ ├── ServerQueryException.php │ ├── SignalException.php │ ├── TeamSpeak3Exception.php │ └── TransportException.php ├── Helper │ ├── Char.php │ ├── Convert.php │ ├── Crypt.php │ ├── Profiler.php │ ├── Profiler │ │ └── Timer.php │ ├── Signal.php │ ├── Signal │ │ ├── Handler.php │ │ └── SignalInterface.php │ ├── StringHelper.php │ └── Uri.php ├── Node │ ├── Channel.php │ ├── ChannelGroup.php │ ├── Client.php │ ├── Group.php │ ├── Host.php │ ├── Node.php │ ├── Server.php │ └── ServerGroup.php ├── TeamSpeak3.php ├── Transport │ ├── MockTCP.php │ ├── TCP.php │ ├── Transport.php │ └── UDP.php └── Viewer │ ├── Html.php │ ├── Json.php │ ├── Text.php │ └── ViewerInterface.php └── tests ├── Adapter ├── ServerQuery │ └── ReplyTest.php └── ServerQueryTest.php ├── Helper ├── CharTest.php ├── ConvertTest.php ├── CryptTest.php ├── SignalTest.php ├── StringTest.php └── UriTest.php └── Transport ├── TCPTest.php ├── TransportTest.php └── UDPTest.php /.github/workflows/phpcsfixer.yml: -------------------------------------------------------------------------------- 1 | name: "PHP-CS-Fixer" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - dev 9 | pull_request: 10 | schedule: 11 | - cron: '0 15 * * 2' 12 | 13 | jobs: 14 | code-style: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | php_versions: [ 19 | '8.3', 20 | ] 21 | name: PHP ${{ matrix.php_versions }} 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: checkout repository 25 | uses: actions/checkout@v3 26 | 27 | - name: install php ${{ matrix.php_versions }} with extensions 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php_versions }} 31 | extensions: json ctype openssl ssh2 32 | coverage: none 33 | tools: none 34 | 35 | - name: install dependencies 36 | uses: php-actions/composer@v6 37 | with: 38 | dev: yes 39 | php_version: ${{ matrix.php_versions }} 40 | php_extensions: json ctype openssl ssh2 41 | 42 | - name: run php-cs-fixer 43 | run: ./vendor/bin/php-cs-fixer fix --config .php-cs-fixer.php --diff --dry-run 44 | -------------------------------------------------------------------------------- /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: "PHPUnit" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - dev 9 | pull_request: 10 | schedule: 11 | - cron: '0 15 * * 2' 12 | 13 | jobs: 14 | phpunit-tests: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | php_versions: [ 19 | '8.1', 20 | '8.2', 21 | '8.3', 22 | ] 23 | name: PHP ${{ matrix.php_versions }} 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: checkout repository 27 | uses: actions/checkout@v3 28 | 29 | - name: install php ${{ matrix.php_versions }} with extensions 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php_versions }} 33 | extensions: json ctype openssl ssh2 34 | coverage: none 35 | tools: none 36 | 37 | - name: install dependencies 38 | uses: php-actions/composer@v6 39 | with: 40 | dev: yes 41 | php_version: ${{ matrix.php_versions }} 42 | php_extensions: json ctype openssl ssh2 43 | 44 | - name: run phpunit tests 45 | run: ./vendor/bin/phpunit --no-coverage 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Configuration files 2 | .ts3config/* 3 | !.ts3config/*.dist 4 | 5 | # Build related 6 | build/ 7 | .php_cs.cache 8 | .phpunit.result.cache 9 | php-cs-fixer.cache 10 | 11 | # OS-specific 12 | .DS_Store 13 | 14 | # Composer libraries 15 | composer.lock 16 | vendor/ 17 | 18 | # Intellij project files 19 | .idea 20 | *.iml 21 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in(__DIR__); 4 | 5 | $config = new PhpCsFixer\Config(); 6 | $config->setCacheFile(__DIR__ . '/php-cs-fixer.cache'); 7 | $config->setFinder($finder); 8 | $config->setIndent(" "); 9 | $config->setLineEnding("\n"); 10 | $config->setRules([ 11 | '@PSR12' => true, 12 | 'array_syntax' => [ 13 | 'syntax' => 'short', 14 | ], 15 | ]); 16 | 17 | return $config; 18 | -------------------------------------------------------------------------------- /.ts3config/mysql.yml.dist: -------------------------------------------------------------------------------- 1 | server: 2 | host: localhost 3 | port: 3306 4 | database: teamspeak 5 | auth: 6 | username: teamspeak 7 | password: password -------------------------------------------------------------------------------- /.ts3config/teamspeak3.yml.dist: -------------------------------------------------------------------------------- 1 | server: 2 | host: localhost 3 | ports: 4 | voice: 9987 5 | query: 10011 6 | file: 30033 7 | tsdns: 41144 8 | auth: 9 | query: 10 | username: serveradmin 11 | password: password -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We are open to contributions from everyone! 3rd-party support goes a long way 4 | towards maintaining and improving the framework. Nothing is too small, from pull 5 | requests to reporting bugs, everything helps! 6 | 7 | ## Ways to contribute 8 | 9 | * Having trouble or find a bug? [Open an 10 | issue!](https://github.com/planetteamspeak/ts3phpframework/issues/new) 11 | * Add a feature or fix a bug: 12 | * Check for existing issue or create a new one. 13 | * Fork the repo, make your changes. 14 | * Create a pull request, and reference the issue. 15 | * Add examples, tests, or improve documentation. 16 | 17 | ## Test 18 | 19 | When committing code, please make sure to test before creating a pull request. 20 | 21 | We use PHPUnit for testing, feel free to add new tests. This is not a 22 | requirement, but helps us maintain code coverage. 23 | 24 | @ToDo: Add sections: test environment setup, running tests, examples 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sven Paulsen (@svenpaulsen) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "planetteamspeak/ts3-php-framework", 3 | "type": "library", 4 | "description": "Modern use-at-will framework that provides individual components to manage TeamSpeak 3 Server instances", 5 | "keywords": ["ts3", "teamspeak","server","query","filetransfer","management","interface","api"], 6 | "homepage": "https://www.planetteamspeak.com", 7 | "license": "GPL-3.0", 8 | "authors": [ 9 | { 10 | "name": "Sven 'ScP' Paulsen", 11 | "email": "scp@planetteamspeak.com" 12 | } 13 | ], 14 | "support": { 15 | "email": "info@planetteamspeak.com", 16 | "issues": "https://github.com/planetteamspeak/ts3phpframework/issues", 17 | "docs": "https://docs.planetteamspeak.com/ts3/php/framework" 18 | }, 19 | "config": { 20 | "sort-packages": true 21 | }, 22 | "require": { 23 | "php": ">=8.1", 24 | "ext-mbstring": "*", 25 | "ext-json": "*", 26 | "ext-ctype": "*", 27 | "phpseclib/phpseclib": "^3.0", 28 | "ext-openssl": "*" 29 | }, 30 | "require-dev": { 31 | "friendsofphp/php-cs-fixer": "^3.8", 32 | "php-coveralls/php-coveralls": "^2.5", 33 | "phpcompatibility/php-compatibility": "^9.3", 34 | "phpunit/phpunit": "^9.5", 35 | "react/socket": "^1.11", 36 | "squizlabs/php_codesniffer": "^3.6", 37 | "symfony/yaml": "^6.0" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "PlanetTeamSpeak\\TeamSpeak3Framework\\": "src/" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "PlanetTeamSpeak\\TeamSpeak3Framework\\Tests\\": "tests/" 47 | } 48 | }, 49 | "scripts": { 50 | "compatibility": "\"vendor/bin/phpcs\" -ps --basepath=. --standard=PHPCompatibility --runtime-set testVersion 8.0- src", 51 | "code-style": "\"vendor/bin/php-cs-fixer\" fix --config .php-cs-fixer.php --diff", 52 | "tests": [ 53 | "\"vendor/bin/phpunit\" --no-coverage" 54 | ], 55 | "coverage": "\"vendor/bin/phpunit\"" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | teamspeak: 4 | image: teamspeak 5 | restart: always 6 | ports: 7 | - 9987:9987/udp 8 | - 10011:10011 9 | - 10022:10022 10 | - 30033:30033 11 | environment: 12 | TS3SERVER_DB_PLUGIN: ts3db_mariadb 13 | TS3SERVER_DB_SQLCREATEPATH: create_mariadb 14 | TS3SERVER_DB_HOST: db 15 | TS3SERVER_DB_USER: root 16 | TS3SERVER_DB_PASSWORD: example 17 | TS3SERVER_DB_NAME: teamspeak 18 | TS3SERVER_DB_WAITUNTILREADY: 30 19 | TS3SERVER_LICENSE: accept 20 | db: 21 | image: mariadb 22 | restart: always 23 | environment: 24 | MYSQL_ROOT_PASSWORD: example 25 | MYSQL_DATABASE: teamspeak -------------------------------------------------------------------------------- /images/flags/ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ad.png -------------------------------------------------------------------------------- /images/flags/ae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ae.png -------------------------------------------------------------------------------- /images/flags/af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/af.png -------------------------------------------------------------------------------- /images/flags/ag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ag.png -------------------------------------------------------------------------------- /images/flags/ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ai.png -------------------------------------------------------------------------------- /images/flags/al.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/al.png -------------------------------------------------------------------------------- /images/flags/am.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/am.png -------------------------------------------------------------------------------- /images/flags/an.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/an.png -------------------------------------------------------------------------------- /images/flags/ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ao.png -------------------------------------------------------------------------------- /images/flags/ar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ar.png -------------------------------------------------------------------------------- /images/flags/as.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/as.png -------------------------------------------------------------------------------- /images/flags/at.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/at.png -------------------------------------------------------------------------------- /images/flags/au.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/au.png -------------------------------------------------------------------------------- /images/flags/aw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/aw.png -------------------------------------------------------------------------------- /images/flags/ax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ax.png -------------------------------------------------------------------------------- /images/flags/az.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/az.png -------------------------------------------------------------------------------- /images/flags/ba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ba.png -------------------------------------------------------------------------------- /images/flags/bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bb.png -------------------------------------------------------------------------------- /images/flags/bd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bd.png -------------------------------------------------------------------------------- /images/flags/be.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/be.png -------------------------------------------------------------------------------- /images/flags/bf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bf.png -------------------------------------------------------------------------------- /images/flags/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bg.png -------------------------------------------------------------------------------- /images/flags/bh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bh.png -------------------------------------------------------------------------------- /images/flags/bi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bi.png -------------------------------------------------------------------------------- /images/flags/bj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bj.png -------------------------------------------------------------------------------- /images/flags/bl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bl.png -------------------------------------------------------------------------------- /images/flags/bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bm.png -------------------------------------------------------------------------------- /images/flags/bn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bn.png -------------------------------------------------------------------------------- /images/flags/bo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bo.png -------------------------------------------------------------------------------- /images/flags/br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/br.png -------------------------------------------------------------------------------- /images/flags/bs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bs.png -------------------------------------------------------------------------------- /images/flags/bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bt.png -------------------------------------------------------------------------------- /images/flags/bv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bv.png -------------------------------------------------------------------------------- /images/flags/bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bw.png -------------------------------------------------------------------------------- /images/flags/by.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/by.png -------------------------------------------------------------------------------- /images/flags/bz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/bz.png -------------------------------------------------------------------------------- /images/flags/ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ca.png -------------------------------------------------------------------------------- /images/flags/cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cc.png -------------------------------------------------------------------------------- /images/flags/cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cd.png -------------------------------------------------------------------------------- /images/flags/cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cf.png -------------------------------------------------------------------------------- /images/flags/cg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cg.png -------------------------------------------------------------------------------- /images/flags/ch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ch.png -------------------------------------------------------------------------------- /images/flags/ci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ci.png -------------------------------------------------------------------------------- /images/flags/ck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ck.png -------------------------------------------------------------------------------- /images/flags/cl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cl.png -------------------------------------------------------------------------------- /images/flags/cm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cm.png -------------------------------------------------------------------------------- /images/flags/cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cn.png -------------------------------------------------------------------------------- /images/flags/co.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/co.png -------------------------------------------------------------------------------- /images/flags/cr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cr.png -------------------------------------------------------------------------------- /images/flags/cs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cs.png -------------------------------------------------------------------------------- /images/flags/cu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cu.png -------------------------------------------------------------------------------- /images/flags/cv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cv.png -------------------------------------------------------------------------------- /images/flags/cw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cw.png -------------------------------------------------------------------------------- /images/flags/cx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cx.png -------------------------------------------------------------------------------- /images/flags/cy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cy.png -------------------------------------------------------------------------------- /images/flags/cz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/cz.png -------------------------------------------------------------------------------- /images/flags/de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/de.png -------------------------------------------------------------------------------- /images/flags/dj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/dj.png -------------------------------------------------------------------------------- /images/flags/dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/dk.png -------------------------------------------------------------------------------- /images/flags/dm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/dm.png -------------------------------------------------------------------------------- /images/flags/do.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/do.png -------------------------------------------------------------------------------- /images/flags/dz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/dz.png -------------------------------------------------------------------------------- /images/flags/ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ec.png -------------------------------------------------------------------------------- /images/flags/ee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ee.png -------------------------------------------------------------------------------- /images/flags/eg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/eg.png -------------------------------------------------------------------------------- /images/flags/eh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/eh.png -------------------------------------------------------------------------------- /images/flags/er.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/er.png -------------------------------------------------------------------------------- /images/flags/es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/es.png -------------------------------------------------------------------------------- /images/flags/et.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/et.png -------------------------------------------------------------------------------- /images/flags/fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/fi.png -------------------------------------------------------------------------------- /images/flags/fj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/fj.png -------------------------------------------------------------------------------- /images/flags/fk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/fk.png -------------------------------------------------------------------------------- /images/flags/fm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/fm.png -------------------------------------------------------------------------------- /images/flags/fo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/fo.png -------------------------------------------------------------------------------- /images/flags/fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/fr.png -------------------------------------------------------------------------------- /images/flags/ga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ga.png -------------------------------------------------------------------------------- /images/flags/gb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gb.png -------------------------------------------------------------------------------- /images/flags/gd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gd.png -------------------------------------------------------------------------------- /images/flags/ge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ge.png -------------------------------------------------------------------------------- /images/flags/gf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gf.png -------------------------------------------------------------------------------- /images/flags/gg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gg.png -------------------------------------------------------------------------------- /images/flags/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gh.png -------------------------------------------------------------------------------- /images/flags/gi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gi.png -------------------------------------------------------------------------------- /images/flags/gl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gl.png -------------------------------------------------------------------------------- /images/flags/gm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gm.png -------------------------------------------------------------------------------- /images/flags/gn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gn.png -------------------------------------------------------------------------------- /images/flags/gp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gp.png -------------------------------------------------------------------------------- /images/flags/gq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gq.png -------------------------------------------------------------------------------- /images/flags/gr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gr.png -------------------------------------------------------------------------------- /images/flags/gs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gs.png -------------------------------------------------------------------------------- /images/flags/gt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gt.png -------------------------------------------------------------------------------- /images/flags/gu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gu.png -------------------------------------------------------------------------------- /images/flags/gw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gw.png -------------------------------------------------------------------------------- /images/flags/gy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/gy.png -------------------------------------------------------------------------------- /images/flags/hk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/hk.png -------------------------------------------------------------------------------- /images/flags/hm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/hm.png -------------------------------------------------------------------------------- /images/flags/hn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/hn.png -------------------------------------------------------------------------------- /images/flags/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/hr.png -------------------------------------------------------------------------------- /images/flags/ht.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ht.png -------------------------------------------------------------------------------- /images/flags/hu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/hu.png -------------------------------------------------------------------------------- /images/flags/id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/id.png -------------------------------------------------------------------------------- /images/flags/ie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ie.png -------------------------------------------------------------------------------- /images/flags/il.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/il.png -------------------------------------------------------------------------------- /images/flags/im.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/im.png -------------------------------------------------------------------------------- /images/flags/in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/in.png -------------------------------------------------------------------------------- /images/flags/io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/io.png -------------------------------------------------------------------------------- /images/flags/iq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/iq.png -------------------------------------------------------------------------------- /images/flags/ir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ir.png -------------------------------------------------------------------------------- /images/flags/is.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/is.png -------------------------------------------------------------------------------- /images/flags/it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/it.png -------------------------------------------------------------------------------- /images/flags/je.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/je.png -------------------------------------------------------------------------------- /images/flags/jm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/jm.png -------------------------------------------------------------------------------- /images/flags/jo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/jo.png -------------------------------------------------------------------------------- /images/flags/jp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/jp.png -------------------------------------------------------------------------------- /images/flags/ke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ke.png -------------------------------------------------------------------------------- /images/flags/kg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kg.png -------------------------------------------------------------------------------- /images/flags/kh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kh.png -------------------------------------------------------------------------------- /images/flags/ki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ki.png -------------------------------------------------------------------------------- /images/flags/km.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/km.png -------------------------------------------------------------------------------- /images/flags/kn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kn.png -------------------------------------------------------------------------------- /images/flags/kp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kp.png -------------------------------------------------------------------------------- /images/flags/kr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kr.png -------------------------------------------------------------------------------- /images/flags/kw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kw.png -------------------------------------------------------------------------------- /images/flags/ky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ky.png -------------------------------------------------------------------------------- /images/flags/kz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/kz.png -------------------------------------------------------------------------------- /images/flags/la.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/la.png -------------------------------------------------------------------------------- /images/flags/lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lb.png -------------------------------------------------------------------------------- /images/flags/lc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lc.png -------------------------------------------------------------------------------- /images/flags/li.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/li.png -------------------------------------------------------------------------------- /images/flags/lk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lk.png -------------------------------------------------------------------------------- /images/flags/lr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lr.png -------------------------------------------------------------------------------- /images/flags/ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ls.png -------------------------------------------------------------------------------- /images/flags/lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lt.png -------------------------------------------------------------------------------- /images/flags/lu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lu.png -------------------------------------------------------------------------------- /images/flags/lv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/lv.png -------------------------------------------------------------------------------- /images/flags/ly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ly.png -------------------------------------------------------------------------------- /images/flags/ma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ma.png -------------------------------------------------------------------------------- /images/flags/mc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mc.png -------------------------------------------------------------------------------- /images/flags/md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/md.png -------------------------------------------------------------------------------- /images/flags/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/me.png -------------------------------------------------------------------------------- /images/flags/mg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mg.png -------------------------------------------------------------------------------- /images/flags/mh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mh.png -------------------------------------------------------------------------------- /images/flags/mk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mk.png -------------------------------------------------------------------------------- /images/flags/ml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ml.png -------------------------------------------------------------------------------- /images/flags/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mm.png -------------------------------------------------------------------------------- /images/flags/mn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mn.png -------------------------------------------------------------------------------- /images/flags/mo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mo.png -------------------------------------------------------------------------------- /images/flags/mp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mp.png -------------------------------------------------------------------------------- /images/flags/mq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mq.png -------------------------------------------------------------------------------- /images/flags/mr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mr.png -------------------------------------------------------------------------------- /images/flags/ms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ms.png -------------------------------------------------------------------------------- /images/flags/mt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mt.png -------------------------------------------------------------------------------- /images/flags/mu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mu.png -------------------------------------------------------------------------------- /images/flags/mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mv.png -------------------------------------------------------------------------------- /images/flags/mw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mw.png -------------------------------------------------------------------------------- /images/flags/mx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mx.png -------------------------------------------------------------------------------- /images/flags/my.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/my.png -------------------------------------------------------------------------------- /images/flags/mz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/mz.png -------------------------------------------------------------------------------- /images/flags/na.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/na.png -------------------------------------------------------------------------------- /images/flags/nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/nc.png -------------------------------------------------------------------------------- /images/flags/ne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ne.png -------------------------------------------------------------------------------- /images/flags/nf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/nf.png -------------------------------------------------------------------------------- /images/flags/ng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ng.png -------------------------------------------------------------------------------- /images/flags/ni.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ni.png -------------------------------------------------------------------------------- /images/flags/nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/nl.png -------------------------------------------------------------------------------- /images/flags/no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/no.png -------------------------------------------------------------------------------- /images/flags/np.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/np.png -------------------------------------------------------------------------------- /images/flags/nr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/nr.png -------------------------------------------------------------------------------- /images/flags/nu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/nu.png -------------------------------------------------------------------------------- /images/flags/nz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/nz.png -------------------------------------------------------------------------------- /images/flags/om.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/om.png -------------------------------------------------------------------------------- /images/flags/pa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pa.png -------------------------------------------------------------------------------- /images/flags/pe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pe.png -------------------------------------------------------------------------------- /images/flags/pf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pf.png -------------------------------------------------------------------------------- /images/flags/pg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pg.png -------------------------------------------------------------------------------- /images/flags/ph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ph.png -------------------------------------------------------------------------------- /images/flags/pk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pk.png -------------------------------------------------------------------------------- /images/flags/pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pl.png -------------------------------------------------------------------------------- /images/flags/pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pm.png -------------------------------------------------------------------------------- /images/flags/pn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pn.png -------------------------------------------------------------------------------- /images/flags/pr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pr.png -------------------------------------------------------------------------------- /images/flags/ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ps.png -------------------------------------------------------------------------------- /images/flags/pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pt.png -------------------------------------------------------------------------------- /images/flags/pw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/pw.png -------------------------------------------------------------------------------- /images/flags/py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/py.png -------------------------------------------------------------------------------- /images/flags/qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/qa.png -------------------------------------------------------------------------------- /images/flags/re.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/re.png -------------------------------------------------------------------------------- /images/flags/ro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ro.png -------------------------------------------------------------------------------- /images/flags/rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/rs.png -------------------------------------------------------------------------------- /images/flags/ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ru.png -------------------------------------------------------------------------------- /images/flags/rw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/rw.png -------------------------------------------------------------------------------- /images/flags/sa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sa.png -------------------------------------------------------------------------------- /images/flags/sb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sb.png -------------------------------------------------------------------------------- /images/flags/sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sc.png -------------------------------------------------------------------------------- /images/flags/sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sd.png -------------------------------------------------------------------------------- /images/flags/se.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/se.png -------------------------------------------------------------------------------- /images/flags/sg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sg.png -------------------------------------------------------------------------------- /images/flags/sh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sh.png -------------------------------------------------------------------------------- /images/flags/si.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/si.png -------------------------------------------------------------------------------- /images/flags/sj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sj.png -------------------------------------------------------------------------------- /images/flags/sk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sk.png -------------------------------------------------------------------------------- /images/flags/sl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sl.png -------------------------------------------------------------------------------- /images/flags/sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sm.png -------------------------------------------------------------------------------- /images/flags/sn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sn.png -------------------------------------------------------------------------------- /images/flags/so.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/so.png -------------------------------------------------------------------------------- /images/flags/sr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sr.png -------------------------------------------------------------------------------- /images/flags/st.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/st.png -------------------------------------------------------------------------------- /images/flags/sv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sv.png -------------------------------------------------------------------------------- /images/flags/sy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sy.png -------------------------------------------------------------------------------- /images/flags/sz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/sz.png -------------------------------------------------------------------------------- /images/flags/tc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tc.png -------------------------------------------------------------------------------- /images/flags/td.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/td.png -------------------------------------------------------------------------------- /images/flags/tf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tf.png -------------------------------------------------------------------------------- /images/flags/tg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tg.png -------------------------------------------------------------------------------- /images/flags/th.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/th.png -------------------------------------------------------------------------------- /images/flags/tj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tj.png -------------------------------------------------------------------------------- /images/flags/tk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tk.png -------------------------------------------------------------------------------- /images/flags/tl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tl.png -------------------------------------------------------------------------------- /images/flags/tm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tm.png -------------------------------------------------------------------------------- /images/flags/tn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tn.png -------------------------------------------------------------------------------- /images/flags/to.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/to.png -------------------------------------------------------------------------------- /images/flags/tr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tr.png -------------------------------------------------------------------------------- /images/flags/tt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tt.png -------------------------------------------------------------------------------- /images/flags/tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tv.png -------------------------------------------------------------------------------- /images/flags/tw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tw.png -------------------------------------------------------------------------------- /images/flags/tz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/tz.png -------------------------------------------------------------------------------- /images/flags/ua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ua.png -------------------------------------------------------------------------------- /images/flags/ug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ug.png -------------------------------------------------------------------------------- /images/flags/uk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/uk.png -------------------------------------------------------------------------------- /images/flags/um.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/um.png -------------------------------------------------------------------------------- /images/flags/us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/us.png -------------------------------------------------------------------------------- /images/flags/uy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/uy.png -------------------------------------------------------------------------------- /images/flags/uz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/uz.png -------------------------------------------------------------------------------- /images/flags/va.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/va.png -------------------------------------------------------------------------------- /images/flags/vc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/vc.png -------------------------------------------------------------------------------- /images/flags/ve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ve.png -------------------------------------------------------------------------------- /images/flags/vg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/vg.png -------------------------------------------------------------------------------- /images/flags/vi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/vi.png -------------------------------------------------------------------------------- /images/flags/vn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/vn.png -------------------------------------------------------------------------------- /images/flags/vu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/vu.png -------------------------------------------------------------------------------- /images/flags/wf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/wf.png -------------------------------------------------------------------------------- /images/flags/ws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ws.png -------------------------------------------------------------------------------- /images/flags/ye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/ye.png -------------------------------------------------------------------------------- /images/flags/yt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/yt.png -------------------------------------------------------------------------------- /images/flags/za.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/za.png -------------------------------------------------------------------------------- /images/flags/zm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/zm.png -------------------------------------------------------------------------------- /images/flags/zw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/flags/zw.png -------------------------------------------------------------------------------- /images/icons/ts3client.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/icons/ts3client.ico -------------------------------------------------------------------------------- /images/icons/ts3server.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/icons/ts3server.ico -------------------------------------------------------------------------------- /images/viewer/channel_flag_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_flag_default.png -------------------------------------------------------------------------------- /images/viewer/channel_flag_moderated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_flag_moderated.png -------------------------------------------------------------------------------- /images/viewer/channel_flag_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_flag_music.png -------------------------------------------------------------------------------- /images/viewer/channel_flag_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_flag_password.png -------------------------------------------------------------------------------- /images/viewer/channel_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_full.png -------------------------------------------------------------------------------- /images/viewer/channel_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_open.png -------------------------------------------------------------------------------- /images/viewer/channel_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/channel_pass.png -------------------------------------------------------------------------------- /images/viewer/client_away.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_away.png -------------------------------------------------------------------------------- /images/viewer/client_cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_cc.png -------------------------------------------------------------------------------- /images/viewer/client_cc_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_cc_idle.png -------------------------------------------------------------------------------- /images/viewer/client_cc_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_cc_talk.png -------------------------------------------------------------------------------- /images/viewer/client_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_idle.png -------------------------------------------------------------------------------- /images/viewer/client_mic_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_mic_disabled.png -------------------------------------------------------------------------------- /images/viewer/client_mic_muted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_mic_muted.png -------------------------------------------------------------------------------- /images/viewer/client_priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_priority.png -------------------------------------------------------------------------------- /images/viewer/client_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_query.png -------------------------------------------------------------------------------- /images/viewer/client_snd_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_snd_disabled.png -------------------------------------------------------------------------------- /images/viewer/client_snd_muted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_snd_muted.png -------------------------------------------------------------------------------- /images/viewer/client_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_talk.png -------------------------------------------------------------------------------- /images/viewer/client_talker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_talker.png -------------------------------------------------------------------------------- /images/viewer/client_talker_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/client_talker_request.png -------------------------------------------------------------------------------- /images/viewer/group_channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_channel.png -------------------------------------------------------------------------------- /images/viewer/group_icon_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_0.png -------------------------------------------------------------------------------- /images/viewer/group_icon_100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_100.png -------------------------------------------------------------------------------- /images/viewer/group_icon_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_200.png -------------------------------------------------------------------------------- /images/viewer/group_icon_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_300.png -------------------------------------------------------------------------------- /images/viewer/group_icon_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_400.png -------------------------------------------------------------------------------- /images/viewer/group_icon_500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_500.png -------------------------------------------------------------------------------- /images/viewer/group_icon_600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_icon_600.png -------------------------------------------------------------------------------- /images/viewer/group_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/group_server.png -------------------------------------------------------------------------------- /images/viewer/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/host.png -------------------------------------------------------------------------------- /images/viewer/server_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/server_full.png -------------------------------------------------------------------------------- /images/viewer/server_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/server_open.png -------------------------------------------------------------------------------- /images/viewer/server_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/server_pass.png -------------------------------------------------------------------------------- /images/viewer/spacer_dashdotdotline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/spacer_dashdotdotline.gif -------------------------------------------------------------------------------- /images/viewer/spacer_dashdotline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/spacer_dashdotline.gif -------------------------------------------------------------------------------- /images/viewer/spacer_dashline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/spacer_dashline.gif -------------------------------------------------------------------------------- /images/viewer/spacer_dotline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/spacer_dotline.gif -------------------------------------------------------------------------------- /images/viewer/spacer_solidline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/spacer_solidline.gif -------------------------------------------------------------------------------- /images/viewer/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/tree.png -------------------------------------------------------------------------------- /images/viewer/tree_blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/tree_blank.png -------------------------------------------------------------------------------- /images/viewer/tree_end.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/tree_end.gif -------------------------------------------------------------------------------- /images/viewer/tree_line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/tree_line.gif -------------------------------------------------------------------------------- /images/viewer/tree_mid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetteamspeak/ts3phpframework/87046b3d493c4d3d8064c639ea4269571192e476/images/viewer/tree_mid.gif -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./ 15 | 16 | 17 | ./images 18 | ./public 19 | ./tests 20 | ./build 21 | ./vendor 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ./tests/ 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Adapter/Adapter.php: -------------------------------------------------------------------------------- 1 | options = $options; 40 | 41 | if ($this->transport === null) { 42 | $this->syn(); 43 | } 44 | } 45 | 46 | /** 47 | * The PlanetTeamSpeak\TeamSpeak3Framework\Adapter\Adapter destructor. 48 | * 49 | * @return void 50 | */ 51 | abstract public function __destruct(); 52 | 53 | /** 54 | * Connects the PlanetTeamSpeak\TeamSpeak3Framework\Transport\Transport object and performs initial actions on the remote 55 | * server. 56 | * 57 | * @return void 58 | * @throws AdapterException 59 | */ 60 | abstract protected function syn(): void; 61 | 62 | /** 63 | * Commit pending data. 64 | * 65 | * @return array 66 | */ 67 | public function __sleep() 68 | { 69 | return ["options"]; 70 | } 71 | 72 | /** 73 | * Reconnects to the remote server. 74 | * 75 | * @return void 76 | * @throws AdapterException 77 | */ 78 | public function __wakeup() 79 | { 80 | $this->syn(); 81 | } 82 | 83 | /** 84 | * Returns the profiler timer used for this connection adapter. 85 | * 86 | * @return Timer 87 | */ 88 | public function getProfiler(): Timer 89 | { 90 | return Profiler::get(spl_object_hash($this)); 91 | } 92 | 93 | /** 94 | * Returns the transport object used for this connection adapter. 95 | * 96 | * @return Transport|null 97 | */ 98 | public function getTransport(): ?Transport 99 | { 100 | return $this->transport; 101 | } 102 | 103 | /** 104 | * Loads the transport object object used for the connection adapter and passes a given set 105 | * of options. 106 | * 107 | * @param array $options 108 | * @param string $transport 109 | * @return void 110 | */ 111 | protected function initTransport(array $options, string $transport = TCP::class): void 112 | { 113 | $this->transport = new $transport($options); 114 | } 115 | 116 | /** 117 | * Returns the hostname or IPv4 address the underlying PlanetTeamSpeak\TeamSpeak3Framework\Transport\Transport object 118 | * is connected to. 119 | * 120 | * @return string 121 | */ 122 | public function getTransportHost(): string 123 | { 124 | return $this->getTransport()->getConfig("host", "0.0.0.0"); 125 | } 126 | 127 | /** 128 | * Returns the port number of the server the underlying PlanetTeamSpeak\TeamSpeak3Framework\Transport\Transport object 129 | * is connected to. 130 | * 131 | * @return string 132 | */ 133 | public function getTransportPort(): string 134 | { 135 | return $this->getTransport()->getConfig("port", "0"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Adapter/FileTransfer.php: -------------------------------------------------------------------------------- 1 | initTransport($this->options); 26 | $this->transport->setAdapter($this); 27 | 28 | Profiler::init(spl_object_hash($this)); 29 | 30 | Signal::getInstance()->emit("filetransferConnected", $this); 31 | } 32 | 33 | /** 34 | * FileTransfer destructor. 35 | */ 36 | public function __destruct() 37 | { 38 | if ($this->getTransport() instanceof Transport && $this->getTransport()->isConnected()) { 39 | $this->getTransport()->disconnect(); 40 | } 41 | } 42 | 43 | /** 44 | * Sends a valid file transfer key to the server to initialize the file transfer. 45 | * 46 | * @param string $ftkey 47 | * @return void 48 | * @throws FileTransferException 49 | */ 50 | protected function init(string $ftkey): void 51 | { 52 | if (strlen($ftkey) != 32 && strlen($ftkey) != 16) { 53 | throw new FileTransferException("invalid file transfer key format"); 54 | } 55 | 56 | $this->getProfiler()->start(); 57 | $this->getTransport()->send($ftkey); 58 | 59 | Signal::getInstance()->emit("filetransferHandshake", $this); 60 | } 61 | 62 | /** 63 | * Sends the content of a file to the server. 64 | * 65 | * @param string $ftkey 66 | * @param integer $seek 67 | * @param string $data 68 | * @return void 69 | * @throws FileTransferException 70 | */ 71 | public function upload(string $ftkey, int $seek, string $data): void 72 | { 73 | $this->init($ftkey); 74 | 75 | $size = strlen($data); 76 | $pack = 4096; 77 | 78 | Signal::getInstance()->emit("filetransferUploadStarted", $ftkey, $seek, $size); 79 | 80 | for (; $seek < $size;) { 81 | $rest = $size - $seek; 82 | $pack = min($rest, $pack); 83 | $buff = substr($data, $seek, $pack); 84 | $seek = $seek + $pack; 85 | 86 | $this->getTransport()->send($buff); 87 | 88 | Signal::getInstance()->emit("filetransferUploadProgress", $ftkey, $seek, $size); 89 | } 90 | 91 | $this->getProfiler()->stop(); 92 | 93 | Signal::getInstance()->emit("filetransferUploadFinished", $ftkey, $seek, $size); 94 | } 95 | 96 | /** 97 | * Returns the content of a downloaded file as a PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper object. 98 | * 99 | * @param string $ftkey 100 | * @param integer $size 101 | * @param boolean $passthru 102 | * @return StringHelper|void 103 | * @throws FileTransferException 104 | * @throws TransportException 105 | */ 106 | public function download(string $ftkey, int $size, bool $passthru = false) 107 | { 108 | $this->init($ftkey); 109 | 110 | if ($passthru) { 111 | $this->passthru($size); 112 | return; 113 | } 114 | 115 | $buff = new StringHelper(""); 116 | $pack = 4096; 117 | 118 | Signal::getInstance()->emit("filetransferDownloadStarted", $ftkey, count($buff), $size); 119 | 120 | for ($seek = 0; $seek < $size;) { 121 | $rest = $size - $seek; 122 | $pack = min($rest, $pack); 123 | $data = $this->getTransport()->read(min($rest, $pack)); 124 | $seek = $seek + $pack; 125 | 126 | $buff->append($data); 127 | 128 | Signal::getInstance()->emit("filetransferDownloadProgress", $ftkey, count($buff), $size); 129 | } 130 | 131 | $this->getProfiler()->stop(); 132 | 133 | Signal::getInstance()->emit("filetransferDownloadFinished", $ftkey, count($buff), $size); 134 | 135 | if (strlen($buff) != $size) { 136 | throw new FileTransferException("incomplete file download (" . count($buff) . " of " . $size . " bytes)"); 137 | } 138 | 139 | return $buff; 140 | } 141 | 142 | /** 143 | * Outputs all remaining data on a TeamSpeak 3 file transfer stream using PHP's fpassthru() 144 | * function. 145 | * 146 | * @param integer $size 147 | * @return void 148 | * @throws FileTransferException 149 | */ 150 | protected function passthru(int $size): void 151 | { 152 | $buff_size = fpassthru($this->getTransport()->getStream()); 153 | 154 | if ($buff_size != $size) { 155 | throw new FileTransferException("incomplete file download (" . $buff_size . " of " . $size . " bytes)"); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Adapter/MockServerQuery.php: -------------------------------------------------------------------------------- 1 | initTransport($this->options, MockTCP::class); 23 | $this->transport->setAdapter($this); 24 | 25 | Profiler::init(spl_object_hash($this)); 26 | 27 | $rdy = $this->getTransport()->readLine(); 28 | 29 | if (!$rdy->startsWith(TeamSpeak3::TS3_PROTO_IDENT) && !$rdy->startsWith(TeamSpeak3::TEA_PROTO_IDENT) && !(defined("CUSTOM_PROTO_IDENT") && $rdy->startsWith(CUSTOM_PROTO_IDENT))) { 30 | throw new AdapterException("invalid reply from the server (" . $rdy . ")"); 31 | } 32 | 33 | Signal::getInstance()->emit("serverqueryConnected", $this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Adapter/ServerQuery.php: -------------------------------------------------------------------------------- 1 | initTransport($this->options); 61 | $this->transport->setAdapter($this); 62 | 63 | Profiler::init(spl_object_hash($this)); 64 | 65 | $rdy = $this->getTransport()->readLine(); 66 | 67 | if (!$rdy->startsWith(TeamSpeak3::TS3_PROTO_IDENT) && !$rdy->startsWith(TeamSpeak3::TEA_PROTO_IDENT) && !(defined("CUSTOM_PROTO_IDENT") && $rdy->startsWith(CUSTOM_PROTO_IDENT))) { 68 | throw new AdapterException("invalid reply from the server (" . $rdy . ")"); 69 | } 70 | 71 | Signal::getInstance()->emit("serverqueryConnected", $this); 72 | } 73 | 74 | /** 75 | * The ServerQuery destructor. 76 | * 77 | * @return void 78 | */ 79 | public function __destruct() 80 | { 81 | // do not disconnect, when acting as bot in non-blocking mode 82 | if (! $this->getTransport()->getConfig("blocking")) { 83 | return; 84 | } 85 | 86 | if ($this->getTransport() instanceof Transport && $this->transport->isConnected()) { 87 | try { 88 | $this->request("quit"); 89 | } catch (AdapterException) { 90 | return; 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Sends a prepared command to the server and returns the result. 97 | * 98 | * @param string $cmd 99 | * @param boolean $throw 100 | * @return Reply 101 | * @throws AdapterException|ServerQueryException 102 | */ 103 | public function request(string $cmd, bool $throw = true): Reply 104 | { 105 | $query = StringHelper::factory($cmd)->section(TeamSpeak3::SEPARATOR_CELL); 106 | 107 | if (strstr($cmd, "\r") || strstr($cmd, "\n")) { 108 | throw new AdapterException("illegal characters in command '" . $query . "'"); 109 | } elseif (in_array($query, $this->block)) { 110 | throw new ServerQueryException("command not found", 0x100); 111 | } 112 | 113 | Signal::getInstance()->emit("serverqueryCommandStarted", $cmd); 114 | 115 | $this->getProfiler()->start(); 116 | $this->getTransport()->sendLine($cmd); 117 | $this->timer = time(); 118 | $this->count++; 119 | 120 | $rpl = []; 121 | 122 | do { 123 | if (! $this->getTransport()->isConnected()) { 124 | break; 125 | } 126 | $str = $this->getTransport()->readLine(); 127 | $rpl[] = $str; 128 | } while ($str->section(TeamSpeak3::SEPARATOR_CELL) != TeamSpeak3::ERROR); 129 | 130 | $this->getProfiler()->stop(); 131 | 132 | $reply = new Reply($rpl, $cmd, $this->getHost(), $throw); 133 | 134 | Signal::getInstance()->emit("serverqueryCommandFinished", $cmd, $reply); 135 | 136 | return $reply; 137 | } 138 | 139 | /** 140 | * Waits for the server to send a notification message and returns the result. 141 | * 142 | * @return Event 143 | * @throws AdapterException 144 | */ 145 | public function wait(): Event 146 | { 147 | if ($this->getTransport()->getConfig("blocking")) { 148 | throw new AdapterException("only available in non-blocking mode"); 149 | } 150 | 151 | do { 152 | if (! $this->getTransport()->isConnected()) { 153 | break; 154 | } 155 | $evt = $this->getTransport()->readLine(); 156 | } while (!$evt->section(TeamSpeak3::SEPARATOR_CELL)->startsWith(TeamSpeak3::EVENT)); 157 | 158 | return new Event($evt, $this->getHost()); 159 | } 160 | 161 | /** 162 | * Uses given parameters and returns a prepared ServerQuery command. 163 | * 164 | * @param string $cmd 165 | * @param array $params 166 | * @return string 167 | */ 168 | public function prepare(string $cmd, array $params = []): string 169 | { 170 | $args = []; 171 | $cells = []; 172 | 173 | foreach ($params as $ident => $value) { 174 | $ident = is_numeric($ident) ? "" : strtolower($ident) . TeamSpeak3::SEPARATOR_PAIR; 175 | 176 | if (is_array($value)) { 177 | $value = array_values($value); 178 | 179 | for ($i = 0; $i < count($value); $i++) { 180 | if ($value[$i] === null) { 181 | continue; 182 | } elseif ($value[$i] === false) { 183 | $value[$i] = 0x00; 184 | } elseif ($value[$i] === true) { 185 | $value[$i] = 0x01; 186 | } elseif ($value[$i] instanceof Node) { 187 | $value[$i] = $value[$i]->getId(); 188 | } 189 | 190 | $cells[$i][] = $ident . StringHelper::factory($value[$i])->escape()->toUtf8(); 191 | } 192 | } else { 193 | if ($value === null) { 194 | continue; 195 | } elseif ($value === false) { 196 | $value = 0x00; 197 | } elseif ($value === true) { 198 | $value = 0x01; 199 | } elseif ($value instanceof Node) { 200 | $value = $value->getId(); 201 | } 202 | 203 | $args[] = $ident . StringHelper::factory($value)->escape()->toUtf8(); 204 | } 205 | } 206 | 207 | foreach (array_keys($cells) as $ident) { 208 | $cells[$ident] = implode(TeamSpeak3::SEPARATOR_CELL, $cells[$ident]); 209 | } 210 | 211 | if (count($args)) { 212 | $cmd .= " " . implode(TeamSpeak3::SEPARATOR_CELL, $args); 213 | } 214 | if (count($cells)) { 215 | $cmd .= " " . implode(TeamSpeak3::SEPARATOR_LIST, $cells); 216 | } 217 | 218 | return trim($cmd); 219 | } 220 | 221 | /** 222 | * Returns the timestamp of the last command. 223 | * 224 | * @return int|null 225 | */ 226 | public function getQueryLastTimestamp(): ?int 227 | { 228 | return $this->timer; 229 | } 230 | 231 | /** 232 | * Returns the number of queries executed on the server. 233 | * 234 | * @return integer 235 | */ 236 | public function getQueryCount(): int 237 | { 238 | return $this->count; 239 | } 240 | 241 | /** 242 | * Returns the total runtime in microseconds of all queries. 243 | * 244 | * @return float 245 | */ 246 | public function getQueryRuntime(): float 247 | { 248 | return $this->getProfiler()->getRuntime(); 249 | } 250 | 251 | /** 252 | * Returns the Host object of the current connection. 253 | * 254 | * @return Host|null 255 | */ 256 | public function getHost(): ?Host 257 | { 258 | if ($this->host === null) { 259 | $this->host = new Host($this); 260 | } 261 | 262 | return $this->host; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/Adapter/ServerQuery/Event.php: -------------------------------------------------------------------------------- 1 | startsWith(TeamSpeak3::EVENT)) { 54 | throw new AdapterException("invalid notification event format"); 55 | } 56 | 57 | list($type, $data) = $evt->split(TeamSpeak3::SEPARATOR_CELL, 2); 58 | 59 | if (empty($data)) { 60 | throw new AdapterException("invalid notification event data"); 61 | } 62 | 63 | $fake = new StringHelper(TeamSpeak3::ERROR . TeamSpeak3::SEPARATOR_CELL . "id" . TeamSpeak3::SEPARATOR_PAIR . 0 . TeamSpeak3::SEPARATOR_CELL . "msg" . TeamSpeak3::SEPARATOR_PAIR . "ok"); 64 | $repl = new Reply([$data, $fake], $type); 65 | 66 | $this->type = $type->substr(strlen(TeamSpeak3::EVENT)); 67 | $this->data = $repl->toList(); 68 | $this->mesg = $data; 69 | 70 | Signal::getInstance()->emit("notifyEvent", $this, $con); 71 | Signal::getInstance()->emit("notify" . ucfirst($this->type), $this, $con); 72 | } 73 | 74 | /** 75 | * Returns the event type string. 76 | * 77 | * @return StringHelper 78 | */ 79 | public function getType(): StringHelper 80 | { 81 | return $this->type; 82 | } 83 | 84 | /** 85 | * Returns the event data array. 86 | * 87 | * @return array 88 | */ 89 | public function getData(): array 90 | { 91 | return $this->data; 92 | } 93 | 94 | /** 95 | * Returns the event data as an unparsed string. 96 | * 97 | * @return StringHelper 98 | */ 99 | public function getMessage(): StringHelper 100 | { 101 | return $this->mesg; 102 | } 103 | 104 | /** 105 | * @ignore 106 | */ 107 | public function offsetExists($offset): bool 108 | { 109 | return array_key_exists($offset, $this->data); 110 | } 111 | 112 | /** 113 | * @throws ServerQueryException 114 | * @ignore 115 | */ 116 | public function offsetGet($offset): mixed 117 | { 118 | if (!$this->offsetExists($offset)) { 119 | throw new ServerQueryException("invalid parameter", 0x602); 120 | } 121 | 122 | return $this->data[$offset]; 123 | } 124 | 125 | /** 126 | * @throws NodeException 127 | * @ignore 128 | */ 129 | public function offsetSet($offset, $value): void 130 | { 131 | throw new NodeException("event '" . $this->getType() . "' is read only"); 132 | } 133 | 134 | /** 135 | * @ignore 136 | */ 137 | public function offsetUnset($offset): void 138 | { 139 | unset($this->data[$offset]); 140 | } 141 | 142 | /** 143 | * @throws ServerQueryException 144 | * @ignore 145 | */ 146 | public function __get($offset) 147 | { 148 | return $this->offsetGet($offset); 149 | } 150 | 151 | /** 152 | * @throws NodeException 153 | * @ignore 154 | */ 155 | public function __set($offset, $value) 156 | { 157 | $this->offsetSet($offset, $value); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Exception/AdapterException.php: -------------------------------------------------------------------------------- 1 | return_code = $return_code; 32 | } 33 | 34 | /** 35 | * Returns TRUE if the exception provides a return code for ServerQuery errors. 36 | * 37 | * @return boolean 38 | */ 39 | public function hasReturnCode(): bool 40 | { 41 | return $this->return_code !== null; 42 | } 43 | 44 | /** 45 | * Returns the optional return code for ServerQuery errors. 46 | * 47 | * @return string|null 48 | */ 49 | public function getReturnCode(): ?string 50 | { 51 | return $this->return_code; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Exception/SignalException.php: -------------------------------------------------------------------------------- 1 | raw_code = $code; 49 | $this->raw_mesg = $mesg; 50 | 51 | if (array_key_exists($code, self::$messages)) { 52 | $this->message = $this->prepareCustomMessage(self::$messages[$code]); 53 | } 54 | 55 | Signal::getInstance()->emit("errorException", $this); 56 | } 57 | 58 | /** 59 | * Prepares a custom error message by replacing pre-defined signs with given values. 60 | * 61 | * @param StringHelper $mesg 62 | * @return string 63 | */ 64 | protected function prepareCustomMessage(StringHelper $mesg): string 65 | { 66 | $args = [ 67 | "code" => $this->getCode(), 68 | "mesg" => $this->getMessage(), 69 | "line" => $this->getLine(), 70 | "file" => $this->getFile(), 71 | ]; 72 | 73 | return $mesg->arg($args)->toString(); 74 | } 75 | 76 | /** 77 | * Registers a custom error message to $code. 78 | * 79 | * @param integer $code 80 | * @param string $mesg 81 | * @return void 82 | * @throws TeamSpeak3Exception 83 | */ 84 | public static function registerCustomMessage(int $code, string $mesg): void 85 | { 86 | if (array_key_exists($code, self::$messages)) { 87 | throw new self("custom message for code 0x" . strtoupper(dechex($code)) . " is already registered"); 88 | } 89 | 90 | self::$messages[$code] = new StringHelper($mesg); 91 | } 92 | 93 | /** 94 | * Unregisters a custom error message from $code. 95 | * 96 | * @param integer $code 97 | * @return void 98 | * @throws TeamSpeak3Exception 99 | */ 100 | public static function unregisterCustomMessage(int $code): void 101 | { 102 | if (!array_key_exists($code, self::$messages)) { 103 | throw new self("custom message for code 0x" . strtoupper(dechex($code)) . " is not registered"); 104 | } 105 | 106 | unset(self::$messages[$code]); 107 | } 108 | 109 | /** 110 | * Returns the original error code. 111 | * 112 | * @return integer 113 | */ 114 | public function getRawCode(): int 115 | { 116 | return $this->raw_code; 117 | } 118 | 119 | /** 120 | * Returns the original error message. 121 | * 122 | * @return string|StringHelper|null 123 | */ 124 | public function getRawMessage(): string|StringHelper|null 125 | { 126 | return $this->raw_mesg; 127 | } 128 | 129 | /** 130 | * Returns the class from which the exception was thrown. 131 | * 132 | * @return string 133 | */ 134 | public function getSender(): string 135 | { 136 | $trace = $this->getTrace(); 137 | 138 | return (isset($trace[0]["class"])) ? $trace[0]["class"] : "{main}"; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Exception/TransportException.php: -------------------------------------------------------------------------------- 1 | char = $char; 35 | } 36 | 37 | /** 38 | * Returns true if the character is a letter. 39 | * 40 | * @return boolean 41 | */ 42 | public function isLetter(): bool 43 | { 44 | return ctype_alpha($this->char); 45 | } 46 | 47 | /** 48 | * Returns true if the character is a decimal digit. 49 | * 50 | * @return boolean 51 | */ 52 | public function isDigit(): bool 53 | { 54 | return ctype_digit($this->char); 55 | } 56 | 57 | /** 58 | * Returns true if the character is a space. 59 | * 60 | * @return boolean 61 | */ 62 | public function isSpace(): bool 63 | { 64 | return ctype_space($this->char); 65 | } 66 | 67 | /** 68 | * Returns true if the character is a mark. 69 | * 70 | * @return boolean 71 | */ 72 | public function isMark(): bool 73 | { 74 | return ctype_punct($this->char); 75 | } 76 | 77 | /** 78 | * Returns true if the character is a control character (i.e. "\t"). 79 | * 80 | * @return boolean 81 | */ 82 | public function isControl(): bool 83 | { 84 | return ctype_cntrl($this->char); 85 | } 86 | 87 | /** 88 | * Returns true if the character is a printable character. 89 | * 90 | * @return boolean 91 | */ 92 | public function isPrintable(): bool 93 | { 94 | return ctype_print($this->char); 95 | } 96 | 97 | /** 98 | * Returns true if the character is the Unicode character 0x0000 ("\0"). 99 | * 100 | * @return boolean 101 | */ 102 | public function isNull(): bool 103 | { 104 | return $this->char === "\0"; 105 | } 106 | 107 | /** 108 | * Returns true if the character is an uppercase letter. 109 | * 110 | * @return boolean 111 | */ 112 | public function isUpper(): bool 113 | { 114 | return $this->char === strtoupper($this->char); 115 | } 116 | 117 | /** 118 | * Returns true if the character is a lowercase letter. 119 | * 120 | * @return boolean 121 | */ 122 | public function isLower(): bool 123 | { 124 | return $this->char === strtolower($this->char); 125 | } 126 | 127 | /** 128 | * Returns the uppercase equivalent if the character is lowercase. 129 | * 130 | * @return Char 131 | * @throws HelperException 132 | */ 133 | public function toUpper(): Char 134 | { 135 | return ($this->isUpper()) ? $this : new self(strtoupper($this)); 136 | } 137 | 138 | /** 139 | * Returns the lowercase equivalent if the character is uppercase. 140 | * 141 | * @return Char 142 | * @throws HelperException 143 | */ 144 | public function toLower(): Char 145 | { 146 | return ($this->isLower()) ? $this : new self(strtolower($this)); 147 | } 148 | 149 | /** 150 | * Returns the ascii value of the character. 151 | * 152 | * @return integer 153 | */ 154 | public function toAscii(): int 155 | { 156 | return ord($this->char); 157 | } 158 | 159 | /** 160 | * Returns the Unicode value of the character. 161 | * 162 | * @return integer 163 | */ 164 | public function toUnicode(): int 165 | { 166 | $h = ord($this->char[0]); 167 | 168 | if ($h <= 0x7F) { 169 | return $h; 170 | } elseif ($h < 0xC2) { 171 | return false; 172 | } elseif ($h <= 0xDF) { 173 | return ($h & 0x1F) << 6 | (ord($this->char[1]) & 0x3F); 174 | } elseif ($h <= 0xEF) { 175 | return ($h & 0x0F) << 12 | (ord($this->char[1]) & 0x3F) << 6 | (ord($this->char[2]) & 0x3F); 176 | } elseif ($h <= 0xF4) { 177 | return ($h & 0x0F) << 18 | (ord($this->char[1]) & 0x3F) << 12 | (ord($this->char[2]) & 0x3F) << 6 | (ord($this->char[3]) & 0x3F); 178 | } else { 179 | return -1; 180 | } 181 | } 182 | 183 | /** 184 | * Returns the hexadecimal value of the char. 185 | * 186 | * @return string 187 | */ 188 | public function toHex(): string 189 | { 190 | return strtoupper(dechex($this->toAscii())); 191 | } 192 | 193 | /** 194 | * Returns the PlanetTeamSpeak\TeamSpeak3Framework\Helper\Char based on a given hex value. 195 | * 196 | * @param string $hex 197 | * @return Char 198 | * @throws HelperException 199 | */ 200 | public static function fromHex(string $hex): Char 201 | { 202 | if (strlen($hex) != 2) { 203 | throw new HelperException("given parameter '" . $hex . "' is not a valid hexadecimal number"); 204 | } 205 | 206 | return new self(chr(hexdec($hex))); 207 | } 208 | 209 | /** 210 | * Returns the character as a standard string. 211 | * 212 | * @return string 213 | */ 214 | public function toString(): string 215 | { 216 | return $this->char; 217 | } 218 | 219 | /** 220 | * Returns the integer value of the character. 221 | * 222 | * @return integer 223 | */ 224 | public function toInt(): int 225 | { 226 | return intval($this->char); 227 | } 228 | 229 | /** 230 | * Returns the character as a standard string. 231 | * 232 | * @return string 233 | */ 234 | public function __toString() 235 | { 236 | return $this->char; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/Helper/Profiler.php: -------------------------------------------------------------------------------- 1 | start(); 43 | } else { 44 | self::$timers[$name] = new Timer($name); 45 | } 46 | } 47 | 48 | /** 49 | * Stops a timer. 50 | * 51 | * @param string $name 52 | * @return void 53 | */ 54 | public static function stop(string $name = "default"): void 55 | { 56 | if (!array_key_exists($name, self::$timers)) { 57 | self::init($name); 58 | } 59 | 60 | self::$timers[$name]->stop(); 61 | } 62 | 63 | /** 64 | * Returns a timer. 65 | * 66 | * @param string $name 67 | * @return Timer 68 | */ 69 | public static function get(string $name = "default"): Timer 70 | { 71 | if (!array_key_exists($name, self::$timers)) { 72 | self::init($name); 73 | } 74 | 75 | return self::$timers[$name]; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Helper/Profiler/Timer.php: -------------------------------------------------------------------------------- 1 | name = $name; 49 | 50 | $this->data["runtime"] = 0; 51 | $this->data["realmem"] = 0; 52 | $this->data["emalloc"] = 0; 53 | 54 | $this->start(); 55 | } 56 | 57 | /** 58 | * Starts the timer. 59 | * 60 | * @return void 61 | */ 62 | public function start(): void 63 | { 64 | if ($this->isRunning()) { 65 | return; 66 | } 67 | 68 | $this->data["realmem_start"] = memory_get_usage(true); 69 | $this->data["emalloc_start"] = memory_get_usage(); 70 | 71 | $this->started = microtime(true); 72 | $this->running = true; 73 | } 74 | 75 | /** 76 | * Stops the timer. 77 | * 78 | * @return void 79 | */ 80 | public function stop(): void 81 | { 82 | if (!$this->isRunning()) { 83 | return; 84 | } 85 | 86 | $this->data["runtime"] += microtime(true) - $this->started; 87 | $this->data["realmem"] += memory_get_usage(true) - $this->data["realmem_start"]; 88 | $this->data["emalloc"] += memory_get_usage() - $this->data["emalloc_start"]; 89 | 90 | $this->started = 0; 91 | $this->running = false; 92 | } 93 | 94 | /** 95 | * Return the timer runtime in microseconds. 96 | * 97 | * @return float 98 | */ 99 | public function getRuntime(): float 100 | { 101 | if ($this->isRunning()) { 102 | $this->stop(); 103 | $this->start(); 104 | } 105 | 106 | return $this->data["runtime"]; 107 | } 108 | 109 | /** 110 | * Returns the amount of memory allocated to PHP in bytes. 111 | * 112 | * @param boolean $realmem 113 | * @return integer 114 | */ 115 | public function getMemUsage(bool $realmem = false): int 116 | { 117 | if ($this->isRunning()) { 118 | $this->stop(); 119 | $this->start(); 120 | } 121 | 122 | return ($realmem !== false) ? $this->data["realmem"] : $this->data["emalloc"]; 123 | } 124 | 125 | /** 126 | * Returns TRUE if the timer is running. 127 | * 128 | * @return boolean 129 | */ 130 | public function isRunning(): bool 131 | { 132 | return $this->running; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Helper/Signal.php: -------------------------------------------------------------------------------- 1 | call() return value. 38 | * It appears all previous calls before last are lost / ignored. 39 | * 40 | */ 41 | public function emit(string $signal, mixed $params = null): mixed 42 | { 43 | if (!$this->hasHandlers($signal)) { 44 | return null; 45 | } 46 | 47 | if (!is_array($params)) { 48 | $params = func_get_args(); 49 | $params = array_slice($params, 1); 50 | } 51 | 52 | foreach ($this->sigslots[$signal] as $slot) { 53 | $return = $slot->call($params); 54 | } 55 | 56 | return $return; 57 | } 58 | 59 | /** 60 | * Generates a MD5 hash based on a given callback. 61 | * 62 | * @param mixed $callback 63 | * @return string 64 | * @throws HelperException 65 | */ 66 | public function getCallbackHash(mixed $callback): string 67 | { 68 | if (!is_callable($callback, true, $callable_name)) { 69 | throw new SignalException("invalid callback specified"); 70 | } 71 | 72 | return md5($callable_name); 73 | } 74 | 75 | /** 76 | * Subscribes to a signal and returns the signal handler. 77 | * 78 | * @param string $signal 79 | * @param mixed $callback 80 | * @return Handler 81 | * @throws HelperException 82 | */ 83 | public function subscribe(string $signal, mixed $callback): Handler 84 | { 85 | if (empty($this->sigslots[$signal])) { 86 | $this->sigslots[$signal] = []; 87 | } 88 | 89 | $index = $this->getCallbackHash($callback); 90 | 91 | if (!array_key_exists($index, $this->sigslots[$signal])) { 92 | $this->sigslots[$signal][$index] = new Handler($signal, $callback); 93 | } 94 | 95 | return $this->sigslots[$signal][$index]; 96 | } 97 | 98 | /** 99 | * Unsubscribes from a signal. 100 | * 101 | * @param string $signal 102 | * @param mixed|null $callback 103 | * @return void 104 | * @throws HelperException 105 | */ 106 | public function unsubscribe(string $signal, mixed $callback = null): void 107 | { 108 | if (!$this->hasHandlers($signal)) { 109 | return; 110 | } 111 | 112 | if ($callback !== null) { 113 | $index = $this->getCallbackHash($callback); 114 | 115 | if (!array_key_exists($index, $this->sigslots[$signal])) { 116 | return; 117 | } 118 | 119 | unset($this->sigslots[$signal][$index]); 120 | } else { 121 | unset($this->sigslots[$signal]); 122 | } 123 | } 124 | 125 | /** 126 | * Returns all registered signals. 127 | * 128 | * @return array 129 | */ 130 | public function getSignals(): array 131 | { 132 | return array_keys($this->sigslots); 133 | } 134 | 135 | /** 136 | * Returns TRUE there are slots subscribed for a specified signal. 137 | * 138 | * @param string $signal 139 | * @return boolean 140 | */ 141 | public function hasHandlers(string $signal): bool 142 | { 143 | return !empty($this->sigslots[$signal]); 144 | } 145 | 146 | /** 147 | * Returns all slots for a specified signal. 148 | * 149 | * @param string $signal 150 | * @return array 151 | */ 152 | public function getHandlers(string $signal): array 153 | { 154 | if ($this->hasHandlers($signal)) { 155 | return $this->sigslots[$signal]; 156 | } 157 | 158 | return []; 159 | } 160 | 161 | /** 162 | * Clears all slots for a specified signal. 163 | * 164 | * @param string $signal 165 | * @return void 166 | */ 167 | public function clearHandlers(string $signal): void 168 | { 169 | if ($this->hasHandlers($signal)) { 170 | unset($this->sigslots[$signal]); 171 | } 172 | } 173 | 174 | /** 175 | * Returns a singleton instance of PlanetTeamSpeak\TeamSpeak3Framework\Helper\Signal. 176 | * 177 | * @return Signal|null 178 | */ 179 | public static function getInstance(): ?Signal 180 | { 181 | if (self::$instance === null) { 182 | self::$instance = new self(); 183 | } 184 | 185 | return self::$instance; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Helper/Signal/Handler.php: -------------------------------------------------------------------------------- 1 | signal = $signal; 39 | 40 | if (!is_callable($callback)) { 41 | throw new SignalException("invalid callback specified for signal '" . $signal . "'"); 42 | } 43 | 44 | $this->callback = $callback; 45 | } 46 | 47 | /** 48 | * Invoke the signal handler. 49 | * 50 | * @param array $args 51 | * @return mixed 52 | */ 53 | public function call(array $args = []): mixed 54 | { 55 | return call_user_func_array($this->callback, $args); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Node/ChannelGroup.php: -------------------------------------------------------------------------------- 1 | parent = $server; 28 | $this->nodeInfo = $info; 29 | 30 | if (!array_key_exists($index, $this->nodeInfo)) { 31 | throw new ServerQueryException("invalid groupID", 0xA00); 32 | } 33 | 34 | $this->nodeId = $this->nodeInfo[$index]; 35 | } 36 | 37 | /** 38 | * Renames the channel group specified. 39 | * 40 | * @param string $name 41 | * @return void 42 | */ 43 | public function rename(string $name): void 44 | { 45 | $this->getParent()->channelGroupRename($this->getId(), $name); 46 | } 47 | 48 | /** 49 | * Deletes the channel group. If $force is set to TRUE, the channel group will be 50 | * deleted even if there are clients within. 51 | * 52 | * @param boolean $force 53 | * @return void 54 | */ 55 | public function delete(bool $force = false): void 56 | { 57 | $this->getParent()->channelGroupDelete($this->getId(), $force); 58 | } 59 | 60 | /** 61 | * Creates a copy of the channel group and returns the new groups ID. 62 | * 63 | * @param string|null $name 64 | * @param integer $tcgid 65 | * @param integer $type 66 | * @return integer 67 | */ 68 | public function copy(string $name = null, int $tcgid = 0, int $type = TeamSpeak3::GROUP_DBTYPE_REGULAR): int 69 | { 70 | return $this->getParent()->channelGroupCopy($this->getId(), $name, $tcgid, $type); 71 | } 72 | 73 | /** 74 | * Returns a list of permissions assigned to the channel group. 75 | * 76 | * @param boolean $permsid 77 | * @return array 78 | */ 79 | public function permList(bool $permsid = false): array 80 | { 81 | return $this->getParent()->channelGroupPermList($this->getId(), $permsid); 82 | } 83 | 84 | /** 85 | * Adds a set of specified permissions to the channel group. Multiple permissions 86 | * can be added by providing the two parameters of each permission in separate arrays. 87 | * 88 | * @param integer $permid 89 | * @param integer $permvalue 90 | * @return void 91 | */ 92 | public function permAssign(int $permid, int $permvalue): void 93 | { 94 | $this->getParent()->channelGroupPermAssign($this->getId(), $permid, $permvalue); 95 | } 96 | 97 | /** 98 | * Alias for permAssign(). 99 | * 100 | * @deprecated 101 | */ 102 | public function permAssignByName($permname, $permvalue) 103 | { 104 | $this->permAssign($permname, $permvalue); 105 | } 106 | 107 | /** 108 | * Removes a set of specified permissions from the channel group. Multiple 109 | * permissions can be removed at once. 110 | * 111 | * @param integer $permid 112 | * @return void 113 | */ 114 | public function permRemove(int $permid): void 115 | { 116 | $this->getParent()->channelGroupPermRemove($this->getId(), $permid); 117 | } 118 | 119 | /** 120 | * Alias for permAssign(). 121 | * 122 | * @deprecated 123 | */ 124 | public function permRemoveByName($permname) 125 | { 126 | $this->permRemove($permname); 127 | } 128 | 129 | /** 130 | * Returns a list of clients assigned to the channel group specified. 131 | * 132 | * @param integer|null $cid 133 | * @param integer|null $cldbid 134 | * @param boolean $resolve 135 | * @return array 136 | */ 137 | public function clientList(int $cid = null, int $cldbid = null, bool $resolve = false): array 138 | { 139 | return $this->getParent()->channelGroupClientList($this->getId(), $cid, $cldbid, $resolve); 140 | } 141 | 142 | /** 143 | * Alias for privilegeKeyCreate(). 144 | * 145 | * @deprecated 146 | */ 147 | public function tokenCreate($cid, $description = null, $customset = null): StringHelper 148 | { 149 | return $this->privilegeKeyCreate($cid, $description, $customset); 150 | } 151 | 152 | /** 153 | * Creates a new privilege key (token) for the channel group and returns the key. 154 | * 155 | * @param integer $cid 156 | * @param string|null $description 157 | * @param string|null $customset 158 | * @return StringHelper 159 | */ 160 | public function privilegeKeyCreate(int $cid, string $description = null, string $customset = null): StringHelper 161 | { 162 | return $this->getParent()->privilegeKeyCreate($this->getId(), TeamSpeak3::TOKEN_CHANNELGROUP, $cid, $description, $customset); 163 | } 164 | 165 | /** 166 | * @ignore 167 | */ 168 | protected function fetchNodeList() 169 | { 170 | $this->nodeList = []; 171 | 172 | foreach ($this->getParent()->clientList() as $client) { 173 | if ($client["client_channel_group_id"] == $this->getId()) { 174 | $this->nodeList[] = $client; 175 | } 176 | } 177 | } 178 | 179 | /** 180 | * Returns a unique identifier for the node which can be used as a HTML property. 181 | * 182 | * @return string 183 | */ 184 | public function getUniqueId(): string 185 | { 186 | return $this->getParent()->getUniqueId() . "_cg" . $this->getId(); 187 | } 188 | 189 | /** 190 | * Returns the name of a possible icon to display the node object. 191 | * 192 | * @return string 193 | */ 194 | public function getIcon(): string 195 | { 196 | return "group_channel"; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Node/Group.php: -------------------------------------------------------------------------------- 1 | execute("sendtextmessage", ["msg" => $msg, "target" => $client, "targetmode" => TeamSpeak3::TEXTMSG_CLIENT]); 26 | } catch (ServerQueryException $e) { 27 | /* ERROR_client_invalid_id */ 28 | if ($e->getCode() != 0x0200) { 29 | throw $e; 30 | } 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * Downloads and returns the channel groups icon file content. 37 | * 38 | * @return StringHelper|void 39 | * @throws AdapterException 40 | * @throws HelperException 41 | * @throws ServerQueryException 42 | */ 43 | public function iconDownload() 44 | { 45 | $iconid = $this['iconid']; 46 | if (!is_int($iconid)) { 47 | $iconid = $iconid->toInt(); 48 | } 49 | 50 | if ($this->iconIsLocal("iconid") || $iconid == 0) { 51 | return; 52 | } 53 | 54 | $download = $this->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->iconGetName("iconid")); 55 | $transfer = TeamSpeak3::factory("filetransfer://" . (str_contains($download["host"], ":") ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); 56 | 57 | return $transfer->download($download["ftkey"], $download["size"]); 58 | } 59 | 60 | /** 61 | * Returns a symbol representing the node. 62 | * 63 | * @return string 64 | */ 65 | public function getSymbol(): string 66 | { 67 | return "%"; 68 | } 69 | 70 | /** 71 | * Returns a string representation of this node. 72 | * 73 | * @return string 74 | */ 75 | public function __toString() 76 | { 77 | return (string)$this["name"]; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Node/ServerGroup.php: -------------------------------------------------------------------------------- 1 | parent = $server; 26 | $this->nodeInfo = $info; 27 | 28 | if (!array_key_exists($index, $this->nodeInfo)) { 29 | throw new NodeException("invalid groupID", 0xA00); 30 | } 31 | 32 | $this->nodeId = $this->nodeInfo[$index]; 33 | } 34 | 35 | /** 36 | * Renames the server group specified. 37 | * 38 | * @param string $name 39 | * @return void 40 | */ 41 | public function rename(string $name): void 42 | { 43 | $this->getParent()->serverGroupRename($this->getId(), $name); 44 | } 45 | 46 | /** 47 | * Deletes the server group. If $force is set to 1, the server group will be 48 | * deleted even if there are clients within. 49 | * 50 | * @param boolean $force 51 | * @return void 52 | */ 53 | public function delete(bool $force = false): void 54 | { 55 | $this->getParent()->serverGroupDelete($this->getId(), $force); 56 | } 57 | 58 | /** 59 | * Creates a copy of the server group and returns the new groups ID. 60 | * 61 | * @param string|null $name 62 | * @param integer $tsgid 63 | * @param integer $type 64 | * @return integer 65 | */ 66 | public function copy(string $name = null, int $tsgid = 0, int $type = TeamSpeak3::GROUP_DBTYPE_REGULAR): int 67 | { 68 | return $this->getParent()->serverGroupCopy($this->getId(), $name, $tsgid, $type); 69 | } 70 | 71 | /** 72 | * Returns a list of permissions assigned to the server group. 73 | * 74 | * @param boolean $permsid 75 | * @return array 76 | */ 77 | public function permList(bool $permsid = false): array 78 | { 79 | return $this->getParent()->serverGroupPermList($this->getId(), $permsid); 80 | } 81 | 82 | /** 83 | * Adds a set of specified permissions to the server group. Multiple permissions 84 | * can be added by providing the four parameters of each permission in separate arrays. 85 | * 86 | * @param integer $permid 87 | * @param integer $permvalue 88 | * @param integer $permnegated 89 | * @param integer $permskip 90 | * @return void 91 | */ 92 | public function permAssign(int $permid, int $permvalue, int $permnegated = 0, int $permskip = 0): void 93 | { 94 | $this->getParent()->serverGroupPermAssign($this->getId(), $permid, $permvalue, $permnegated, $permskip); 95 | } 96 | 97 | /** 98 | * Alias for permAssign(). 99 | * 100 | * @deprecated 101 | */ 102 | public function permAssignByName($permname, $permvalue, $permnegated = false, $permskip = false) 103 | { 104 | $this->permAssign($permname, $permvalue, $permnegated, $permskip); 105 | } 106 | 107 | /** 108 | * Removes a set of specified permissions from the server group. Multiple 109 | * permissions can be removed at once. 110 | * 111 | * @param integer $permid 112 | * @return void 113 | */ 114 | public function permRemove(int $permid): void 115 | { 116 | $this->getParent()->serverGroupPermRemove($this->getId(), $permid); 117 | } 118 | 119 | /** 120 | * Alias for permRemove(). 121 | * 122 | * @deprecated 123 | */ 124 | public function permRemoveByName($permname) 125 | { 126 | $this->permRemove($permname); 127 | } 128 | 129 | /** 130 | * Returns a list of clients assigned to the server group specified. 131 | * 132 | * @return array 133 | */ 134 | public function clientList(): array 135 | { 136 | return $this->getParent()->serverGroupClientList($this->getId()); 137 | } 138 | 139 | /** 140 | * Adds a client to the server group specified. Please note that a client cannot be 141 | * added to default groups or template groups. 142 | * 143 | * @param integer $cldbid 144 | * @return void 145 | */ 146 | public function clientAdd(int $cldbid): void 147 | { 148 | $this->getParent()->serverGroupClientAdd($this->getId(), $cldbid); 149 | } 150 | 151 | /** 152 | * Removes a client from the server group. 153 | * 154 | * @param integer $cldbid 155 | * @return void 156 | */ 157 | public function clientDel(int $cldbid): void 158 | { 159 | $this->getParent()->serverGroupClientDel($this->getId(), $cldbid); 160 | } 161 | 162 | /** 163 | * Alias for privilegeKeyCreate(). 164 | * 165 | * @deprecated 166 | */ 167 | public function tokenCreate($description = null, $customset = null): string 168 | { 169 | return $this->privilegeKeyCreate($description, $customset); 170 | } 171 | 172 | /** 173 | * Creates a new privilege key (token) for the server group and returns the key. 174 | * 175 | * @param string|null $description 176 | * @param string|null $customset 177 | * @return string 178 | */ 179 | public function privilegeKeyCreate(string $description = null, string $customset = null): string 180 | { 181 | return $this->getParent() 182 | ->privilegeKeyCreate($this->getId(), TeamSpeak3::TOKEN_SERVERGROUP, 0, $description, $customset); 183 | } 184 | 185 | /** 186 | * @ignore 187 | */ 188 | protected function fetchNodeList() 189 | { 190 | $this->nodeList = []; 191 | 192 | foreach ($this->getParent()->clientList() as $client) { 193 | if (in_array($this->getId(), explode(",", $client["client_servergroups"]))) { 194 | $this->nodeList[] = $client; 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * Returns a unique identifier for the node which can be used as a HTML property. 201 | * 202 | * @return string 203 | */ 204 | public function getUniqueId(): string 205 | { 206 | return $this->getParent()->getUniqueId() . "_sg" . $this->getId(); 207 | } 208 | 209 | /** 210 | * Returns the name of a possible icon to display the node object. 211 | * 212 | * @return string 213 | */ 214 | public function getIcon(): string 215 | { 216 | return "group_server"; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Transport/MockTCP.php: -------------------------------------------------------------------------------- 1 | " for information on a specific command.'; 13 | public const S_ERROR_OK = 'error id=0 msg=ok'; 14 | 15 | public const CMD = [ 16 | 'login serveradmin secret' => self::S_ERROR_OK, 17 | 'login client_login_name=serveradmin client_login_password=secret' => self::S_ERROR_OK, 18 | ]; 19 | 20 | protected mixed $reply = null; 21 | 22 | public function connect(): void 23 | { 24 | if ($this->stream !== null) { 25 | return; 26 | } 27 | 28 | $this->reply = sprintf("%s\n%s\n", self::S_WELCOME_L0, self::S_WELCOME_L1); 29 | $this->stream = true; 30 | } 31 | 32 | public function readLine(string $token = "\n"): StringHelper 33 | { 34 | $line = StringHelper::factory(""); 35 | $this->connect(); 36 | 37 | while (!$line->endsWith($token)) { 38 | // $this->waitForReadyRead(); 39 | 40 | $data = $this->fget(); 41 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); 42 | if (!$data) { 43 | if ($line->count()) { 44 | $line->append($token); 45 | } else { 46 | throw new TransportException("connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "' lost"); 47 | } 48 | } else { 49 | $line->append($data); 50 | } 51 | } 52 | 53 | return $line->trim(); 54 | } 55 | 56 | public function sendLine(string $data, string $separator = "\n"): void 57 | { 58 | $this->send($data); 59 | } 60 | 61 | /** 62 | * Writes data to the stream. 63 | * 64 | * @param string $data 65 | * @return void 66 | */ 67 | public function send(string $data): void 68 | { 69 | $this->fwrite($data); 70 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataSend", $data); 71 | } 72 | 73 | protected function fget(): string 74 | { 75 | $this->reply = explode("\n", $this->reply); 76 | $reply = array_shift($this->reply); 77 | $this->reply = join("\n", $this->reply); 78 | return $reply . "\n"; 79 | } 80 | 81 | protected function fwrite($data) 82 | { 83 | if (!key_exists($data, self::CMD)) { 84 | $this->reply = "error id=1 msg=Unkown\n"; 85 | return; 86 | } 87 | 88 | $this->reply = sprintf("%s\n%s\n", self::CMD[$data], self::S_ERROR_OK); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Transport/TCP.php: -------------------------------------------------------------------------------- 1 | stream !== null) { 28 | return; 29 | } 30 | 31 | $host = strval($this->config["host"]); 32 | $port = strval($this->config["port"]); 33 | $timeout = intval($this->config["timeout"]); 34 | $blocking = intval($this->config["blocking"]); 35 | 36 | if (empty($this->config["ssh"])) { 37 | $address = "tcp://" . (str_contains($host, ":") ? "[" . $host . "]" : $host) . ":" . $port; 38 | $options = empty($this->config["tls"]) ? [] : ["ssl" => ["allow_self_signed" => true, "verify_peer" => false, "verify_peer_name" => false]]; 39 | 40 | $this->stream = @stream_socket_client($address, $errno, $errstr, $this->config["timeout"], STREAM_CLIENT_CONNECT, stream_context_create($options)); 41 | 42 | if ($this->stream === false) { 43 | throw new TransportException(StringHelper::factory($errstr)->toUtf8()->toString(), $errno); 44 | } 45 | 46 | if (!empty($this->config["tls"])) { 47 | stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); 48 | } 49 | } else { 50 | $this->session = @ssh2_connect($host, $port); 51 | 52 | if ($this->session === false) { 53 | throw new TransportException("failed to establish secure shell connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "'"); 54 | } 55 | 56 | if (!@ssh2_auth_password($this->session, $this->config["username"], $this->config["password"])) { 57 | throw new ServerQueryException("invalid loginname or password", 0x208); 58 | } 59 | 60 | $this->stream = @ssh2_shell($this->session, "raw"); 61 | 62 | if ($this->stream === false) { 63 | throw new TransportException("failed to open a secure shell on server '" . $this->config["host"] . ":" . $this->config["port"] . "'"); 64 | } 65 | } 66 | 67 | @stream_set_timeout($this->stream, $timeout); 68 | @stream_set_blocking($this->stream, $blocking ? 1 : 0); 69 | } 70 | 71 | /** 72 | * Disconnects from a remote server. 73 | * 74 | * @return void 75 | */ 76 | public function disconnect(): void 77 | { 78 | if ($this->stream === null) { 79 | return; 80 | } 81 | 82 | $this->stream = null; 83 | 84 | if (is_resource($this->session)) { 85 | @ssh2_disconnect($this->session); 86 | } 87 | 88 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "Disconnected"); 89 | } 90 | 91 | /** 92 | * Reads data from the stream. 93 | * 94 | * @param integer $length 95 | * @return StringHelper 96 | * @throws TransportException 97 | * @throws ServerQueryException 98 | */ 99 | public function read(int $length = 4096): StringHelper 100 | { 101 | $this->connect(); 102 | $this->waitForReadyRead(); 103 | 104 | $data = @stream_get_contents($this->stream, $length); 105 | 106 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); 107 | 108 | if ($data === false) { 109 | throw new TransportException("connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "' lost"); 110 | } 111 | 112 | return new StringHelper($data); 113 | } 114 | 115 | /** 116 | * Reads a single line of data from the stream. 117 | * 118 | * @param string $token 119 | * @return StringHelper 120 | * @throws TransportException 121 | * @throws ServerQueryException 122 | */ 123 | public function readLine(string $token = "\n"): StringHelper 124 | { 125 | $this->connect(); 126 | 127 | $line = StringHelper::factory(""); 128 | 129 | while (!$line->endsWith($token)) { 130 | $this->waitForReadyRead(); 131 | 132 | $data = @fgets($this->stream, 4096); 133 | 134 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); 135 | 136 | if ($data === false) { 137 | if ($line->count()) { 138 | $line->append($token); 139 | } 140 | } else { 141 | $line->append($data); 142 | } 143 | } 144 | 145 | return $line->trim(); 146 | } 147 | 148 | /** 149 | * Writes data to the stream. 150 | * 151 | * @param string $data 152 | * @return void 153 | * @throws TransportException 154 | * @throws ServerQueryException 155 | */ 156 | public function send(string $data): void 157 | { 158 | $this->connect(); 159 | 160 | @fwrite($this->stream, $data); 161 | 162 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataSend", $data); 163 | } 164 | 165 | /** 166 | * Writes a line of data to the stream. 167 | * 168 | * @param string $data 169 | * @param string $separator 170 | * @return void 171 | * @throws TransportException 172 | * @throws ServerQueryException 173 | */ 174 | public function sendLine(string $data, string $separator = "\n"): void 175 | { 176 | $size = strlen($data); 177 | $pack = 4096; 178 | 179 | for ($seek = 0; $seek < $size;) { 180 | $rest = $size - $seek; 181 | $pack = min($rest, $pack); 182 | $buff = substr($data, $seek, $pack); 183 | $seek = $seek + $pack; 184 | 185 | if ($seek >= $size) { 186 | $buff .= $separator; 187 | } 188 | 189 | $this->send($buff); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Transport/Transport.php: -------------------------------------------------------------------------------- 1 | config = $config; 72 | return $this; 73 | } 74 | 75 | /** 76 | * Commit pending data. 77 | * 78 | * @return array 79 | */ 80 | public function __sleep() 81 | { 82 | return ["config"]; 83 | } 84 | 85 | /** 86 | * Reconnects to the remote server. 87 | * 88 | * @return void 89 | * @throws TransportException 90 | */ 91 | public function __wakeup() 92 | { 93 | $this->connect(); 94 | } 95 | 96 | /** 97 | * The PlanetTeamSpeak\TeamSpeak3Framework\Transport\Transport destructor. 98 | * 99 | * @return void 100 | */ 101 | public function __destruct() 102 | { 103 | if ($this->adapter instanceof Adapter) { 104 | $this->adapter->__destruct(); 105 | } 106 | 107 | $this->disconnect(); 108 | } 109 | 110 | /** 111 | * Connects to a remote server. 112 | * 113 | * @return void 114 | * @throws TransportException 115 | */ 116 | abstract public function connect(): void; 117 | 118 | /** 119 | * Disconnects from a remote server. 120 | * 121 | * @return void 122 | */ 123 | abstract public function disconnect(): void; 124 | 125 | /** 126 | * Reads data from the stream. 127 | * 128 | * @param integer $length 129 | * @return StringHelper 130 | * @throws TransportException 131 | */ 132 | abstract public function read(int $length = 4096): StringHelper; 133 | 134 | /** 135 | * Writes data to the stream. 136 | * 137 | * @param string $data 138 | * @return void 139 | */ 140 | abstract public function send(string $data): void; 141 | 142 | /** 143 | * Returns the underlying stream resource. 144 | * 145 | * @return resource 146 | */ 147 | public function getStream() 148 | { 149 | return $this->stream; 150 | } 151 | 152 | /** 153 | * Returns the configuration variables in this adapter. 154 | * 155 | * @param string|null $key 156 | * @param mixed|null $default 157 | * @return array|string 158 | */ 159 | public function getConfig(string $key = null, mixed $default = null): array|string|int 160 | { 161 | if ($key !== null) { 162 | return array_key_exists($key, $this->config) ? $this->config[$key] : $default; 163 | } 164 | 165 | return $this->config; 166 | } 167 | 168 | /** 169 | * Sets the PlanetTeamSpeak\TeamSpeak3Framework\Adapter\Adapter object using this transport. 170 | * 171 | * @param Adapter $adapter 172 | * @return void 173 | */ 174 | public function setAdapter(Adapter $adapter): void 175 | { 176 | $this->adapter = $adapter; 177 | } 178 | 179 | /** 180 | * Returns the PlanetTeamSpeak\TeamSpeak3Framework\Adapter\Adapter object using this transport. 181 | * 182 | * @return Adapter|null 183 | */ 184 | public function getAdapter(): ?Adapter 185 | { 186 | return $this->adapter; 187 | } 188 | 189 | /** 190 | * Returns the adapter type. 191 | * 192 | * @return string 193 | */ 194 | public function getAdapterType(): string 195 | { 196 | if ($this->adapter instanceof Adapter) { 197 | $string = StringHelper::factory(get_class($this->adapter)); 198 | 199 | return $string->substr($string->findLast("\\"))->replace(["\\", " "], "")->toString(); 200 | } 201 | 202 | return "Unknown"; 203 | } 204 | 205 | /** 206 | * Returns header/meta data from stream pointer. 207 | * 208 | * @return array 209 | * @throws TransportException 210 | */ 211 | public function getMetaData(): array 212 | { 213 | if ($this->stream === null) { 214 | throw new TransportException("unable to retrieve header/meta data from stream pointer"); 215 | } 216 | 217 | return stream_get_meta_data($this->stream); 218 | } 219 | 220 | /** 221 | * Returns TRUE if the transport is connected. 222 | * 223 | * @return boolean 224 | */ 225 | public function isConnected(): bool 226 | { 227 | return ($this->getStream() === null) ? false : true; 228 | } 229 | 230 | /** 231 | * Blocks a stream until data is available for reading if the stream is connected 232 | * in non-blocking mode. 233 | * 234 | * @param integer $time 235 | * @return void 236 | */ 237 | protected function waitForReadyRead(int $time = 0): void 238 | { 239 | if (!$this->isConnected() || $this->config["blocking"]) { 240 | return; 241 | } 242 | 243 | do { 244 | $read = [$this->stream]; 245 | $null = null; 246 | 247 | if ($time) { 248 | Signal::getInstance() 249 | ->emit(strtolower($this->getAdapterType()) . "WaitTimeout", $time, $this->getAdapter()); 250 | } 251 | 252 | $time = $time + $this->config["timeout"]; 253 | } while (@stream_select($read, $null, $null, $this->config["timeout"]) == 0); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/Transport/UDP.php: -------------------------------------------------------------------------------- 1 | stream !== null) { 26 | return; 27 | } 28 | 29 | $host = strval($this->config["host"]); 30 | $port = strval($this->config["port"]); 31 | 32 | $address = "udp://" . (str_contains($host, ":") ? "[" . $host . "]" : $host) . ":" . $port; 33 | $timeout = (int)$this->config["timeout"]; 34 | 35 | $this->stream = @stream_socket_client($address, $errno, $errstr, $timeout); 36 | 37 | if ($this->stream === false) { 38 | throw new TransportException(StringHelper::factory($errstr)->toUtf8()->toString(), $errno); 39 | } 40 | 41 | @stream_set_timeout($this->stream, $timeout); 42 | @stream_set_blocking($this->stream, $this->config["blocking"] ? 1 : 0); 43 | } 44 | 45 | /** 46 | * Disconnects from a remote server. 47 | * 48 | * @return void 49 | */ 50 | public function disconnect(): void 51 | { 52 | if ($this->stream === null) { 53 | return; 54 | } 55 | 56 | $this->stream = null; 57 | 58 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "Disconnected"); 59 | } 60 | 61 | /** 62 | * Reads data from the stream. 63 | * 64 | * @param integer $length 65 | * @return StringHelper 66 | * @throws TransportException 67 | */ 68 | public function read(int $length = 4096): StringHelper 69 | { 70 | $this->connect(); 71 | $this->waitForReadyRead(); 72 | 73 | $data = @fread($this->stream, $length); 74 | 75 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); 76 | 77 | if ($data === false) { 78 | throw new TransportException("connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "' lost"); 79 | } 80 | 81 | return new StringHelper($data); 82 | } 83 | 84 | /** 85 | * Writes data to the stream. 86 | * 87 | * @param string $data 88 | * @return void 89 | * @throws TransportException 90 | */ 91 | public function send(string $data): void 92 | { 93 | $this->connect(); 94 | 95 | @stream_socket_sendto($this->stream, $data); 96 | 97 | Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataSend", $data); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Viewer/Text.php: -------------------------------------------------------------------------------- 1 | currObj = $node; 32 | $this->currSib = $siblings; 33 | 34 | $args = [ 35 | $this->getPrefix(), 36 | $this->getCorpusIcon(), 37 | $this->getCorpusName(), 38 | ]; 39 | 40 | return StringHelper::factory($this->pattern)->arg($args); 41 | } 42 | 43 | /** 44 | * Returns the ASCII string to display the prefix of the current node. 45 | * 46 | * @return string 47 | */ 48 | protected function getPrefix(): string 49 | { 50 | $prefix = ""; 51 | 52 | if (count($this->currSib)) { 53 | $last = array_pop($this->currSib); 54 | 55 | foreach ($this->currSib as $sibling) { 56 | $prefix .= ($sibling) ? "| " : " "; 57 | } 58 | 59 | $prefix .= ($last) ? "\\-" : "|-"; 60 | } 61 | 62 | return $prefix; 63 | } 64 | 65 | /** 66 | * Returns an ASCII string which can be used to display the status icon for a 67 | * TeamSpeak_Node_Abstract object. 68 | * 69 | * @return string 70 | */ 71 | protected function getCorpusIcon(): string 72 | { 73 | return $this->currObj->getSymbol(); 74 | } 75 | 76 | /** 77 | * Returns a string for the current corpus element which contains the display name 78 | * for the current Node object. 79 | * 80 | * @return string 81 | */ 82 | protected function getCorpusName(): string 83 | { 84 | return $this->currObj; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Viewer/ViewerInterface.php: -------------------------------------------------------------------------------- 1 | " for information on a specific command.'; 24 | 25 | private static string $S_ERROR_OK = 'error id=0 msg=ok'; 26 | 27 | // Default virtual server 28 | // Response from `serverlist` command on default virtual server 29 | private static string $S_SERVERLIST = 'virtualserver_id=1 virtualserver_port=9987 virtualserver_status=online virtualserver_clientsonline=1 virtualserver_queryclientsonline=1 virtualserver_maxclients=32 virtualserver_uptime=5470 virtualserver_name=TeamSpeak\s]I[\sServer virtualserver_autostart=1 virtualserver_machine_id'; 30 | 31 | // Expected string output after parsing for `serverlist` command. 32 | private static string $E_SERVERLIST = 'virtualserver_id=1 virtualserver_port=9987 virtualserver_status=online virtualserver_clientsonline=1 virtualserver_queryclientsonline=1 virtualserver_maxclients=32 virtualserver_uptime=5470 virtualserver_name=TeamSpeak ]I[ Server virtualserver_autostart=1 virtualserver_machine_id'; 33 | 34 | // 3 users connected 35 | private static string $S_CLIENTLIST = 'clid=1 cid=1 client_database_id=1 client_nickname=serveradmin\sfrom\s[::1]:59642 client_type=1|clid=2 cid=1 client_database_id=3 client_nickname=Unknown\sfrom\s[::1]:59762 client_type=1|clid=3 cid=1 client_database_id=3 client_nickname=Unknown\sfrom\s[::1]:59766 client_type=1'; 36 | 37 | private static string $S_CHANNELLIST = 'cid=1 pid=0 channel_order=0 channel_name=Default\sChannel total_clients=3 channel_needed_subscribe_power=0|cid=2 pid=1 channel_order=0 channel_name=Test\sParent\s1 total_clients=0 channel_needed_subscribe_power=0|cid=3 pid=1 channel_order=2 channel_name=Test\sParent\s2 total_clients=0 channel_needed_subscribe_power=0|cid=5 pid=3 channel_order=0 channel_name=P2\s-\sSub\s1 total_clients=0 channel_needed_subscribe_power=0|cid=6 pid=3 channel_order=5 channel_name=P2\s-\sSub\s2 total_clients=0 channel_needed_subscribe_power=0|cid=4 pid=1 channel_order=3 channel_name=Test\sParent\s3 total_clients=0 channel_needed_subscribe_power=0|cid=7 pid=4 channel_order=0 channel_name=P3\s-\sSub\s1 total_clients=0 channel_needed_subscribe_power=0|cid=8 pid=4 channel_order=7 channel_name=P3\s-\sSub\s2 total_clients=0 channel_needed_subscribe_power=0'; 38 | 39 | // `clientlist` command with all parameters (single client) 40 | private static string $S_CLIENTLIST_EXTENDED_SINGLE = "clid=63 cid=53 client_database_id=25 client_nickname=HouseMaister-Radio\sBob\s(Rock) client_type=0 client_away=0 client_away_message client_flag_talking=0 client_input_muted=0 client_output_muted=0 client_input_hardware=1 client_output_hardware=1 client_talk_power=70 client_is_talker=0 client_is_priority_speaker=0 client_is_recording=0 client_is_channel_commander=0 client_unique_identifier=7seVjHY4Hbe2cSxTjyv8es7wv54= client_servergroups=147,227 client_channel_group_id=13 client_channel_group_inherited_channel_id=53 client_version=3.5.6\s[Build:\s1606312422] client_platform=Linux client_idle_time=102752 client_created=1684051977 client_lastconnected=1691241932 client_icon_id=0 client_country=DE connection_client_ip=fe80::9400:ff:fe2b::1 client_badges=Overwolf=1:badges=c2368518-3728-4260-bcd1-8b85e9f8984c"; 41 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLID = "63"; 42 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_NICKNAME = "HouseMaister-Radio\sBob\s(Rock)"; 43 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_AWAY_MESSAGE = ""; 44 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_FLAG_TALKING = "0"; 45 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_UNIQUE_IDENTIFIER = "7seVjHY4Hbe2cSxTjyv8es7wv54="; 46 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_SERVERGROUPS = "147,227"; 47 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_VERSION = "3.5.6\s[Build:\s1606312422]"; 48 | private static string $E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_BADGES = "Overwolf=1:badges=c2368518-3728-4260-bcd1-8b85e9f8984c"; 49 | 50 | /** 51 | * @throws AdapterException 52 | * @throws ServerQueryException 53 | */ 54 | public function testConstructor() 55 | { 56 | $reply = new Reply([ 57 | new StringHelper(static::$S_SERVERLIST), 58 | new StringHelper(static::$S_ERROR_OK) 59 | ]); 60 | 61 | $this->assertInstanceOf(Reply::class, $reply); 62 | } 63 | 64 | /** 65 | * @throws AdapterException 66 | * @throws ServerQueryException 67 | */ 68 | public function testToString() 69 | { 70 | $reply = new Reply([ 71 | new StringHelper(static::$S_SERVERLIST), 72 | new StringHelper(static::$S_ERROR_OK) 73 | ]); 74 | 75 | $this->assertEquals(static::$E_SERVERLIST, (string) $reply->toString()); 76 | } 77 | 78 | public function testToLines() 79 | { 80 | } 81 | public function testToTable() 82 | { 83 | } 84 | 85 | /** 86 | * Tests the toArray() function 87 | */ 88 | public function testToArray() 89 | { 90 | $reply = new Reply([ 91 | new StringHelper(static::$S_CLIENTLIST_EXTENDED_SINGLE), 92 | new StringHelper(static::$S_ERROR_OK) 93 | ]); 94 | 95 | $clientlist_array = $reply->toArray('clid')[0]; 96 | 97 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLID, (string) $clientlist_array['clid']); 98 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_NICKNAME, (string) $clientlist_array['client_nickname']); 99 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_AWAY_MESSAGE, (string) $clientlist_array['client_away_message']); 100 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_FLAG_TALKING, (string) $clientlist_array['client_flag_talking']); 101 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_UNIQUE_IDENTIFIER, (string) $clientlist_array['client_unique_identifier']); 102 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_SERVERGROUPS, (string) $clientlist_array['client_servergroups']); 103 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_VERSION, (string) $clientlist_array['client_version']); 104 | $this->assertEquals(static::$E_CLIENTLIST_EXTENDED_SINGLE_CLIENT_BADGES, (string) $clientlist_array['client_badges']); 105 | } 106 | 107 | public function testToAssocArray() 108 | { 109 | } 110 | public function testToList() 111 | { 112 | } 113 | public function testToObjectArray() 114 | { 115 | } 116 | public function testGetCommandString() 117 | { 118 | } 119 | public function testGetNotifyEvents() 120 | { 121 | } 122 | public function testGetErrorProperty() 123 | { 124 | } 125 | public function testFetchError() 126 | { 127 | //$this->assertInstanceOf(\TeamSpeak3_Adapter_ServerQuery_Reply::class, $reply); 128 | //$this->assertInternalType(PHPUnit_IsType::TYPE_INT, $reply->getErrorProperty('id')); 129 | //$this->assertEquals(0, $reply->getErrorProperty('id')); 130 | //$this->assertInternalType(PHPUnit_IsType::TYPE_STRING, $reply->getErrorProperty('msg')); 131 | //$this->assertEquals('ok', $reply->getErrorProperty('msg')); 132 | } 133 | public function testFetchReply() 134 | { 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/Adapter/ServerQueryTest.php: -------------------------------------------------------------------------------- 1 | '0.0.0.0', 'port' => 9987]); 20 | } 21 | 22 | /** 23 | * @throws ServerQueryException 24 | */ 25 | public function testRequestIllegalCharakterException() 26 | { 27 | $query = "Hello\nWorld\r"; 28 | 29 | $this->expectException(AdapterException::class); 30 | $this->expectExceptionMessage(sprintf("illegal characters in command '%s'", $query)); 31 | $serverQuery = $this->createMockServerQuery(); 32 | $serverQuery->request($query); 33 | } 34 | 35 | /** 36 | * @throws AdapterException 37 | * @throws ServerQueryException 38 | */ 39 | public function testLogin() 40 | { 41 | $query = "login serveradmin secret"; 42 | $serverQuery = $this->createMockServerQuery(); 43 | $reply = $serverQuery->request($query); 44 | $this->assertEquals("ok", $reply->getErrorProperty('msg')->toString()); 45 | 46 | $query = "login client_login_name=serveradmin client_login_password=secret"; 47 | $serverQuery = $this->createMockServerQuery(); 48 | $reply = $serverQuery->request($query); 49 | $this->assertEquals("ok", $reply->getErrorProperty('msg')->toString()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Helper/CryptTest.php: -------------------------------------------------------------------------------- 1 | expectException(ArgumentCountError::class); 15 | new Crypt(); 16 | } 17 | 18 | public function testSetSecretKeyWithTooFewCharacters() 19 | { 20 | $this->expectException(HelperException::class); 21 | new Crypt(""); 22 | } 23 | 24 | public function testSetSecretKeyWithTooManyCharacters() 25 | { 26 | $this->expectException(HelperException::class); 27 | new Crypt("Lorem ipsum dolor sit amet consetetur sadipscing elitr se"); 28 | } 29 | 30 | public function testEncrypt() 31 | { 32 | $crypto = new Crypt('My Secret Key'); 33 | $this->assertEquals('b45xr3dIAI4=', $crypto->encrypt('password')); 34 | } 35 | 36 | public function testDecrypt() 37 | { 38 | $crypto = new Crypt('My Secret Key'); 39 | $this->assertEquals('password', $crypto->decrypt('b45xr3dIAI4=')); 40 | } 41 | 42 | public function testDecryptWithDifferentSecret() 43 | { 44 | $crypto = new Crypt('My Secret Key'); 45 | $this->assertEquals('b45xr3dIAI4=', $crypto->encrypt('password')); 46 | $this->assertSame('password', $crypto->decrypt('b45xr3dIAI4=')); 47 | 48 | $crypto = new Crypt('The Secret Changed'); 49 | $this->assertNotSame('password', $crypto->decrypt('b45xr3dIAI4=')); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Helper/SignalTest.php: -------------------------------------------------------------------------------- 1 | rO*f=KS:8Yj'; 18 | 19 | protected function setUp(): void 20 | { 21 | static::$cTriggers = []; 22 | foreach (Signal::getInstance()->getSignals() as $signal) { 23 | Signal::getInstance()->clearHandlers($signal); 24 | } 25 | } 26 | 27 | public function testGetInstance() 28 | { 29 | $snapshot = clone Signal::getInstance(); 30 | $this->assertEquals($snapshot, Signal::getInstance()); 31 | $this->assertEmpty(Signal::getInstance()->getSignals()); 32 | } 33 | 34 | /** 35 | * @throws HelperException 36 | */ 37 | public function testGetCallbackHash() 38 | { 39 | $this->assertEquals( 40 | md5(static::$callback), 41 | Signal::getInstance()->getCallbackHash(static::$callback) 42 | ); 43 | } 44 | 45 | /** 46 | * @throws HelperException 47 | */ 48 | public function testGetCallbackHashException() 49 | { 50 | $this->expectException(SignalException::class); 51 | $this->expectExceptionMessage('invalid callback specified'); 52 | Signal::getInstance()->getCallbackHash([]); 53 | } 54 | 55 | /** 56 | * @throws HelperException 57 | */ 58 | public function testSubscribe() 59 | { 60 | $snapshot = clone Signal::getInstance(); 61 | $instSignal = Signal::getInstance(); 62 | 63 | $signalHandler = $instSignal->subscribe(static::$signal, static::$callback); 64 | // Test state: returned TeamSpeak3_Helper_Handler 65 | $this->assertInstanceOf(Handler::class, $signalHandler); 66 | $this->assertNotEquals($snapshot, Signal::getInstance()); 67 | 68 | // Test state: subscribed signals 69 | $signals = $instSignal->getSignals(); 70 | $this->assertIsArray($signals); 71 | $this->assertEquals(1, count($signals)); 72 | $this->assertEquals(static::$signal, $signals[0]); 73 | 74 | // Test state: subscribed signal handlers 75 | $handlers = $instSignal->getHandlers(static::$signal); 76 | $this->assertIsArray($handlers); 77 | $this->assertEquals(1, count($handlers)); 78 | $this->assertArrayHasKey( 79 | Signal::getInstance()->getCallbackHash(static::$callback), 80 | $handlers 81 | ); 82 | $handler = $handlers[Signal::getInstance()->getCallbackHash(static::$callback)]; 83 | $this->assertInstanceOf(Handler::class, $handler); 84 | $this->assertEquals($signalHandler, $handler); 85 | } 86 | 87 | /** 88 | * @throws HelperException 89 | */ 90 | public function testEmit() 91 | { 92 | $callbackHash = Signal::getInstance() 93 | ->getCallbackHash(__CLASS__ . '::onEvent'); 94 | Signal::getInstance()->subscribe(static::$signal, static::$callback); 95 | $response = Signal::getInstance()->emit(static::$signal, static::$testString); 96 | $this->assertEquals(static::$testString, $response); 97 | $this->assertIsString(static::$testString); 98 | $this->assertIsString($response); 99 | 100 | // Verify correct count of callback executions 101 | $this->assertEquals(1, count(static::$cTriggers)); 102 | $this->assertEquals( 103 | '0-'.static::$testString, 104 | static::$cTriggers[$callbackHash] 105 | ); 106 | } 107 | 108 | /** 109 | * @throws HelperException 110 | */ 111 | public function testSubscribeTwo() 112 | { 113 | $instSignal = Signal::getInstance(); 114 | $signalHandler1 = $instSignal->subscribe( 115 | static::$signal, 116 | static::$callback 117 | ); 118 | $signalHandler2 = $instSignal->subscribe( 119 | static::$signal, 120 | static::$callback.'2' 121 | ); 122 | 123 | // Test state: subscribed signals 124 | $signals = $instSignal->getSignals(); 125 | $this->assertEquals(1, count($signals)); 126 | $this->assertEquals(static::$signal, $signals[0]); 127 | 128 | // Test state: subscribed signal handlers 129 | $handlers = $instSignal->getHandlers(static::$signal); 130 | $this->assertEquals(2, count($handlers)); 131 | $this->assertArrayHasKey( 132 | $instSignal->getCallbackHash(static::$callback), 133 | $handlers 134 | ); 135 | $this->assertArrayHasKey( 136 | $instSignal->getCallbackHash(static::$callback.'2'), 137 | $handlers 138 | ); 139 | 140 | $handler1 = $handlers[$instSignal->getCallbackHash(static::$callback)]; 141 | $this->assertEquals($signalHandler1, $handler1); 142 | $handler2 = $handlers[$instSignal->getCallbackHash(static::$callback.'2')]; 143 | $this->assertEquals($signalHandler2, $handler2); 144 | } 145 | 146 | /** 147 | * @throws HelperException 148 | */ 149 | public function testEmitToTwoSubscribers() 150 | { 151 | $instSignal = Signal::getInstance(); 152 | $callbackHash1 = $instSignal->getCallbackHash(__CLASS__ . '::onEvent'); 153 | $callbackHash2 = $instSignal->getCallbackHash(__CLASS__ . '::onEvent2'); 154 | 155 | $instSignal->subscribe(static::$signal, static::$callback); 156 | $instSignal->subscribe(static::$signal, static::$callback.'2'); 157 | 158 | $response = $instSignal->emit(static::$signal, static::$testString); 159 | $this->assertEquals(static::$testString, $response); 160 | $this->assertIsString(static::$testString); 161 | $this->assertIsString($response); 162 | 163 | // Verify correct count of callback executions 164 | $this->assertEquals(2, count(static::$cTriggers)); 165 | $this->assertEquals( 166 | '0-' . static::$testString, 167 | static::$cTriggers[$callbackHash1] 168 | ); 169 | $this->assertEquals( 170 | '1-' . static::$testString, 171 | static::$cTriggers[$callbackHash2] 172 | ); 173 | } 174 | 175 | /** 176 | * @throws HelperException 177 | */ 178 | public static function onEvent($data) 179 | { 180 | $signature = Signal::getInstance() 181 | ->getCallbackHash(__CLASS__ . '::onEvent'); 182 | 183 | static::$cTriggers[$signature] = count(static::$cTriggers).'-'.$data; 184 | return $data; 185 | } 186 | 187 | /** 188 | * @throws HelperException 189 | */ 190 | public static function onEvent2($data) 191 | { 192 | $signature = Signal::getInstance() 193 | ->getCallbackHash(__CLASS__ . '::onEvent2'); 194 | 195 | static::$cTriggers[$signature] = count(static::$cTriggers).'-'.$data; 196 | return $data; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/Transport/TCPTest.php: -------------------------------------------------------------------------------- 1 | 'test', 'port' => 12345] 21 | ); 22 | $this->assertInstanceOf(TCP::class, $adapter); 23 | 24 | $this->assertArrayHasKey('host', $adapter->getConfig()); 25 | $this->assertEquals('test', $adapter->getConfig('host')); 26 | 27 | $this->assertArrayHasKey('port', $adapter->getConfig()); 28 | $this->assertEquals(12345, $adapter->getConfig('port')); 29 | 30 | $this->assertArrayHasKey('timeout', $adapter->getConfig()); 31 | $this->assertIsInt($adapter->getConfig('timeout')); 32 | 33 | $this->assertArrayHasKey('blocking', $adapter->getConfig()); 34 | $this->assertIsInt($adapter->getConfig('blocking')); 35 | } 36 | 37 | public function testConstructorExceptionNoHost() 38 | { 39 | $this->expectException(TransportException::class); 40 | $this->expectExceptionMessage("config must have a key for 'host'"); 41 | 42 | new TCP(['port' => 12345]); 43 | } 44 | 45 | public function testConstructorExceptionNoPort() 46 | { 47 | $this->expectException(TransportException::class); 48 | $this->expectExceptionMessage("config must have a key for 'port'"); 49 | 50 | new TCP(['host' => 'test']); 51 | } 52 | 53 | /** 54 | * @throws TransportException 55 | */ 56 | public function testGetConfig() 57 | { 58 | $adapter = new TCP( 59 | ['host' => 'test', 'port' => 12345] 60 | ); 61 | 62 | $this->assertIsArray($adapter->getConfig()); 63 | $this->assertCount(4, $adapter->getConfig()); 64 | $this->assertArrayHasKey('host', $adapter->getConfig()); 65 | $this->assertEquals('test', $adapter->getConfig()['host']); 66 | $this->assertEquals('test', $adapter->getConfig('host')); 67 | } 68 | 69 | /** 70 | * @throws TransportException 71 | */ 72 | public function testSetGetAdapter() 73 | { 74 | $transport = new TCP( 75 | ['host' => 'test', 'port' => 12345] 76 | ); 77 | // Mocking adaptor since `stream_socket_client()` depends on running server 78 | $adaptor = $this->createMock(ServerQuery::class); 79 | $transport->setAdapter($adaptor); 80 | 81 | $this->assertSame($adaptor, $transport->getAdapter()); 82 | } 83 | 84 | /** 85 | * @throws TransportException 86 | */ 87 | public function testGetStream() 88 | { 89 | $transport = new TCP( 90 | ['host' => 'test', 'port' => 12345] 91 | ); 92 | $this->assertNull($transport->getStream()); 93 | } 94 | 95 | /** 96 | * @throws AdapterException 97 | */ 98 | protected function createMockServerQuery(): MockServerQuery 99 | { 100 | return new MockServerQuery(['host' => '0.0.0.0', 'port' => 9987]); 101 | } 102 | 103 | /** 104 | * Tests if the connection status gets properly returned. 105 | */ 106 | public function testConnectionStatus() 107 | { 108 | $mockServerQuery = $this->createMockServerQuery(); 109 | $this->assertTrue($mockServerQuery->getTransport()->isConnected()); 110 | $mockServerQuery->getTransport()->disconnect(); 111 | $this->assertFalse($mockServerQuery->getTransport()->isConnected()); 112 | } 113 | 114 | /** 115 | * @throws TransportException 116 | * @throws ServerQueryException 117 | */ 118 | public function testConnectBadHost() 119 | { 120 | $host = 'test'; 121 | $transport = new TCP( 122 | ['host' => $host, 'port' => 12345] 123 | ); 124 | $this->expectException(TransportException::class); 125 | if (PHP_VERSION_ID < 80100) { 126 | $this->expectExceptionMessage("getaddrinfo failed"); 127 | } else { 128 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 129 | } 130 | $transport->connect(); 131 | } 132 | 133 | /** 134 | * @throws TransportException 135 | * @throws ServerQueryException 136 | */ 137 | public function testConnectHostRefuseConnection() 138 | { 139 | $transport = new TCP( 140 | ['host' => '127.0.0.1', 'port' => 12345] 141 | ); 142 | $this->expectException(TransportException::class); 143 | $this->expectExceptionMessage('Connection refused'); 144 | $transport->connect(); 145 | } 146 | 147 | /** 148 | * @throws TransportException 149 | */ 150 | public function testDisconnect() 151 | { 152 | $transport = new TCP( 153 | ['host' => '127.0.0.1', 'port' => 12345] 154 | ); 155 | $transport->disconnect(); 156 | $this->assertNull($transport->getStream()); 157 | } 158 | 159 | /** 160 | * @throws TransportException 161 | */ 162 | public function testDisconnectNoConnection() 163 | { 164 | $transport = new TCP( 165 | ['host' => 'test', 'port' => 12345] 166 | ); 167 | $this->assertNull($transport->getStream()); 168 | $transport->disconnect(); 169 | } 170 | 171 | /** 172 | * @throws ServerQueryException 173 | * @throws TransportException 174 | */ 175 | public function testReadNoConnection() 176 | { 177 | $host = 'test'; 178 | $transport = new TCP( 179 | ['host' => $host, 'port' => 12345] 180 | ); 181 | $this->expectException(TransportException::class); 182 | if (PHP_VERSION_ID < 80100) { 183 | $this->expectExceptionMessage("getaddrinfo failed"); 184 | } else { 185 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 186 | } 187 | $transport->read(); 188 | } 189 | 190 | /** 191 | * @throws TransportException 192 | * @throws ServerQueryException 193 | */ 194 | public function testReadLineNoConnection() 195 | { 196 | $host = 'test'; 197 | $transport = new TCP( 198 | ['host' => $host, 'port' => 12345] 199 | ); 200 | $this->expectException(TransportException::class); 201 | if (PHP_VERSION_ID < 80100) { 202 | $this->expectExceptionMessage("getaddrinfo failed"); 203 | } else { 204 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 205 | } 206 | $transport->readLine(); 207 | } 208 | 209 | /** 210 | * @throws TransportException 211 | * @throws ServerQueryException 212 | */ 213 | public function testSendNoConnection() 214 | { 215 | $host = 'test'; 216 | $transport = new TCP( 217 | ['host' => $host, 'port' => 12345] 218 | ); 219 | $this->expectException(TransportException::class); 220 | if (PHP_VERSION_ID < 80100) { 221 | $this->expectExceptionMessage("getaddrinfo failed"); 222 | } else { 223 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 224 | } 225 | $transport->send('testsend'); 226 | } 227 | 228 | /** 229 | * @throws ServerQueryException 230 | * @throws TransportException 231 | */ 232 | public function testSendLineNoConnection() 233 | { 234 | $host = 'abc'; 235 | $transport = new TCP( 236 | ['host' => $host, 'port' => 12345] 237 | ); 238 | $this->expectException(TransportException::class); 239 | if (PHP_VERSION_ID < 80100) { 240 | $this->expectExceptionMessage("getaddrinfo failed"); 241 | } else { 242 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 243 | } 244 | $transport->sendLine('test.sendLine'); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /tests/Transport/TransportTest.php: -------------------------------------------------------------------------------- 1 | '0.0.0.0', 'port' => 9987]); 17 | } 18 | 19 | /** 20 | * @throws TransportException 21 | */ 22 | public function testGetAdapterTypeReturnValue() 23 | { 24 | $mockServerQuery = $this->createMockServerQuery(); 25 | 26 | // The original value should be returned as it is 27 | $this->assertEquals("MockServerQuery", $mockServerQuery->getTransport()->getAdapterType()); 28 | 29 | // The Signal class combines the lowered class name with an additional string for the `emit()` function 30 | $this->assertEquals("mockserverquery", strtolower($mockServerQuery->getTransport()->getAdapterType())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Transport/UDPTest.php: -------------------------------------------------------------------------------- 1 | 'test', 'port' => 12345] 19 | ); 20 | $this->assertInstanceOf(UDP::class, $adapter); 21 | 22 | $this->assertArrayHasKey('host', $adapter->getConfig()); 23 | $this->assertEquals('test', $adapter->getConfig('host')); 24 | 25 | $this->assertArrayHasKey('port', $adapter->getConfig()); 26 | $this->assertEquals(12345, $adapter->getConfig('port')); 27 | 28 | $this->assertArrayHasKey('timeout', $adapter->getConfig()); 29 | $this->assertIsInt($adapter->getConfig('timeout')); 30 | 31 | $this->assertArrayHasKey('blocking', $adapter->getConfig()); 32 | $this->assertIsInt($adapter->getConfig('blocking')); 33 | } 34 | 35 | public function testConstructorExceptionNoHost() 36 | { 37 | $this->expectException(TransportException::class); 38 | $this->expectExceptionMessage("config must have a key for 'host'"); 39 | 40 | new UDP(['port' => 12345]); 41 | } 42 | 43 | public function testConstructorExceptionNoPort() 44 | { 45 | $this->expectException(TransportException::class); 46 | $this->expectExceptionMessage("config must have a key for 'port'"); 47 | 48 | new UDP(['host' => 'test']); 49 | } 50 | 51 | /** 52 | * @throws TransportException 53 | */ 54 | public function testGetConfig() 55 | { 56 | $adapter = new UDP( 57 | ['host' => 'test', 'port' => 12345] 58 | ); 59 | 60 | $this->assertIsArray($adapter->getConfig()); 61 | $this->assertCount(4, $adapter->getConfig()); 62 | $this->assertArrayHasKey('host', $adapter->getConfig()); 63 | $this->assertEquals('test', $adapter->getConfig()['host']); 64 | $this->assertEquals('test', $adapter->getConfig('host')); 65 | } 66 | 67 | /** 68 | * @throws TransportException 69 | */ 70 | public function testSetGetAdapter() 71 | { 72 | $transport = new UDP( 73 | ['host' => 'test', 'port' => 12345] 74 | ); 75 | // Mocking adaptor since `stream_socket_client()` depends on running server 76 | $adaptor = $this->createMock(ServerQuery::class); 77 | $transport->setAdapter($adaptor); 78 | 79 | $this->assertSame($adaptor, $transport->getAdapter()); 80 | } 81 | 82 | /** 83 | * @throws TransportException 84 | */ 85 | public function testGetStream() 86 | { 87 | $transport = new UDP( 88 | ['host' => 'test', 'port' => 12345] 89 | ); 90 | $this->assertNull($transport->getStream()); 91 | } 92 | 93 | /** 94 | * @throws TransportException 95 | */ 96 | public function testConnect() 97 | { 98 | $transport = new UDP( 99 | ['host' => '127.0.0.1', 'port' => 12345] 100 | ); 101 | $transport->connect(); 102 | $this->assertIsResource($transport->getStream()); 103 | } 104 | 105 | /** 106 | * @throws TransportException 107 | */ 108 | public function testConnectBadHost() 109 | { 110 | $host = 'test'; 111 | $transport = new UDP( 112 | ['host' => $host, 'port' => 12345] 113 | ); 114 | $this->expectException(TransportException::class); 115 | if (PHP_VERSION_ID < 80100) { 116 | $this->expectExceptionMessage("getaddrinfo failed"); 117 | } else { 118 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 119 | } 120 | $transport->connect(); 121 | } 122 | 123 | /** 124 | * @throws TransportException 125 | */ 126 | public function testDisconnect() 127 | { 128 | $transport = new UDP( 129 | ['host' => '127.0.0.1', 'port' => 12345] 130 | ); 131 | $transport->connect(); 132 | $this->assertIsResource($transport->getStream()); 133 | $transport->disconnect(); 134 | $this->assertNull($transport->getStream()); 135 | } 136 | 137 | /** 138 | * @throws TransportException 139 | */ 140 | public function testDisconnectNoConnection() 141 | { 142 | $transport = new UDP( 143 | ['host' => 'test', 'port' => 12345] 144 | ); 145 | $this->assertNull($transport->getStream()); 146 | $transport->disconnect(); 147 | } 148 | 149 | /** 150 | * @throws TransportException 151 | */ 152 | public function testReadNoConnection() 153 | { 154 | $host = 'test'; 155 | $transport = new UDP( 156 | ['host' => $host, 'port' => 12345] 157 | ); 158 | $this->expectException(TransportException::class); 159 | if (PHP_VERSION_ID < 80100) { 160 | $this->expectExceptionMessage("getaddrinfo failed"); 161 | } else { 162 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 163 | } 164 | $transport->read(); 165 | } 166 | 167 | /** 168 | * @throws TransportException 169 | */ 170 | public function testSendNoConnection() 171 | { 172 | $host = 'test'; 173 | $transport = new UDP( 174 | ['host' => $host, 'port' => 12345] 175 | ); 176 | $this->expectException(TransportException::class); 177 | if (PHP_VERSION_ID < 80100) { 178 | $this->expectExceptionMessage("getaddrinfo failed"); 179 | } else { 180 | $this->expectExceptionMessage("getaddrinfo for $host failed"); 181 | } 182 | $transport->send('test.send'); 183 | } 184 | } 185 | --------------------------------------------------------------------------------