From e49c2c27ad2aa003971968ef8d15c30ed85c3af3 Mon Sep 17 00:00:00 2001 From: Wruczek Date: Tue, 28 Jun 2016 23:18:59 +0200 Subject: [PATCH] Initial commit --- .gitignore | 66 + .htaccess | 43 + LICENSE.md | 21 + README.md | 1 + api/status.php | 83 + bans.php | 105 + config/config.template.php | 46 + css/navbar.css | 390 +++ css/style.css | 100 + include/adminlist.php | 74 + include/configcheck.php | 53 + include/footer.php | 47 + include/header.php | 130 + include/modulecheck.php | 59 + include/tsutils.php | 32 + js/bans.js | 8 + js/script.js | 10 + js/status.js | 46 + lib/parsedown/parsedown.php | 1538 ++++++++++ lib/phpfastcache/autoload.php | 15 + lib/phpfastcache/phpFastCache/.htaccess | 3 + .../phpFastCache/CacheManager.php | 173 ++ .../phpFastCache/Core/DriverAbstract.php | 846 ++++++ .../phpFastCache/Core/DriverInterface.php | 86 + .../phpFastCache/Core/phpFastCache.php | 354 +++ .../Core/phpFastCacheExtensions.php | 31 + lib/phpfastcache/phpFastCache/Drivers/apc.php | 137 + .../phpFastCache/Drivers/cookie.php | 156 + .../phpFastCache/Drivers/example.php | 128 + .../phpFastCache/Drivers/files.php | 306 ++ .../phpFastCache/Drivers/memcache.php | 200 ++ .../phpFastCache/Drivers/memcached.php | 200 ++ .../phpFastCache/Drivers/mongodb.php | 125 + .../phpFastCache/Drivers/predis.php | 222 ++ .../phpFastCache/Drivers/redis.php | 225 ++ .../phpFastCache/Drivers/sqlite.php | 482 ++++ .../phpFastCache/Drivers/ssdb.php | 191 ++ .../phpFastCache/Drivers/wincache.php | 133 + .../phpFastCache/Drivers/xcache.php | 141 + .../Exceptions/phpFastCacheCoreException.php | 26 + .../phpFastCacheDriverException.php | 26 + .../phpFastCache/Plugins/CronClearFiles.php | 33 + .../phpFastCache/Util/Languages.php | 47 + lib/phpfastcache/phpFastCache/Util/Legacy.php | 43 + .../phpFastCache/Util/OpenBaseDir.php | 33 + lib/phpfastcache/phpFastCache/index.html | 1 + .../phpFastCache/phpFastCache.php | 55 + lib/ts3phpframework/LICENSE | 674 +++++ lib/ts3phpframework/images/flags/ad.png | Bin 0 -> 643 bytes lib/ts3phpframework/images/flags/ae.png | Bin 0 -> 408 bytes lib/ts3phpframework/images/flags/af.png | Bin 0 -> 604 bytes lib/ts3phpframework/images/flags/ag.png | Bin 0 -> 591 bytes lib/ts3phpframework/images/flags/ai.png | Bin 0 -> 643 bytes lib/ts3phpframework/images/flags/al.png | Bin 0 -> 600 bytes lib/ts3phpframework/images/flags/am.png | Bin 0 -> 497 bytes lib/ts3phpframework/images/flags/an.png | Bin 0 -> 488 bytes lib/ts3phpframework/images/flags/ao.png | Bin 0 -> 428 bytes lib/ts3phpframework/images/flags/ar.png | Bin 0 -> 506 bytes lib/ts3phpframework/images/flags/as.png | Bin 0 -> 647 bytes lib/ts3phpframework/images/flags/at.png | Bin 0 -> 403 bytes lib/ts3phpframework/images/flags/au.png | Bin 0 -> 673 bytes lib/ts3phpframework/images/flags/aw.png | Bin 0 -> 524 bytes lib/ts3phpframework/images/flags/ax.png | Bin 0 -> 663 bytes lib/ts3phpframework/images/flags/az.png | Bin 0 -> 589 bytes lib/ts3phpframework/images/flags/ba.png | Bin 0 -> 593 bytes lib/ts3phpframework/images/flags/bb.png | Bin 0 -> 585 bytes lib/ts3phpframework/images/flags/bd.png | Bin 0 -> 504 bytes lib/ts3phpframework/images/flags/be.png | Bin 0 -> 449 bytes lib/ts3phpframework/images/flags/bf.png | Bin 0 -> 497 bytes lib/ts3phpframework/images/flags/bg.png | Bin 0 -> 462 bytes lib/ts3phpframework/images/flags/bh.png | Bin 0 -> 457 bytes lib/ts3phpframework/images/flags/bi.png | Bin 0 -> 675 bytes lib/ts3phpframework/images/flags/bj.png | Bin 0 -> 486 bytes lib/ts3phpframework/images/flags/bl.png | Bin 0 -> 545 bytes lib/ts3phpframework/images/flags/bm.png | Bin 0 -> 611 bytes lib/ts3phpframework/images/flags/bn.png | Bin 0 -> 639 bytes lib/ts3phpframework/images/flags/bo.png | Bin 0 -> 500 bytes lib/ts3phpframework/images/flags/br.png | Bin 0 -> 593 bytes lib/ts3phpframework/images/flags/bs.png | Bin 0 -> 526 bytes lib/ts3phpframework/images/flags/bt.png | Bin 0 -> 631 bytes lib/ts3phpframework/images/flags/bv.png | Bin 0 -> 512 bytes lib/ts3phpframework/images/flags/bw.png | Bin 0 -> 443 bytes lib/ts3phpframework/images/flags/by.png | Bin 0 -> 514 bytes lib/ts3phpframework/images/flags/bz.png | Bin 0 -> 600 bytes lib/ts3phpframework/images/flags/ca.png | Bin 0 -> 628 bytes lib/ts3phpframework/images/flags/cc.png | Bin 0 -> 625 bytes lib/ts3phpframework/images/flags/cd.png | Bin 0 -> 528 bytes lib/ts3phpframework/images/flags/cf.png | Bin 0 -> 614 bytes lib/ts3phpframework/images/flags/cg.png | Bin 0 -> 521 bytes lib/ts3phpframework/images/flags/ch.png | Bin 0 -> 367 bytes lib/ts3phpframework/images/flags/ci.png | Bin 0 -> 453 bytes lib/ts3phpframework/images/flags/ck.png | Bin 0 -> 586 bytes lib/ts3phpframework/images/flags/cl.png | Bin 0 -> 450 bytes lib/ts3phpframework/images/flags/cm.png | Bin 0 -> 525 bytes lib/ts3phpframework/images/flags/cn.png | Bin 0 -> 472 bytes lib/ts3phpframework/images/flags/co.png | Bin 0 -> 483 bytes lib/ts3phpframework/images/flags/cr.png | Bin 0 -> 477 bytes lib/ts3phpframework/images/flags/cs.png | Bin 0 -> 439 bytes lib/ts3phpframework/images/flags/cu.png | Bin 0 -> 563 bytes lib/ts3phpframework/images/flags/cv.png | Bin 0 -> 529 bytes lib/ts3phpframework/images/flags/cw.png | Bin 0 -> 205 bytes lib/ts3phpframework/images/flags/cx.png | Bin 0 -> 608 bytes lib/ts3phpframework/images/flags/cy.png | Bin 0 -> 428 bytes lib/ts3phpframework/images/flags/cz.png | Bin 0 -> 476 bytes lib/ts3phpframework/images/flags/de.png | Bin 0 -> 545 bytes lib/ts3phpframework/images/flags/dj.png | Bin 0 -> 572 bytes lib/ts3phpframework/images/flags/dk.png | Bin 0 -> 495 bytes lib/ts3phpframework/images/flags/dm.png | Bin 0 -> 620 bytes lib/ts3phpframework/images/flags/do.png | Bin 0 -> 508 bytes lib/ts3phpframework/images/flags/dz.png | Bin 0 -> 582 bytes lib/ts3phpframework/images/flags/ec.png | Bin 0 -> 500 bytes lib/ts3phpframework/images/flags/ee.png | Bin 0 -> 429 bytes lib/ts3phpframework/images/flags/eg.png | Bin 0 -> 465 bytes lib/ts3phpframework/images/flags/eh.png | Bin 0 -> 508 bytes lib/ts3phpframework/images/flags/er.png | Bin 0 -> 653 bytes lib/ts3phpframework/images/flags/es.png | Bin 0 -> 469 bytes lib/ts3phpframework/images/flags/et.png | Bin 0 -> 592 bytes lib/ts3phpframework/images/flags/fi.png | Bin 0 -> 489 bytes lib/ts3phpframework/images/flags/fj.png | Bin 0 -> 610 bytes lib/ts3phpframework/images/flags/fk.png | Bin 0 -> 648 bytes lib/ts3phpframework/images/flags/fm.png | Bin 0 -> 552 bytes lib/ts3phpframework/images/flags/fo.png | Bin 0 -> 474 bytes lib/ts3phpframework/images/flags/fr.png | Bin 0 -> 545 bytes lib/ts3phpframework/images/flags/ga.png | Bin 0 -> 489 bytes lib/ts3phpframework/images/flags/gb.png | Bin 0 -> 599 bytes lib/ts3phpframework/images/flags/gd.png | Bin 0 -> 637 bytes lib/ts3phpframework/images/flags/ge.png | Bin 0 -> 594 bytes lib/ts3phpframework/images/flags/gf.png | Bin 0 -> 545 bytes lib/ts3phpframework/images/flags/gg.png | Bin 0 -> 362 bytes lib/ts3phpframework/images/flags/gh.png | Bin 0 -> 490 bytes lib/ts3phpframework/images/flags/gi.png | Bin 0 -> 463 bytes lib/ts3phpframework/images/flags/gl.png | Bin 0 -> 470 bytes lib/ts3phpframework/images/flags/gm.png | Bin 0 -> 493 bytes lib/ts3phpframework/images/flags/gn.png | Bin 0 -> 480 bytes lib/ts3phpframework/images/flags/gp.png | Bin 0 -> 488 bytes lib/ts3phpframework/images/flags/gq.png | Bin 0 -> 537 bytes lib/ts3phpframework/images/flags/gr.png | Bin 0 -> 487 bytes lib/ts3phpframework/images/flags/gs.png | Bin 0 -> 630 bytes lib/ts3phpframework/images/flags/gt.png | Bin 0 -> 493 bytes lib/ts3phpframework/images/flags/gu.png | Bin 0 -> 509 bytes lib/ts3phpframework/images/flags/gw.png | Bin 0 -> 516 bytes lib/ts3phpframework/images/flags/gy.png | Bin 0 -> 645 bytes lib/ts3phpframework/images/flags/hk.png | Bin 0 -> 527 bytes lib/ts3phpframework/images/flags/hm.png | Bin 0 -> 673 bytes lib/ts3phpframework/images/flags/hn.png | Bin 0 -> 537 bytes lib/ts3phpframework/images/flags/hr.png | Bin 0 -> 524 bytes lib/ts3phpframework/images/flags/ht.png | Bin 0 -> 487 bytes lib/ts3phpframework/images/flags/hu.png | Bin 0 -> 432 bytes lib/ts3phpframework/images/flags/id.png | Bin 0 -> 430 bytes lib/ts3phpframework/images/flags/ie.png | Bin 0 -> 481 bytes lib/ts3phpframework/images/flags/il.png | Bin 0 -> 431 bytes lib/ts3phpframework/images/flags/im.png | Bin 0 -> 372 bytes lib/ts3phpframework/images/flags/in.png | Bin 0 -> 503 bytes lib/ts3phpframework/images/flags/io.png | Bin 0 -> 658 bytes lib/ts3phpframework/images/flags/iq.png | Bin 0 -> 515 bytes lib/ts3phpframework/images/flags/ir.png | Bin 0 -> 512 bytes lib/ts3phpframework/images/flags/is.png | Bin 0 -> 532 bytes lib/ts3phpframework/images/flags/it.png | Bin 0 -> 420 bytes lib/ts3phpframework/images/flags/je.png | Bin 0 -> 471 bytes lib/ts3phpframework/images/flags/jm.png | Bin 0 -> 637 bytes lib/ts3phpframework/images/flags/jo.png | Bin 0 -> 473 bytes lib/ts3phpframework/images/flags/jp.png | Bin 0 -> 420 bytes lib/ts3phpframework/images/flags/ke.png | Bin 0 -> 569 bytes lib/ts3phpframework/images/flags/kg.png | Bin 0 -> 510 bytes lib/ts3phpframework/images/flags/kh.png | Bin 0 -> 549 bytes lib/ts3phpframework/images/flags/ki.png | Bin 0 -> 656 bytes lib/ts3phpframework/images/flags/km.png | Bin 0 -> 577 bytes lib/ts3phpframework/images/flags/kn.png | Bin 0 -> 604 bytes lib/ts3phpframework/images/flags/kp.png | Bin 0 -> 561 bytes lib/ts3phpframework/images/flags/kr.png | Bin 0 -> 592 bytes lib/ts3phpframework/images/flags/kw.png | Bin 0 -> 486 bytes lib/ts3phpframework/images/flags/ky.png | Bin 0 -> 643 bytes lib/ts3phpframework/images/flags/kz.png | Bin 0 -> 616 bytes lib/ts3phpframework/images/flags/la.png | Bin 0 -> 563 bytes lib/ts3phpframework/images/flags/lb.png | Bin 0 -> 517 bytes lib/ts3phpframework/images/flags/lc.png | Bin 0 -> 520 bytes lib/ts3phpframework/images/flags/li.png | Bin 0 -> 537 bytes lib/ts3phpframework/images/flags/lk.png | Bin 0 -> 627 bytes lib/ts3phpframework/images/flags/lr.png | Bin 0 -> 466 bytes lib/ts3phpframework/images/flags/ls.png | Bin 0 -> 628 bytes lib/ts3phpframework/images/flags/lt.png | Bin 0 -> 508 bytes lib/ts3phpframework/images/flags/lu.png | Bin 0 -> 481 bytes lib/ts3phpframework/images/flags/lv.png | Bin 0 -> 465 bytes lib/ts3phpframework/images/flags/ly.png | Bin 0 -> 258 bytes lib/ts3phpframework/images/flags/ma.png | Bin 0 -> 432 bytes lib/ts3phpframework/images/flags/mc.png | Bin 0 -> 380 bytes lib/ts3phpframework/images/flags/md.png | Bin 0 -> 566 bytes lib/ts3phpframework/images/flags/me.png | Bin 0 -> 448 bytes lib/ts3phpframework/images/flags/mg.png | Bin 0 -> 453 bytes lib/ts3phpframework/images/flags/mh.png | Bin 0 -> 628 bytes lib/ts3phpframework/images/flags/mk.png | Bin 0 -> 664 bytes lib/ts3phpframework/images/flags/ml.png | Bin 0 -> 474 bytes lib/ts3phpframework/images/flags/mm.png | Bin 0 -> 483 bytes lib/ts3phpframework/images/flags/mn.png | Bin 0 -> 492 bytes lib/ts3phpframework/images/flags/mo.png | Bin 0 -> 588 bytes lib/ts3phpframework/images/flags/mp.png | Bin 0 -> 597 bytes lib/ts3phpframework/images/flags/mq.png | Bin 0 -> 655 bytes lib/ts3phpframework/images/flags/mr.png | Bin 0 -> 569 bytes lib/ts3phpframework/images/flags/ms.png | Bin 0 -> 614 bytes lib/ts3phpframework/images/flags/mt.png | Bin 0 -> 420 bytes lib/ts3phpframework/images/flags/mu.png | Bin 0 -> 496 bytes lib/ts3phpframework/images/flags/mv.png | Bin 0 -> 542 bytes lib/ts3phpframework/images/flags/mw.png | Bin 0 -> 529 bytes lib/ts3phpframework/images/flags/mx.png | Bin 0 -> 574 bytes lib/ts3phpframework/images/flags/my.png | Bin 0 -> 571 bytes lib/ts3phpframework/images/flags/mz.png | Bin 0 -> 584 bytes lib/ts3phpframework/images/flags/na.png | Bin 0 -> 647 bytes lib/ts3phpframework/images/flags/nc.png | Bin 0 -> 591 bytes lib/ts3phpframework/images/flags/ne.png | Bin 0 -> 537 bytes lib/ts3phpframework/images/flags/nf.png | Bin 0 -> 602 bytes lib/ts3phpframework/images/flags/ng.png | Bin 0 -> 482 bytes lib/ts3phpframework/images/flags/ni.png | Bin 0 -> 508 bytes lib/ts3phpframework/images/flags/nl.png | Bin 0 -> 453 bytes lib/ts3phpframework/images/flags/no.png | Bin 0 -> 512 bytes lib/ts3phpframework/images/flags/np.png | Bin 0 -> 443 bytes lib/ts3phpframework/images/flags/nr.png | Bin 0 -> 527 bytes lib/ts3phpframework/images/flags/nu.png | Bin 0 -> 572 bytes lib/ts3phpframework/images/flags/nz.png | Bin 0 -> 639 bytes lib/ts3phpframework/images/flags/om.png | Bin 0 -> 478 bytes lib/ts3phpframework/images/flags/pa.png | Bin 0 -> 519 bytes lib/ts3phpframework/images/flags/pe.png | Bin 0 -> 397 bytes lib/ts3phpframework/images/flags/pf.png | Bin 0 -> 498 bytes lib/ts3phpframework/images/flags/pg.png | Bin 0 -> 593 bytes lib/ts3phpframework/images/flags/ph.png | Bin 0 -> 538 bytes lib/ts3phpframework/images/flags/pk.png | Bin 0 -> 569 bytes lib/ts3phpframework/images/flags/pl.png | Bin 0 -> 374 bytes lib/ts3phpframework/images/flags/pm.png | Bin 0 -> 689 bytes lib/ts3phpframework/images/flags/pn.png | Bin 0 -> 657 bytes lib/ts3phpframework/images/flags/pr.png | Bin 0 -> 556 bytes lib/ts3phpframework/images/flags/ps.png | Bin 0 -> 472 bytes lib/ts3phpframework/images/flags/pt.png | Bin 0 -> 554 bytes lib/ts3phpframework/images/flags/pw.png | Bin 0 -> 550 bytes lib/ts3phpframework/images/flags/py.png | Bin 0 -> 473 bytes lib/ts3phpframework/images/flags/qa.png | Bin 0 -> 450 bytes lib/ts3phpframework/images/flags/re.png | Bin 0 -> 545 bytes lib/ts3phpframework/images/flags/ro.png | Bin 0 -> 495 bytes lib/ts3phpframework/images/flags/rs.png | Bin 0 -> 423 bytes lib/ts3phpframework/images/flags/ru.png | Bin 0 -> 420 bytes lib/ts3phpframework/images/flags/rw.png | Bin 0 -> 533 bytes lib/ts3phpframework/images/flags/sa.png | Bin 0 -> 551 bytes lib/ts3phpframework/images/flags/sb.png | Bin 0 -> 624 bytes lib/ts3phpframework/images/flags/sc.png | Bin 0 -> 608 bytes lib/ts3phpframework/images/flags/sd.png | Bin 0 -> 492 bytes lib/ts3phpframework/images/flags/se.png | Bin 0 -> 542 bytes lib/ts3phpframework/images/flags/sg.png | Bin 0 -> 468 bytes lib/ts3phpframework/images/flags/sh.png | Bin 0 -> 645 bytes lib/ts3phpframework/images/flags/si.png | Bin 0 -> 510 bytes lib/ts3phpframework/images/flags/sj.png | Bin 0 -> 512 bytes lib/ts3phpframework/images/flags/sk.png | Bin 0 -> 562 bytes lib/ts3phpframework/images/flags/sl.png | Bin 0 -> 436 bytes lib/ts3phpframework/images/flags/sm.png | Bin 0 -> 502 bytes lib/ts3phpframework/images/flags/sn.png | Bin 0 -> 532 bytes lib/ts3phpframework/images/flags/so.png | Bin 0 -> 527 bytes lib/ts3phpframework/images/flags/sr.png | Bin 0 -> 513 bytes lib/ts3phpframework/images/flags/st.png | Bin 0 -> 584 bytes lib/ts3phpframework/images/flags/sv.png | Bin 0 -> 501 bytes lib/ts3phpframework/images/flags/sy.png | Bin 0 -> 422 bytes lib/ts3phpframework/images/flags/sz.png | Bin 0 -> 643 bytes lib/ts3phpframework/images/flags/tc.png | Bin 0 -> 624 bytes lib/ts3phpframework/images/flags/td.png | Bin 0 -> 570 bytes lib/ts3phpframework/images/flags/tf.png | Bin 0 -> 527 bytes lib/ts3phpframework/images/flags/tg.png | Bin 0 -> 562 bytes lib/ts3phpframework/images/flags/th.png | Bin 0 -> 452 bytes lib/ts3phpframework/images/flags/tj.png | Bin 0 -> 496 bytes lib/ts3phpframework/images/flags/tk.png | Bin 0 -> 638 bytes lib/ts3phpframework/images/flags/tl.png | Bin 0 -> 514 bytes lib/ts3phpframework/images/flags/tm.png | Bin 0 -> 593 bytes lib/ts3phpframework/images/flags/tn.png | Bin 0 -> 495 bytes lib/ts3phpframework/images/flags/to.png | Bin 0 -> 426 bytes lib/ts3phpframework/images/flags/tr.png | Bin 0 -> 492 bytes lib/ts3phpframework/images/flags/tt.png | Bin 0 -> 617 bytes lib/ts3phpframework/images/flags/tv.png | Bin 0 -> 536 bytes lib/ts3phpframework/images/flags/tw.png | Bin 0 -> 465 bytes lib/ts3phpframework/images/flags/tz.png | Bin 0 -> 642 bytes lib/ts3phpframework/images/flags/ua.png | Bin 0 -> 446 bytes lib/ts3phpframework/images/flags/ug.png | Bin 0 -> 531 bytes lib/ts3phpframework/images/flags/uk.png | Bin 0 -> 599 bytes lib/ts3phpframework/images/flags/um.png | Bin 0 -> 571 bytes lib/ts3phpframework/images/flags/us.png | Bin 0 -> 609 bytes lib/ts3phpframework/images/flags/uy.png | Bin 0 -> 532 bytes lib/ts3phpframework/images/flags/uz.png | Bin 0 -> 515 bytes lib/ts3phpframework/images/flags/va.png | Bin 0 -> 553 bytes lib/ts3phpframework/images/flags/vc.png | Bin 0 -> 577 bytes lib/ts3phpframework/images/flags/ve.png | Bin 0 -> 528 bytes lib/ts3phpframework/images/flags/vg.png | Bin 0 -> 630 bytes lib/ts3phpframework/images/flags/vi.png | Bin 0 -> 616 bytes lib/ts3phpframework/images/flags/vn.png | Bin 0 -> 474 bytes lib/ts3phpframework/images/flags/vu.png | Bin 0 -> 604 bytes lib/ts3phpframework/images/flags/wf.png | Bin 0 -> 554 bytes lib/ts3phpframework/images/flags/ws.png | Bin 0 -> 476 bytes lib/ts3phpframework/images/flags/ye.png | Bin 0 -> 413 bytes lib/ts3phpframework/images/flags/yt.png | Bin 0 -> 593 bytes lib/ts3phpframework/images/flags/za.png | Bin 0 -> 642 bytes lib/ts3phpframework/images/flags/zm.png | Bin 0 -> 500 bytes lib/ts3phpframework/images/flags/zw.png | Bin 0 -> 574 bytes .../images/icons/ts3client.ico | Bin 0 -> 70333 bytes .../images/icons/ts3server.ico | Bin 0 -> 69372 bytes .../images/viewer/channel_flag_default.png | Bin 0 -> 696 bytes .../images/viewer/channel_flag_moderated.png | Bin 0 -> 945 bytes .../images/viewer/channel_flag_music.png | Bin 0 -> 716 bytes .../images/viewer/channel_flag_password.png | Bin 0 -> 589 bytes .../images/viewer/channel_full.png | Bin 0 -> 822 bytes .../images/viewer/channel_open.png | Bin 0 -> 847 bytes .../images/viewer/channel_pass.png | Bin 0 -> 827 bytes .../images/viewer/client_away.png | Bin 0 -> 821 bytes .../images/viewer/client_cc.png | Bin 0 -> 3558 bytes .../images/viewer/client_cc_idle.png | Bin 0 -> 867 bytes .../images/viewer/client_cc_talk.png | Bin 0 -> 868 bytes .../images/viewer/client_idle.png | Bin 0 -> 829 bytes .../images/viewer/client_mic_disabled.png | Bin 0 -> 977 bytes .../images/viewer/client_mic_muted.png | Bin 0 -> 906 bytes .../images/viewer/client_priority.png | Bin 0 -> 1215 bytes .../images/viewer/client_query.png | Bin 0 -> 901 bytes .../images/viewer/client_snd_disabled.png | Bin 0 -> 929 bytes .../images/viewer/client_snd_muted.png | Bin 0 -> 908 bytes .../images/viewer/client_talk.png | Bin 0 -> 860 bytes .../images/viewer/client_talker.png | Bin 0 -> 978 bytes .../images/viewer/client_talker_request.png | Bin 0 -> 1065 bytes .../images/viewer/group_channel.png | Bin 0 -> 1309 bytes .../images/viewer/group_icon_0.png | Bin 0 -> 354 bytes .../images/viewer/group_icon_100.png | Bin 0 -> 809 bytes .../images/viewer/group_icon_200.png | Bin 0 -> 781 bytes .../images/viewer/group_icon_300.png | Bin 0 -> 820 bytes .../images/viewer/group_icon_400.png | Bin 0 -> 1160 bytes .../images/viewer/group_icon_500.png | Bin 0 -> 803 bytes .../images/viewer/group_icon_600.png | Bin 0 -> 757 bytes .../images/viewer/group_server.png | Bin 0 -> 1301 bytes lib/ts3phpframework/images/viewer/host.png | Bin 0 -> 1067 bytes .../images/viewer/server_full.png | Bin 0 -> 774 bytes .../images/viewer/server_open.png | Bin 0 -> 827 bytes .../images/viewer/server_pass.png | Bin 0 -> 773 bytes .../images/viewer/spacer_dashdotdotline.gif | Bin 0 -> 58 bytes .../images/viewer/spacer_dashdotline.gif | Bin 0 -> 54 bytes .../images/viewer/spacer_dashline.gif | Bin 0 -> 51 bytes .../images/viewer/spacer_dotline.gif | Bin 0 -> 46 bytes .../images/viewer/spacer_solidline.gif | Bin 0 -> 45 bytes lib/ts3phpframework/images/viewer/tree.png | Bin 0 -> 548 bytes .../images/viewer/tree_blank.png | Bin 0 -> 125 bytes .../images/viewer/tree_end.gif | Bin 0 -> 61 bytes .../images/viewer/tree_line.gif | Bin 0 -> 63 bytes .../images/viewer/tree_mid.gif | Bin 0 -> 64 bytes .../libraries/TeamSpeak3/Adapter/Abstract.php | 160 ++ .../TeamSpeak3/Adapter/Blacklist.php | 119 + .../Adapter/Blacklist/Exception.php | 32 + .../TeamSpeak3/Adapter/Exception.php | 32 + .../TeamSpeak3/Adapter/FileTransfer.php | 190 ++ .../Adapter/FileTransfer/Exception.php | 32 + .../TeamSpeak3/Adapter/ServerQuery.php | 261 ++ .../TeamSpeak3/Adapter/ServerQuery/Event.php | 170 ++ .../Adapter/ServerQuery/Exception.php | 32 + .../TeamSpeak3/Adapter/ServerQuery/Reply.php | 346 +++ .../libraries/TeamSpeak3/Adapter/TSDNS.php | 95 + .../TeamSpeak3/Adapter/TSDNS/Exception.php | 32 + .../libraries/TeamSpeak3/Adapter/Update.php | 217 ++ .../TeamSpeak3/Adapter/Update/Exception.php | 32 + .../libraries/TeamSpeak3/Exception.php | 129 + .../libraries/TeamSpeak3/Helper/Char.php | 269 ++ .../libraries/TeamSpeak3/Helper/Convert.php | 349 +++ .../libraries/TeamSpeak3/Helper/Crypt.php | 482 ++++ .../libraries/TeamSpeak3/Helper/Exception.php | 32 + .../libraries/TeamSpeak3/Helper/Profiler.php | 101 + .../TeamSpeak3/Helper/Profiler/Exception.php | 32 + .../TeamSpeak3/Helper/Profiler/Timer.php | 154 + .../libraries/TeamSpeak3/Helper/Signal.php | 213 ++ .../TeamSpeak3/Helper/Signal/Exception.php | 32 + .../TeamSpeak3/Helper/Signal/Handler.php | 78 + .../TeamSpeak3/Helper/Signal/Interface.php | 353 +++ .../libraries/TeamSpeak3/Helper/String.php | 939 ++++++ .../libraries/TeamSpeak3/Helper/Uri.php | 717 +++++ .../libraries/TeamSpeak3/Node/Abstract.php | 624 ++++ .../libraries/TeamSpeak3/Node/Channel.php | 588 ++++ .../TeamSpeak3/Node/Channelgroup.php | 276 ++ .../libraries/TeamSpeak3/Node/Client.php | 441 +++ .../libraries/TeamSpeak3/Node/Exception.php | 32 + .../libraries/TeamSpeak3/Node/Host.php | 1202 ++++++++ .../libraries/TeamSpeak3/Node/Server.php | 2536 +++++++++++++++++ .../libraries/TeamSpeak3/Node/Servergroup.php | 300 ++ .../libraries/TeamSpeak3/TeamSpeak3.php | 974 +++++++ .../TeamSpeak3/Transport/Abstract.php | 270 ++ .../TeamSpeak3/Transport/Exception.php | 32 + .../libraries/TeamSpeak3/Transport/TCP.php | 179 ++ .../libraries/TeamSpeak3/Transport/UDP.php | 113 + .../libraries/TeamSpeak3/Viewer/Html.php | 670 +++++ .../libraries/TeamSpeak3/Viewer/Interface.php | 42 + .../libraries/TeamSpeak3/Viewer/Text.php | 107 + news/README.txt | 15 + news/news1.md | 14 + news/news2.md | 378 +++ news/news3.md | 145 + viewer.php | 49 + 390 files changed, 22561 insertions(+) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 api/status.php create mode 100644 bans.php create mode 100644 config/config.template.php create mode 100644 css/navbar.css create mode 100644 css/style.css create mode 100644 include/adminlist.php create mode 100644 include/configcheck.php create mode 100644 include/footer.php create mode 100644 include/header.php create mode 100644 include/modulecheck.php create mode 100644 include/tsutils.php create mode 100644 js/bans.js create mode 100644 js/script.js create mode 100644 js/status.js create mode 100644 lib/parsedown/parsedown.php create mode 100644 lib/phpfastcache/autoload.php create mode 100644 lib/phpfastcache/phpFastCache/.htaccess create mode 100644 lib/phpfastcache/phpFastCache/CacheManager.php create mode 100644 lib/phpfastcache/phpFastCache/Core/DriverAbstract.php create mode 100644 lib/phpfastcache/phpFastCache/Core/DriverInterface.php create mode 100644 lib/phpfastcache/phpFastCache/Core/phpFastCache.php create mode 100644 lib/phpfastcache/phpFastCache/Core/phpFastCacheExtensions.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/apc.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/cookie.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/example.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/files.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/memcache.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/memcached.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/mongodb.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/predis.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/redis.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/sqlite.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/ssdb.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/wincache.php create mode 100644 lib/phpfastcache/phpFastCache/Drivers/xcache.php create mode 100644 lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheCoreException.php create mode 100644 lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheDriverException.php create mode 100644 lib/phpfastcache/phpFastCache/Plugins/CronClearFiles.php create mode 100644 lib/phpfastcache/phpFastCache/Util/Languages.php create mode 100644 lib/phpfastcache/phpFastCache/Util/Legacy.php create mode 100644 lib/phpfastcache/phpFastCache/Util/OpenBaseDir.php create mode 100644 lib/phpfastcache/phpFastCache/index.html create mode 100644 lib/phpfastcache/phpFastCache/phpFastCache.php create mode 100644 lib/ts3phpframework/LICENSE create mode 100644 lib/ts3phpframework/images/flags/ad.png create mode 100644 lib/ts3phpframework/images/flags/ae.png create mode 100644 lib/ts3phpframework/images/flags/af.png create mode 100644 lib/ts3phpframework/images/flags/ag.png create mode 100644 lib/ts3phpframework/images/flags/ai.png create mode 100644 lib/ts3phpframework/images/flags/al.png create mode 100644 lib/ts3phpframework/images/flags/am.png create mode 100644 lib/ts3phpframework/images/flags/an.png create mode 100644 lib/ts3phpframework/images/flags/ao.png create mode 100644 lib/ts3phpframework/images/flags/ar.png create mode 100644 lib/ts3phpframework/images/flags/as.png create mode 100644 lib/ts3phpframework/images/flags/at.png create mode 100644 lib/ts3phpframework/images/flags/au.png create mode 100644 lib/ts3phpframework/images/flags/aw.png create mode 100644 lib/ts3phpframework/images/flags/ax.png create mode 100644 lib/ts3phpframework/images/flags/az.png create mode 100644 lib/ts3phpframework/images/flags/ba.png create mode 100644 lib/ts3phpframework/images/flags/bb.png create mode 100644 lib/ts3phpframework/images/flags/bd.png create mode 100644 lib/ts3phpframework/images/flags/be.png create mode 100644 lib/ts3phpframework/images/flags/bf.png create mode 100644 lib/ts3phpframework/images/flags/bg.png create mode 100644 lib/ts3phpframework/images/flags/bh.png create mode 100644 lib/ts3phpframework/images/flags/bi.png create mode 100644 lib/ts3phpframework/images/flags/bj.png create mode 100644 lib/ts3phpframework/images/flags/bl.png create mode 100644 lib/ts3phpframework/images/flags/bm.png create mode 100644 lib/ts3phpframework/images/flags/bn.png create mode 100644 lib/ts3phpframework/images/flags/bo.png create mode 100644 lib/ts3phpframework/images/flags/br.png create mode 100644 lib/ts3phpframework/images/flags/bs.png create mode 100644 lib/ts3phpframework/images/flags/bt.png create mode 100644 lib/ts3phpframework/images/flags/bv.png create mode 100644 lib/ts3phpframework/images/flags/bw.png create mode 100644 lib/ts3phpframework/images/flags/by.png create mode 100644 lib/ts3phpframework/images/flags/bz.png create mode 100644 lib/ts3phpframework/images/flags/ca.png create mode 100644 lib/ts3phpframework/images/flags/cc.png create mode 100644 lib/ts3phpframework/images/flags/cd.png create mode 100644 lib/ts3phpframework/images/flags/cf.png create mode 100644 lib/ts3phpframework/images/flags/cg.png create mode 100644 lib/ts3phpframework/images/flags/ch.png create mode 100644 lib/ts3phpframework/images/flags/ci.png create mode 100644 lib/ts3phpframework/images/flags/ck.png create mode 100644 lib/ts3phpframework/images/flags/cl.png create mode 100644 lib/ts3phpframework/images/flags/cm.png create mode 100644 lib/ts3phpframework/images/flags/cn.png create mode 100644 lib/ts3phpframework/images/flags/co.png create mode 100644 lib/ts3phpframework/images/flags/cr.png create mode 100644 lib/ts3phpframework/images/flags/cs.png create mode 100644 lib/ts3phpframework/images/flags/cu.png create mode 100644 lib/ts3phpframework/images/flags/cv.png create mode 100644 lib/ts3phpframework/images/flags/cw.png create mode 100644 lib/ts3phpframework/images/flags/cx.png create mode 100644 lib/ts3phpframework/images/flags/cy.png create mode 100644 lib/ts3phpframework/images/flags/cz.png create mode 100644 lib/ts3phpframework/images/flags/de.png create mode 100644 lib/ts3phpframework/images/flags/dj.png create mode 100644 lib/ts3phpframework/images/flags/dk.png create mode 100644 lib/ts3phpframework/images/flags/dm.png create mode 100644 lib/ts3phpframework/images/flags/do.png create mode 100644 lib/ts3phpframework/images/flags/dz.png create mode 100644 lib/ts3phpframework/images/flags/ec.png create mode 100644 lib/ts3phpframework/images/flags/ee.png create mode 100644 lib/ts3phpframework/images/flags/eg.png create mode 100644 lib/ts3phpframework/images/flags/eh.png create mode 100644 lib/ts3phpframework/images/flags/er.png create mode 100644 lib/ts3phpframework/images/flags/es.png create mode 100644 lib/ts3phpframework/images/flags/et.png create mode 100644 lib/ts3phpframework/images/flags/fi.png create mode 100644 lib/ts3phpframework/images/flags/fj.png create mode 100644 lib/ts3phpframework/images/flags/fk.png create mode 100644 lib/ts3phpframework/images/flags/fm.png create mode 100644 lib/ts3phpframework/images/flags/fo.png create mode 100644 lib/ts3phpframework/images/flags/fr.png create mode 100644 lib/ts3phpframework/images/flags/ga.png create mode 100644 lib/ts3phpframework/images/flags/gb.png create mode 100644 lib/ts3phpframework/images/flags/gd.png create mode 100644 lib/ts3phpframework/images/flags/ge.png create mode 100644 lib/ts3phpframework/images/flags/gf.png create mode 100644 lib/ts3phpframework/images/flags/gg.png create mode 100644 lib/ts3phpframework/images/flags/gh.png create mode 100644 lib/ts3phpframework/images/flags/gi.png create mode 100644 lib/ts3phpframework/images/flags/gl.png create mode 100644 lib/ts3phpframework/images/flags/gm.png create mode 100644 lib/ts3phpframework/images/flags/gn.png create mode 100644 lib/ts3phpframework/images/flags/gp.png create mode 100644 lib/ts3phpframework/images/flags/gq.png create mode 100644 lib/ts3phpframework/images/flags/gr.png create mode 100644 lib/ts3phpframework/images/flags/gs.png create mode 100644 lib/ts3phpframework/images/flags/gt.png create mode 100644 lib/ts3phpframework/images/flags/gu.png create mode 100644 lib/ts3phpframework/images/flags/gw.png create mode 100644 lib/ts3phpframework/images/flags/gy.png create mode 100644 lib/ts3phpframework/images/flags/hk.png create mode 100644 lib/ts3phpframework/images/flags/hm.png create mode 100644 lib/ts3phpframework/images/flags/hn.png create mode 100644 lib/ts3phpframework/images/flags/hr.png create mode 100644 lib/ts3phpframework/images/flags/ht.png create mode 100644 lib/ts3phpframework/images/flags/hu.png create mode 100644 lib/ts3phpframework/images/flags/id.png create mode 100644 lib/ts3phpframework/images/flags/ie.png create mode 100644 lib/ts3phpframework/images/flags/il.png create mode 100644 lib/ts3phpframework/images/flags/im.png create mode 100644 lib/ts3phpframework/images/flags/in.png create mode 100644 lib/ts3phpframework/images/flags/io.png create mode 100644 lib/ts3phpframework/images/flags/iq.png create mode 100644 lib/ts3phpframework/images/flags/ir.png create mode 100644 lib/ts3phpframework/images/flags/is.png create mode 100644 lib/ts3phpframework/images/flags/it.png create mode 100644 lib/ts3phpframework/images/flags/je.png create mode 100644 lib/ts3phpframework/images/flags/jm.png create mode 100644 lib/ts3phpframework/images/flags/jo.png create mode 100644 lib/ts3phpframework/images/flags/jp.png create mode 100644 lib/ts3phpframework/images/flags/ke.png create mode 100644 lib/ts3phpframework/images/flags/kg.png create mode 100644 lib/ts3phpframework/images/flags/kh.png create mode 100644 lib/ts3phpframework/images/flags/ki.png create mode 100644 lib/ts3phpframework/images/flags/km.png create mode 100644 lib/ts3phpframework/images/flags/kn.png create mode 100644 lib/ts3phpframework/images/flags/kp.png create mode 100644 lib/ts3phpframework/images/flags/kr.png create mode 100644 lib/ts3phpframework/images/flags/kw.png create mode 100644 lib/ts3phpframework/images/flags/ky.png create mode 100644 lib/ts3phpframework/images/flags/kz.png create mode 100644 lib/ts3phpframework/images/flags/la.png create mode 100644 lib/ts3phpframework/images/flags/lb.png create mode 100644 lib/ts3phpframework/images/flags/lc.png create mode 100644 lib/ts3phpframework/images/flags/li.png create mode 100644 lib/ts3phpframework/images/flags/lk.png create mode 100644 lib/ts3phpframework/images/flags/lr.png create mode 100644 lib/ts3phpframework/images/flags/ls.png create mode 100644 lib/ts3phpframework/images/flags/lt.png create mode 100644 lib/ts3phpframework/images/flags/lu.png create mode 100644 lib/ts3phpframework/images/flags/lv.png create mode 100644 lib/ts3phpframework/images/flags/ly.png create mode 100644 lib/ts3phpframework/images/flags/ma.png create mode 100644 lib/ts3phpframework/images/flags/mc.png create mode 100644 lib/ts3phpframework/images/flags/md.png create mode 100644 lib/ts3phpframework/images/flags/me.png create mode 100644 lib/ts3phpframework/images/flags/mg.png create mode 100644 lib/ts3phpframework/images/flags/mh.png create mode 100644 lib/ts3phpframework/images/flags/mk.png create mode 100644 lib/ts3phpframework/images/flags/ml.png create mode 100644 lib/ts3phpframework/images/flags/mm.png create mode 100644 lib/ts3phpframework/images/flags/mn.png create mode 100644 lib/ts3phpframework/images/flags/mo.png create mode 100644 lib/ts3phpframework/images/flags/mp.png create mode 100644 lib/ts3phpframework/images/flags/mq.png create mode 100644 lib/ts3phpframework/images/flags/mr.png create mode 100644 lib/ts3phpframework/images/flags/ms.png create mode 100644 lib/ts3phpframework/images/flags/mt.png create mode 100644 lib/ts3phpframework/images/flags/mu.png create mode 100644 lib/ts3phpframework/images/flags/mv.png create mode 100644 lib/ts3phpframework/images/flags/mw.png create mode 100644 lib/ts3phpframework/images/flags/mx.png create mode 100644 lib/ts3phpframework/images/flags/my.png create mode 100644 lib/ts3phpframework/images/flags/mz.png create mode 100644 lib/ts3phpframework/images/flags/na.png create mode 100644 lib/ts3phpframework/images/flags/nc.png create mode 100644 lib/ts3phpframework/images/flags/ne.png create mode 100644 lib/ts3phpframework/images/flags/nf.png create mode 100644 lib/ts3phpframework/images/flags/ng.png create mode 100644 lib/ts3phpframework/images/flags/ni.png create mode 100644 lib/ts3phpframework/images/flags/nl.png create mode 100644 lib/ts3phpframework/images/flags/no.png create mode 100644 lib/ts3phpframework/images/flags/np.png create mode 100644 lib/ts3phpframework/images/flags/nr.png create mode 100644 lib/ts3phpframework/images/flags/nu.png create mode 100644 lib/ts3phpframework/images/flags/nz.png create mode 100644 lib/ts3phpframework/images/flags/om.png create mode 100644 lib/ts3phpframework/images/flags/pa.png create mode 100644 lib/ts3phpframework/images/flags/pe.png create mode 100644 lib/ts3phpframework/images/flags/pf.png create mode 100644 lib/ts3phpframework/images/flags/pg.png create mode 100644 lib/ts3phpframework/images/flags/ph.png create mode 100644 lib/ts3phpframework/images/flags/pk.png create mode 100644 lib/ts3phpframework/images/flags/pl.png create mode 100644 lib/ts3phpframework/images/flags/pm.png create mode 100644 lib/ts3phpframework/images/flags/pn.png create mode 100644 lib/ts3phpframework/images/flags/pr.png create mode 100644 lib/ts3phpframework/images/flags/ps.png create mode 100644 lib/ts3phpframework/images/flags/pt.png create mode 100644 lib/ts3phpframework/images/flags/pw.png create mode 100644 lib/ts3phpframework/images/flags/py.png create mode 100644 lib/ts3phpframework/images/flags/qa.png create mode 100644 lib/ts3phpframework/images/flags/re.png create mode 100644 lib/ts3phpframework/images/flags/ro.png create mode 100644 lib/ts3phpframework/images/flags/rs.png create mode 100644 lib/ts3phpframework/images/flags/ru.png create mode 100644 lib/ts3phpframework/images/flags/rw.png create mode 100644 lib/ts3phpframework/images/flags/sa.png create mode 100644 lib/ts3phpframework/images/flags/sb.png create mode 100644 lib/ts3phpframework/images/flags/sc.png create mode 100644 lib/ts3phpframework/images/flags/sd.png create mode 100644 lib/ts3phpframework/images/flags/se.png create mode 100644 lib/ts3phpframework/images/flags/sg.png create mode 100644 lib/ts3phpframework/images/flags/sh.png create mode 100644 lib/ts3phpframework/images/flags/si.png create mode 100644 lib/ts3phpframework/images/flags/sj.png create mode 100644 lib/ts3phpframework/images/flags/sk.png create mode 100644 lib/ts3phpframework/images/flags/sl.png create mode 100644 lib/ts3phpframework/images/flags/sm.png create mode 100644 lib/ts3phpframework/images/flags/sn.png create mode 100644 lib/ts3phpframework/images/flags/so.png create mode 100644 lib/ts3phpframework/images/flags/sr.png create mode 100644 lib/ts3phpframework/images/flags/st.png create mode 100644 lib/ts3phpframework/images/flags/sv.png create mode 100644 lib/ts3phpframework/images/flags/sy.png create mode 100644 lib/ts3phpframework/images/flags/sz.png create mode 100644 lib/ts3phpframework/images/flags/tc.png create mode 100644 lib/ts3phpframework/images/flags/td.png create mode 100644 lib/ts3phpframework/images/flags/tf.png create mode 100644 lib/ts3phpframework/images/flags/tg.png create mode 100644 lib/ts3phpframework/images/flags/th.png create mode 100644 lib/ts3phpframework/images/flags/tj.png create mode 100644 lib/ts3phpframework/images/flags/tk.png create mode 100644 lib/ts3phpframework/images/flags/tl.png create mode 100644 lib/ts3phpframework/images/flags/tm.png create mode 100644 lib/ts3phpframework/images/flags/tn.png create mode 100644 lib/ts3phpframework/images/flags/to.png create mode 100644 lib/ts3phpframework/images/flags/tr.png create mode 100644 lib/ts3phpframework/images/flags/tt.png create mode 100644 lib/ts3phpframework/images/flags/tv.png create mode 100644 lib/ts3phpframework/images/flags/tw.png create mode 100644 lib/ts3phpframework/images/flags/tz.png create mode 100644 lib/ts3phpframework/images/flags/ua.png create mode 100644 lib/ts3phpframework/images/flags/ug.png create mode 100644 lib/ts3phpframework/images/flags/uk.png create mode 100644 lib/ts3phpframework/images/flags/um.png create mode 100644 lib/ts3phpframework/images/flags/us.png create mode 100644 lib/ts3phpframework/images/flags/uy.png create mode 100644 lib/ts3phpframework/images/flags/uz.png create mode 100644 lib/ts3phpframework/images/flags/va.png create mode 100644 lib/ts3phpframework/images/flags/vc.png create mode 100644 lib/ts3phpframework/images/flags/ve.png create mode 100644 lib/ts3phpframework/images/flags/vg.png create mode 100644 lib/ts3phpframework/images/flags/vi.png create mode 100644 lib/ts3phpframework/images/flags/vn.png create mode 100644 lib/ts3phpframework/images/flags/vu.png create mode 100644 lib/ts3phpframework/images/flags/wf.png create mode 100644 lib/ts3phpframework/images/flags/ws.png create mode 100644 lib/ts3phpframework/images/flags/ye.png create mode 100644 lib/ts3phpframework/images/flags/yt.png create mode 100644 lib/ts3phpframework/images/flags/za.png create mode 100644 lib/ts3phpframework/images/flags/zm.png create mode 100644 lib/ts3phpframework/images/flags/zw.png create mode 100644 lib/ts3phpframework/images/icons/ts3client.ico create mode 100644 lib/ts3phpframework/images/icons/ts3server.ico create mode 100644 lib/ts3phpframework/images/viewer/channel_flag_default.png create mode 100644 lib/ts3phpframework/images/viewer/channel_flag_moderated.png create mode 100644 lib/ts3phpframework/images/viewer/channel_flag_music.png create mode 100644 lib/ts3phpframework/images/viewer/channel_flag_password.png create mode 100644 lib/ts3phpframework/images/viewer/channel_full.png create mode 100644 lib/ts3phpframework/images/viewer/channel_open.png create mode 100644 lib/ts3phpframework/images/viewer/channel_pass.png create mode 100644 lib/ts3phpframework/images/viewer/client_away.png create mode 100644 lib/ts3phpframework/images/viewer/client_cc.png create mode 100644 lib/ts3phpframework/images/viewer/client_cc_idle.png create mode 100644 lib/ts3phpframework/images/viewer/client_cc_talk.png create mode 100644 lib/ts3phpframework/images/viewer/client_idle.png create mode 100644 lib/ts3phpframework/images/viewer/client_mic_disabled.png create mode 100644 lib/ts3phpframework/images/viewer/client_mic_muted.png create mode 100644 lib/ts3phpframework/images/viewer/client_priority.png create mode 100644 lib/ts3phpframework/images/viewer/client_query.png create mode 100644 lib/ts3phpframework/images/viewer/client_snd_disabled.png create mode 100644 lib/ts3phpframework/images/viewer/client_snd_muted.png create mode 100644 lib/ts3phpframework/images/viewer/client_talk.png create mode 100644 lib/ts3phpframework/images/viewer/client_talker.png create mode 100644 lib/ts3phpframework/images/viewer/client_talker_request.png create mode 100644 lib/ts3phpframework/images/viewer/group_channel.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_0.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_100.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_200.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_300.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_400.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_500.png create mode 100644 lib/ts3phpframework/images/viewer/group_icon_600.png create mode 100644 lib/ts3phpframework/images/viewer/group_server.png create mode 100644 lib/ts3phpframework/images/viewer/host.png create mode 100644 lib/ts3phpframework/images/viewer/server_full.png create mode 100644 lib/ts3phpframework/images/viewer/server_open.png create mode 100644 lib/ts3phpframework/images/viewer/server_pass.png create mode 100644 lib/ts3phpframework/images/viewer/spacer_dashdotdotline.gif create mode 100644 lib/ts3phpframework/images/viewer/spacer_dashdotline.gif create mode 100644 lib/ts3phpframework/images/viewer/spacer_dashline.gif create mode 100644 lib/ts3phpframework/images/viewer/spacer_dotline.gif create mode 100644 lib/ts3phpframework/images/viewer/spacer_solidline.gif create mode 100644 lib/ts3phpframework/images/viewer/tree.png create mode 100644 lib/ts3phpframework/images/viewer/tree_blank.png create mode 100644 lib/ts3phpframework/images/viewer/tree_end.gif create mode 100644 lib/ts3phpframework/images/viewer/tree_line.gif create mode 100644 lib/ts3phpframework/images/viewer/tree_mid.gif create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Abstract.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Event.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Reply.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Char.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Convert.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Crypt.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Timer.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Handler.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Interface.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/String.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Helper/Uri.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Abstract.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Channel.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Channelgroup.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Client.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Host.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Server.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Node/Servergroup.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/TeamSpeak3.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Transport/Abstract.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Transport/Exception.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Transport/TCP.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Transport/UDP.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Html.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Interface.php create mode 100644 lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Text.php create mode 100644 news/README.txt create mode 100644 news/news1.md create mode 100644 news/news2.md create mode 100644 news/news3.md create mode 100644 viewer.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caba276 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +config/config.php +prototypes/ + +# Created by https://www.gitignore.io/api/windows,osx,linux + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..62a4124 --- /dev/null +++ b/.htaccess @@ -0,0 +1,43 @@ +# (c) 2015-2016 Wruczek + +AddDefaultCharset UTF-8 + +# DISABLE DIRECTORY LISTING +Options -Indexes + +ErrorDocument 403 " 403 - brak dostępu

403 brak dostępu

Nie posiadasz uprawnień do przeglądania tej strony.


←   Powrót
" + +ErrorDocument 404 " 404 - nie znaleziono

404 strony nie znaleziono

Strona której szukasz nie została odnaleziona.


←   Powrót
" + +ErrorDocument 500 " 500 - błąd serwera

500 błąd serwera

Wystąpił błąd serwera. Spróbuj ponownie później.


←   Powrót
" + + + + +RewriteEngine On + +# REDIRECTING FROM lol.com/page.php or lol.com/page.html to lol.com/page +RewriteCond %{THE_REQUEST} \ /(.+)\.php +RewriteRule ^ /%1 [L,R=301] +RewriteCond %{THE_REQUEST} \ /(.+)\.html +RewriteRule ^ /%1 [L,R=301] + +# PAGES WITHOUT .PHP AT THE END +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME}\.php -f +RewriteRule ^(.*)$ $1.php + +# PAGES WITHOUT .HTML AT THE END +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME}\.html -f +RewriteRule ^(.*)$ $1.html + + + +# Enable GZIP + +AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript +BrowserMatch ^Mozilla/4 gzip-only-text/html +BrowserMatch ^Mozilla/4\.0[678] no-gzip +BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b227051 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Wruczek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e37bf0 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ts-website \ No newline at end of file diff --git a/api/status.php b/api/status.php new file mode 100644 index 0000000..2d4ffb6 --- /dev/null +++ b/api/status.php @@ -0,0 +1,83 @@ +get('serverstatus'); + +// $cache->clean(); + +if (is_null($serverstatus)) { + $serverstatus = getResult(); + $cache->set('serverstatus', $serverstatus, 10); +} + +die ($serverstatus); + +// ********* +// METHODS +// ********* + +function getResult() { + try { + $start = microtime(true); + + $tsstatus = getTeamspeakServerStatus(); + + $stop = microtime(true); + + return json_encode(array( + "tsstatus" => $tsstatus, + "generated" => date('d-m-Y H:i:s') + )); + } catch (Exception $e) { + scriptFail($e); + } +} + +function scriptFail($error) { + die(json_encode(array( + "success" => false, + "id" => "script_error", + "message" => "There has been an error while retrieving the server status" + ,"error" => $error + ))); +} + +function exception_error_handler($errno, $errstr, $errfile, $errline ) { + scriptFail("[$errfile @ $errline] " . $errstr); +} + +function getTeamspeakServerStatus() { + + $response = pingTeamspeakServerFromConfig(); + + if ($response) { + return array( + "success" => $response["virtualserver_status"]->toString() == "online", + "name" => $response["virtualserver_name"]->toString(), + "clientsonline" => $response["virtualserver_clientsonline"] - $response["virtualserver_queryclientsonline"], + "maxclients" => $response["virtualserver_maxclients"], + "version" => TeamSpeak3_Helper_Convert::versionShort($response["virtualserver_version"]->toString())->toString(), + "platform" => $response["virtualserver_platform"]->toString(), + "uptime" => TeamSpeak3_Helper_Convert::seconds($response["virtualserver_uptime"], false, "%dd %02dh %02dm"), + "averagePacketloss" => $response["virtualserver_total_packetloss_total"]->toString(), + "averagePing" => $response["virtualserver_total_ping"]->toString() + ); + } else { + return array( + "success" => false, + "id" => "not_responding", + "message" => "Server is not responding" + ); + } +} diff --git a/bans.php b/bans.php new file mode 100644 index 0000000..8be13be --- /dev/null +++ b/bans.php @@ -0,0 +1,105 @@ +get('banlist'); + +// $cache->clean(); + +if (is_null($banlist)) { + $banlist = array(getBanlist(), date('d-m-Y H:i:s')); + $cache->set('banlist', $banlist, 60); +} + + +?> + +
+
+

Lista banów

+
+
+ + +
+

BRAK ZBANOWANYCH UŻYTKOWNIKÓW

+
+ +
+ + + + + + + + + + + + + +
NickPowódZbanowany przezData zbanowaniaWygasa
+
+ + +
+ +
+ +banList(); + + $output = ""; + + foreach ($bany as $ban) { + + if(!isset($ban['lastnickname'])) + continue; + + $lastnickname = $ban['lastnickname']->toString(); + $reason = $ban['reason']->toString(); + $invokername = $ban['invokername']->toString(); + $created = date('d-m-Y H:i:s', $ban['created']); + $duration = $ban['duration']; + + if($duration == 0) + $expires = "Ban permanentny"; + else + $expires = date('d-m-Y H:i:s', $ban['created'] + $duration); + + $output .= "$lastnickname$reason$invokername$created$expires"; + } + + return $output; + } catch(TeamSpeak3_Exception $e) { + if($e->getCode() == 1281) { + return false; + } else { + return '

Wystąpił błąd ' . $e->getCode() . ': ' . $e->getMessage() . '

'; + } + } + +} + + +require_once __DIR__ . "/include/footer.php"; +?> diff --git a/config/config.template.php b/config/config.template.php new file mode 100644 index 0000000..93e86bd --- /dev/null +++ b/config/config.template.php @@ -0,0 +1,46 @@ + .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} + +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} + +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} + +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} + +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} + +.navbar-brand { + float: left; + padding: 19.5px 15px; + font-size: 19px; + line-height: 21px; + height: 60px; +} + +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} + +.navbar-brand > img { + display: block; +} + +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} + +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 13px; + margin-bottom: 0px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.navbar-toggle:focus { + outline: 0; +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} + +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} + +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} + +.navbar-nav { + margin: 9.75px -15px; +} + +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 21px; +} + +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + + .navbar-nav .open .dropdown-menu > li > a { + line-height: 21px; + } + + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} + +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + + .navbar-nav > li { + float: left; + } + + .navbar-nav > li > a { + padding-top: 19.5px; + padding-bottom: 19.5px; + } +} + +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 7.5px; + margin-bottom: 7.5px; +} + +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + + .navbar-form .form-control-static { + display: inline-block; + } + + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + + .navbar-form .input-group > .form-control { + width: 100%; + } + + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} + +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} + +@media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} + +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.navbar-btn { + margin-top: 7.5px; + margin-bottom: 7.5px; +} + +.navbar-btn.btn-sm { + margin-top: 12.5px; + margin-bottom: 12.5px; +} + +.navbar-btn.btn-xs { + margin-top: 19px; + margin-bottom: 19px; +} + +.navbar-text { + margin-top: 19.5px; + margin-bottom: 19.5px; +} + +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-left: 15px; + margin-right: 15px; + } +} + +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + + .navbar-right { + float: right !important; + margin-right: -15px; + } + + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..ad31e97 --- /dev/null +++ b/css/style.css @@ -0,0 +1,100 @@ +.fa { + margin-right: 5px; +} + +.icon-smaller { + font-size: 0.7em; +} + +a { + -webkit-transition: color 500ms ease-out; + -moz-transition: color 500ms ease-out; + -o-transition: color 500ms ease-out; + transition: color 500ms ease-out; +} + +.news-header { + font-size: 1.4em; + text-align: center; +} + +.news-author { + font-size: 0.7em; + text-align: right; +} + +.pullright { + float: right +} + +/* ****** */ +/* STATUS */ +/* ****** */ + +.serverstatus p { + margin: 0px +} + +.serverstatus a { + float: right +} + +.serverstatus span { + float: right +} + +/* ********* */ +/* ADMINLIST */ +/* ********* */ + +.adminlist > .groupname ~ .groupname { + margin-top: 15px; +} + +.adminlist .groupname { + text-align: center; + font-size: 1.2em; + border-bottom: 1px solid #677481; + margin-bottom: 5px; +} + +.adminlist .label-primary { + background-color: #375a7f; +} + +.adminlist p { + margin: 0px +} + +.adminlist img { + margin-right: 3px +} + +.adminlist .iconspacer { + margin-left: 19px +} + + +/* ****** */ +/* NAVBAR */ +/* ****** */ + +body { + padding-top: 80px; +} + +.navbar { + font-size: initial; +} + +.navbar-brand { + padding: 0px 10px 0px 0px; + display: flex; + align-items: center; +} + +.navbar-brand > img { + height: 100%; + padding: 10px 5px 10px 15px; + width: auto; +} diff --git a/include/adminlist.php b/include/adminlist.php new file mode 100644 index 0000000..8ee636d --- /dev/null +++ b/include/adminlist.php @@ -0,0 +1,74 @@ +get('adminlist'); + +// $cache->clean(); + +if (is_null($adminlist)) { + $adminlist = array(getAdminList(), date('d-m-Y H:i:s')); + $cache->set('adminlist', $adminlist, 30); +} + +// FUNCTIONS + +function getAdminList() { + + try { + $tsAdmin = TeamSpeak3::factory(getTeamspeakURI(). "#no_query_clients"); + $serverGroupList = $tsAdmin->serverGroupList(); + + $output = ""; + + foreach ($serverGroupList as $group) { + + if(!isAdminGroup($group->getId())) + continue; + + $output .= "

$group

"; + + foreach ($group->clientList() as $userInfo) { + $user = getClientByDbid($tsAdmin, $userInfo['cldbid']); + + if(!$user) { + $output .= '

' . $userInfo['client_nickname'] . 'Offline

'; + continue; + } + + $output .= '

' . '' . '' . $user . '' . ($user['client_away'] ? 'Away' : 'Online') . '

'; + } + } + + return $output; + } catch(TeamSpeak3_Exception $e) { + return '

Wystąpił błąd ' . $e->getCode() . ': ' . $e->getMessage() . '

'; + } + +} + +function isAdminGroup($groupid) { + global $config; + $admingroups = $config["adminlist"]; + + return in_array($groupid, $admingroups); +} + +function getClientByDbid($tsAdmin, $cldbid) { + try { + return $tsAdmin->clientGetByDbid($cldbid); + } catch(TeamSpeak3_Exception $e) { + return false; + } +} + +// echo getAdminList(); diff --git a/include/configcheck.php b/include/configcheck.php new file mode 100644 index 0000000..9621253 --- /dev/null +++ b/include/configcheck.php @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + BŁĄD: Brak pliku config.php + + + + + + + + + + + + + +
+ +
+
+

Brak pliku config.php

+
+
+

Przejdź do folderu config i zmień nazwę pliku z config.template.php na config.php.

+

Skonfiguruj stronę według własnych potrzeb.

+
+
+ +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +", $creationtime); +?> diff --git a/include/header.php b/include/header.php new file mode 100644 index 0000000..194c08b --- /dev/null +++ b/include/header.php @@ -0,0 +1,130 @@ + + + + + + + + + + "> + + + <?php echo $config["general"]["title"] . $config["general"]["subtitle"]; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Status serwera
+
+
+

Adres:

+
+
+ + Ładowanie... +
+
+
+
+
+ + +
+
Status administracji
+
+ +
+
+ +
+
Podgląd serwera
+ +
+
+ +
\ No newline at end of file diff --git a/include/modulecheck.php b/include/modulecheck.php new file mode 100644 index 0000000..23e6b6e --- /dev/null +++ b/include/modulecheck.php @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + BŁĄD: Brak wymaganych rozszerzeń + + + + + + + + + + + + + +
+ +
+
+

Brak wymaganych rozszerzeń

+
+
+

Na swoim serwerze nie posiadasz modułu rewrite wymaganego do poprawnego działania tej strony.

+

Posiadasz system Ubuntu? Świetnie! Uruchom poniższe komendy, by włączyć wymagany moduł:

+
sudo a2enmod rewrite
+sudo service apache2 reload
+

Używasz system Debian? Uruchom owe komendy pomijając przedrostek sudo:

+
a2enmod rewrite
+service apache2 reload
+

Jeśli używasz hostingu i nie masz dostępu do konsoli, skontaktuj się z administratorem lub pomocą techniczną Twojego hostingu.

+
+
+ +
+ + + + + +getProperty("virtualserver_status")) + throw new Exception("Server is offline"); + + return $tsAdmin->getInfo(); + } catch(TeamSpeak3_Exception $e) { + return false; + } + +} + +function getTeamspeakURI() { + global $config; + $host = $config['teamspeak']['host']; + $login = $config['teamspeak']['login']; + $passwd = $config['teamspeak']['password']; + $sport = $config['teamspeak']['server_port']; + $qport = $config['teamspeak']['query_port']; + return "serverquery://$login:$passwd@$host:$qport/?server_port=$sport"; +} diff --git a/js/bans.js b/js/bans.js new file mode 100644 index 0000000..f771279 --- /dev/null +++ b/js/bans.js @@ -0,0 +1,8 @@ +$(document).ready(function () { + $('#banlist').dataTable({ + "order": [], + "language": { + "url": "//cdn.datatables.net/plug-ins/1.10.12/i18n/Polish.json" + } + }); +}); diff --git a/js/script.js b/js/script.js new file mode 100644 index 0000000..2231d46 --- /dev/null +++ b/js/script.js @@ -0,0 +1,10 @@ +$(function () { + $('[data-toggle="tooltip"]').tooltip({"html": true}) +}) + +$('.news-body').readmore({ + speed: 500, + collapsedHeight: 300, + moreLink: '', + lessLink: '' +}); diff --git a/js/status.js b/js/status.js new file mode 100644 index 0000000..1e58759 --- /dev/null +++ b/js/status.js @@ -0,0 +1,46 @@ +$(document).ready(function () { + + checkStatus(); + + var intervalid = setInterval(function () { + checkStatus(); + }, 10 * 1000); +}) + +function checkStatus() { + + $.ajax({ + url: "api/status", + success: function (json) { + + json = json.tsstatus; + + var result = ""; + + if (json.success) { + var clientsonline = json.clientsonline; + var maxclients = json.maxclients; + var clientsprecent = Math.round(json.clientsonline * 100 / json.maxclients); + var version = json.version; + var platform = json.platform; + var uptime = json.uptime; + var averagePacketloss = Math.round(json.averagePacketloss * 100) / 100; + var averagePing = Math.round(json.averagePing * 100) / 100; + + result = + '

Online: ' + clientsonline + ' / ' + maxclients + ' (' + clientsprecent + '%)

' + + '

Uptime: ' + uptime + '

' + + '

Wersja: ' + version + ' @ ' + platform + '

' + + '

Średni ping: ' + averagePing + ' ms

' + + '

Średni packet loss: ' + averagePacketloss + '%

'; + } else { + result = '

Online: Offline

'; + } + + $("#serverstatus").html(result); + }, + error: function (result) { + $("#serverstatus").html('

Online: ERROR

'); + } + }) +} diff --git a/lib/parsedown/parsedown.php b/lib/parsedown/parsedown.php new file mode 100644 index 0000000..646af86 --- /dev/null +++ b/lib/parsedown/parsedown.php @@ -0,0 +1,1538 @@ +DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + if (strpos($line, "\t") !== false) + { + $parts = explode("\t", $line); + + $line = $parts[0]; + + unset($parts[0]); + + foreach ($parts as $part) + { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + + $indent = 0; + + while (isset($line[$indent]) and $line[$indent] === ' ') + { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + $Blocks []= $CurrentBlock; + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) + { + $CurrentBlock['element']['text'] .= "\n".$text; + } + else + { + $Blocks []= $CurrentBlock; + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + + # ~ + + $Blocks []= $CurrentBlock; + + unset($Blocks[0]); + + # ~ + + $markup = ''; + + foreach ($Blocks as $Block) + { + if (isset($Block['hidden'])) + { + continue; + } + + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } + + $markup .= "\n"; + + # ~ + + return $markup; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block'.$Type.'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block'.$Type.'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped) + { + return; + } + + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') + { + $Block = array( + 'markup' => $Line['body'], + ); + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['markup'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) + { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[1])) + { + $class = 'language-'.$matches[1]; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) + { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body'];; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + # + # List + + protected function blockList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $text, + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + return $Block; + } + + if ($Line['indent'] > 0) + { + $Block['li']['text'] []= ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text'] []= ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['text'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) + { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped) + { + return; + } + + if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], + ); + + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + $Block['closed'] = true; + + $Block['void'] = true; + } + } + else + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + return; + } + + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) + { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open + { + $Block['depth'] ++; + } + + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) + { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n".$Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => null, + ); + + if (isset($matches[3])) + { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') + { + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => 'text-align: '.$alignment.';', + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; + } + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + + foreach ($matches[0] as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: '.$Block['alignments'][$index].';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + $Block = array( + 'element' => array( + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ), + ); + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '"' => array('SpecialCharacter'), + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), + '>' => array('SpecialCharacter'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!"*_&[:<>`~\\'; + + # + # ~ + # + + public function line($text) + { + $markup = ''; + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strpos($text, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + $Inline = $this->{'inline'.$inlineType}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); + + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); + } + + $markup .= $this->unmarkedText($text); + + return $markup; + } + + # + # ~ + # + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = 'mailto:' . $url; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ), + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'text' => null, + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) + { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['text']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']); + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + + $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); + + if (isset($SpecialCharacter[$Excerpt['text'][0]])) + { + return array( + 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', + 'extent' => 1, + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + { + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $matches[0][0], + 'attributes' => array( + 'href' => $matches[0][0], + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) + { + $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) + { + $text = preg_replace('/[ ]*\n/', "
\n", $text); + } + else + { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); + $text = str_replace(" \n", "\n", $text); + } + + return $text; + } + + # + # Handlers + # + + protected function element(array $Element) + { + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= ' '.$name.'="'.$value.'"'; + } + } + + if (isset($Element['text'])) + { + $markup .= '>'; + + if (isset($Element['handler'])) + { + $markup .= $this->{$Element['handler']}($Element['text']); + } + else + { + $markup .= $Element['text']; + } + + $markup .= ''; + } + else + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) + { + $markup .= "\n" . $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

') + { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

"); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + # + # Static Methods + # + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'sub', 'mark', + 'u', 'xm', 'sup', 'nobr', + 'var', 'ruby', + 'wbr', 'span', + 'time', + ); +} diff --git a/lib/phpfastcache/autoload.php b/lib/phpfastcache/autoload.php new file mode 100644 index 0000000..b21baf8 --- /dev/null +++ b/lib/phpfastcache/autoload.php @@ -0,0 +1,15 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +require_once __DIR__."/phpFastCache/phpFastCache.php"; \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/.htaccess b/lib/phpfastcache/phpFastCache/.htaccess new file mode 100644 index 0000000..608da1a --- /dev/null +++ b/lib/phpfastcache/phpFastCache/.htaccess @@ -0,0 +1,3 @@ +order deny,allow +deny from all +allow from 127.0.0.1 \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/CacheManager.php b/lib/phpfastcache/phpFastCache/CacheManager.php new file mode 100644 index 0000000..e690d48 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/CacheManager.php @@ -0,0 +1,173 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache; + +use phpFastCache\Core\phpFastCache; +use phpFastCache\Core\DriverAbstract; + +/** + * Class CacheManager + * @package phpFastCache + * + * @method static DriverAbstract Apc() Apc($config = array()) Return a driver "apc" instance + * @method static DriverAbstract Cookie() Cookie($config = array()) Return a driver "cookie" instance + * @method static DriverAbstract Files() Files($config = array()) Return a driver "files" instance + * @method static DriverAbstract Memcache() Memcache($config = array()) Return a driver "memcache" instance + * @method static DriverAbstract Memcached() Memcached($config = array()) Return a driver "memcached" instance + * @method static DriverAbstract Predis() Predis($config = array()) Return a driver "predis" instance + * @method static DriverAbstract Redis() Redis($config = array()) Return a driver "redis" instance + * @method static DriverAbstract Sqlite() Sqlite($config = array()) Return a driver "sqlite" instance + * @method static DriverAbstract Ssdb() Ssdb($config = array()) Return a driver "ssdb" instance + * @method static DriverAbstract Wincache() Wincache($config = array()) Return a driver "wincache" instance + * @method static DriverAbstract Xcache() Xcache($config = array()) Return a driver "xcache" instance + * + */ +class CacheManager +{ + public static $instances = array(); + public static $memory = array(); + public static $hit = array(); + + /** + * @param string $storage + * @param array $config + * @return DriverAbstract + */ + public static function getInstance($storage = 'auto', $config = array()) + { + $storage = strtolower($storage); + if (empty($config)) { + $config = phpFastCache::$config; + } + if (!isset($config[ 'cache_method' ])) { + $config[ 'cache_method' ] = phpFastCache::$config[ 'cache_method' ]; + } + if (!isset($config[ 'limited_memory_each_object' ])) { + $config[ 'limited_memory_each_object' ] = phpFastCache::$config[ 'limited_memory_each_object' ]; + } + if (isset(phpFastCache::$config[ 'overwrite' ]) && !in_array(phpFastCache::$config[ 'overwrite' ], array('auto', ''), true)) { + phpFastCache::$config[ 'storage' ] = phpFastCache::$config[ 'overwrite' ]; + $storage = phpFastCache::$config[ 'overwrite' ]; + } else if (isset(phpFastCache::$config[ 'storage' ]) && !in_array(phpFastCache::$config[ 'storage' ], array('auto', ''), true)) { + $storage = phpFastCache::$config[ 'storage' ]; + } else if (in_array($storage, array('auto', ''), true)) { + $storage = phpFastCache::getAutoClass($config); + } + + // echo $storage."
"; + $instance = md5(serialize($config) . $storage); + if (!isset(self::$instances[ $instance ]) || is_null(self::$instances[ $instance ])) { + $class = '\phpFastCache\Drivers\\' . $storage; + $config[ 'storage' ] = $storage; + $config[ 'instance' ] = $instance; + $config[ 'class' ] = $class; + if (!isset(self::$memory[ $instance ])) { + self::$memory[ $instance ] = array(); + } + + if (!isset(self::$hit[ $instance ])) { + self::$hit[ $instance ] = array( + "class" => $class, + "storage" => $storage, + "data" => array(), + ); + if ($config[ 'cache_method' ] == 4) { + register_shutdown_function('phpFastCache\CacheManager::cleanCachingMethod', null); + } + } + + self::$instances[ $instance ] = new $class($config); + } + + return self::$instances[ $instance ]; + } + + /** + * Setup Method + * @param string $string | traditional(normal), memory (fast), phpfastcache (fastest) + */ + public static function CachingMethod($string = "phpFastCache") + { + $string = strtolower($string); + if (in_array($string, array("normal", "traditional"))) { + phpFastCache::$config[ 'cache_method' ] = 1; + } else if (in_array($string, array("fast", "memory"))) { + phpFastCache::$config[ 'cache_method' ] = 2; + } else if (in_array($string, array("fastest", "phpfastcache"))) { + phpFastCache::$config[ 'cache_method' ] = 3; + } else if (in_array($string, array("superfast", "phpfastcachex"))) { + phpFastCache::$config[ 'cache_method' ] = 4; + } + } + + /** + * CacheManager::Files(); + * CacheManager::Memcached(); + * CacheManager::get($keyword); + * CacheManager::set(), touch, other @method supported + */ + public static function __callStatic($name, $arguments) + { + $driver = strtolower($name); + if (!isset(self::$instances[ 'loaded' ][ $driver ]) && class_exists("\\phpFastCache\\Drivers\\{$driver}")) { + self::$instances[ 'loaded' ][ $driver ] = true; + } + if (isset(self::$instances[ 'loaded' ][ $driver ])) { + return self::getInstance($name, (isset($arguments[ 0 ]) ? $arguments[ 0 ] : array())); + } else { + return call_user_func_array(array(self::getInstance(), $name), $arguments); + } + + } + + /** + * Shortcut to phpFastCache::setup() + */ + public static function setup($name, $value = '') + { + phpFastCache::setup($name, $value); + } + + /** + * @param string $instance + */ + public static function cleanCachingMethod($instance = null) + { + if (is_null($instance)) { + foreach (self::$instances as $instance => $data) { + self::__CleanCachingMethod($instance); + unset($data); + } + } else { + self::__CleanCachingMethod($instance); + } + } + + /** + * @param string $instance + */ + protected static function __CleanCachingMethod($instance) + { + if(is_array(self::$memory[ $instance ]) && !empty(self::$memory[ $instance ])) { + $old = self::$instances[$instance]->config['cache_method']; + self::$instances[$instance]->config['cache_method'] = 1; + foreach (self::$memory[$instance] as $keyword => $object) { + self::$instances[$instance]->set($keyword, $object['value'], $object['expired_in']); + } + self::$instances[$instance]->config['cache_method'] = $old; + self::$memory[$instance] = array(); + } + } +} diff --git a/lib/phpfastcache/phpFastCache/Core/DriverAbstract.php b/lib/phpfastcache/phpFastCache/Core/DriverAbstract.php new file mode 100644 index 0000000..b880401 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Core/DriverAbstract.php @@ -0,0 +1,846 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Core; + +use phpFastCache\Exceptions\phpFastCacheDriverException; +use phpFastCache\CacheManager; + +/** + * Class DriverAbstract + * @package phpFastCache\Core + */ +abstract class DriverAbstract implements DriverInterface +{ + + /** + * @var array + */ + public $extension_dir = '_extensions'; + + /** + * @var array + */ + public $tmp = array(); + + /** + * @var array default options, this will be merge to Driver's Options + */ + public $config = array(); + + /** + * @var bool + */ + public $fallback = false; + + /** + * @var + */ + public $instant; + + + public function __destruct() + { + // clean up the memory and don't want for PHP clean for caching method "phpfastcache" + if (isset($this->config[ 'instance' ]) && (int)$this->config[ 'cache_method' ] === 3) { + CacheManager::cleanCachingMethod($this->config[ 'instance' ]); + } + } + + /** + * @param $keyword + * @return string + */ + protected function encodeFilename($keyword) + { + // return trim(trim(preg_replace('/[^a-zA-Z0-9]+/', '_', $keyword), '_')); + // return rtrim(base64_encode($keyword), '='); + return md5($keyword); + } + + /** + * Basic Functions + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool|null + */ + public function set($keyword, $value = '', $time = 0, $option = array()) + { + /** + * Infinity Time + * Khoa. B + */ + if ((int)$time <= 0) { + /** + * 5 years, however memcached or memory cached will gone when u restart it + * just recommended for sqlite. files + */ + $time = 3600 * 24 * 365 * 5; + } + + /** + * Temporary disabled phpFastCache::$disabled = true + * Khoa. B + */ + if (phpFastCache::$disabled === true) { + return false; + } + $object = array( + "value" => $value, + "write_time" => time(), + "expired_in" => $time, + "expired_time" => time() + (int)$time, + "size" => (is_array($value) || is_object($value)) ? strlen(serialize($value)) : strlen((String)$value), + ); + + // handle search + if (isset($this->config[ 'allow_search' ]) && $this->config[ 'allow_search' ] == true) { + $option[ 'tags' ][] = "search"; + } + + // handle tags + if (isset($option[ 'tags' ])) { + $this->_handleTags($keyword, $time, $option[ 'tags' ]); + } + + // handle method + if ((int)$this->config[ 'cache_method' ] > 1 && isset($object[ 'size' ]) && (int)$object[ 'size' ] <= (int)$this->config[ 'limited_memory_each_object' ]) { + CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ] = $object; + if (in_array((int)$this->config[ 'cache_method' ], array(3, 4))) { + return true; + } + } + $this->_hit("set", 1); + return $this->driver_set($keyword, $object, $time, $option); + + } + + /** + * @param $keyword + * @param array $option + * @return mixed + */ + public function get($keyword, $option = array()) + { + /** + * Temporary disabled phpFastCache::$disabled = true + * Khoa. B + */ + + if (phpFastCache::$disabled === true) { + return null; + } + + // handle method + if ((int)$this->config[ 'cache_method' ] > 1) { + if (isset(CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ])) { + $object = CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ]; + } + } + + if (!isset($object)) { + $this->_hit("get", 1); + $object = $this->driver_get($keyword, $option); + + // handle method + if ((int)$this->config[ 'cache_method' ] > 1 && isset($object[ 'size' ]) && (int)$object[ 'size' ] <= (int)$this->config[ 'limited_memory_each_object' ]) { + CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ] = $object; + } + // end handle method + } + + if ($object == null) { + return null; + } + + $value = isset($object[ 'value' ]) ? $object[ 'value' ] : null; + return isset($option[ 'all_keys' ]) && $option[ 'all_keys' ] ? $object : $value; + } + + /** + * @param $keyword + * @param array $option + * @return null|object + */ + public function getInfo($keyword, $option = array()) + { + if ((int)$this->config[ 'cache_method' ] > 1) { + if (isset(CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ])) { + $object = CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ]; + } + } + if (!isset($object)) { + $object = $this->driver_get($keyword, $option); + } + if ($object == null) { + return null; + } + return $object; + } + + /** + * @param $keyword + * @param array $option + * @return mixed + */ + public function delete($keyword, array $option = array()) + { + // handle method + if ((int)$this->config[ 'cache_method' ] > 1) { + // use memory + unset(CacheManager::$memory[ $this->config[ 'instance' ] ][ $keyword ]); + } + // end handle method + return $this->driver_delete($keyword, $option); + } + + /** + * @param array $option + * @return mixed + */ + public function stats(array $option = array()) + { + return $this->driver_stats($option); + } + + /** + * @param array $option + * @return mixed + */ + public function clean(array $option = array()) + { + // handle method + if ((int)$this->config[ 'cache_method' ] > 1) { + // use memory + CacheManager::$memory[ $this->config[ 'instance' ] ] = array(); + } + // end handle method + return $this->driver_clean($option); + } + + /** + * @param $keyword + * @return bool + */ + public function isExisting($keyword) + { + if (method_exists($this, 'driver_isExisting')) { + return $this->driver_isExisting($keyword); + } + + $data = $this->get($keyword); + if ($data == null) { + return false; + } else { + return true; + } + + } + + /** + * Searches though the cache for keys that match the given query. + * @param $query_as_regex_or_string + * @param bool $search_in_value + * @return mixed + * @throws phpFastCacheDriverException + */ + public function search($query_as_regex_or_string, $search_in_value = false) + { + if ($this->config[ 'allow_search' ] != true) { + throw new phpFastCacheDriverException('Please setup allow_search = true'); + } else { + $list = $this->getTags("search", $search_in_value); + $tmp = explode("/", $query_as_regex_or_string, 2); + $regex = isset($tmp[ 1 ]) ? true : false; + $return_list = array(); + foreach ($list as $tag) { + foreach ($tag as $keyword => $value) { + $gotcha = false; + if ($search_in_value == true) { + $value = $this->get($keyword); + } + + if ($regex == true && $gotcha == false) { // look in keyword + if (preg_match($query_as_regex_or_string, $keyword)) { + $return_list[ $keyword ] = $value; + $gotcha = true; + } + } + if ($gotcha == false) { + if (strpos($keyword, $query_as_regex_or_string) !== false) { + $return_list[ $keyword ] = $value; + $gotcha = true; + } + } + + if ($search_in_value == true && $gotcha == false) { // value search + if ($regex == true && $gotcha == false) { + if (preg_match($query_as_regex_or_string, $value)) { + $return_list[ $keyword ] = $value; + $gotcha = true; + } + } + if ($gotcha == false) { + if (strpos($value, $query_as_regex_or_string) !== false) { + $return_list[ $keyword ] = $value; + $gotcha = true; + } + } + } + } // each tags loop + } // end foreach + return $return_list; + } + } + + /** + * @param $keyword + * @param int $step + * @param array $option + * @return bool + */ + public function increment($keyword, $step = 1, array $option = array()) + { + $object = $this->get($keyword, array('all_keys' => true)); + if ($object == null) { + return false; + } else { + $value = (int)$object[ 'value' ] + (int)$step; + $time = $object[ 'expired_time' ] - time(); + $this->set($keyword, $value, $time, $option); + return true; + } + } + + /** + * @param $keyword + * @param int $step + * @param array $option + * @return bool + */ + public function decrement($keyword, $step = 1, array $option = array()) + { + $object = $this->get($keyword, array('all_keys' => true)); + if ($object == null) { + return false; + } else { + $value = (int)$object[ 'value' ] - (int)$step; + $time = $object[ 'expired_time' ] - time(); + $this->set($keyword, $value, $time, $option); + return true; + } + } + + /** + * Extend more time + * @param $keyword + * @param int $time + * @param array $option + * @return bool + */ + public function touch($keyword, $time = 300, array $option = array()) + { + $object = $this->get($keyword, array('all_keys' => true)); + if ($object == null) { + return false; + } else { + $value = $object[ 'value' ]; + $time = $object[ 'expired_time' ] - time() + $time; + $this->set($keyword, $value, $time, $option); + return true; + } + } + + + /** + * Other Functions Built-int for phpFastCache since 1.3 + */ + + /** + * @param array $list + */ + public function setMulti(array $list = array()) + { + foreach ($list as $array) { + $this->set($array[ 0 ], isset($array[ 1 ]) ? $array[ 1 ] : 0, + isset($array[ 2 ]) ? $array[ 2 ] : array()); + } + } + + /** + * @param array $list + * @return array + */ + public function getMulti(array $list = array()) + { + $res = array(); + foreach ($list as $array) { + $name = $array[ 0 ]; + $res[ $name ] = $this->get($name, + isset($array[ 1 ]) ? $array[ 1 ] : array()); + } + return $res; + } + + /** + * @param array $list + * @return array + */ + public function getInfoMulti(array $list = array()) + { + $res = array(); + foreach ($list as $array) { + $name = $array[ 0 ]; + $res[ $name ] = $this->getInfo($name, + isset($array[ 1 ]) ? $array[ 1 ] : array()); + } + return $res; + } + + /** + * @param array $list + * @param array $option + */ + public function deleteMulti(array $list = array(), array $option = array()) + { + foreach ($list as $item) { + if (is_array($item) && count($item) === 2) { + $this->delete($item[ 0 ], $item[ 1 ]); + } + } + } + + /** + * @param array $list + * @return array + */ + public function isExistingMulti(array $list = array()) + { + $res = array(); + foreach ($list as $array) { + $name = $array[ 0 ]; + $res[ $name ] = $this->isExisting($name); + } + return $res; + } + + /** + * @param array $list + * @return array + */ + public function incrementMulti(array $list = array()) + { + $res = array(); + foreach ($list as $array) { + $name = $array[ 0 ]; + $res[ $name ] = $this->increment($name, $array[ 1 ], + isset($array[ 2 ]) ? $array[ 2 ] : array()); + } + return $res; + } + + /** + * @param array $list + * @return array + */ + public function decrementMulti(array $list = array()) + { + $res = array(); + foreach ($list as $array) { + $name = $array[ 0 ]; + $res[ $name ] = $this->decrement($name, $array[ 1 ], + isset($array[ 2 ]) ? $array[ 2 ] : array()); + } + return $res; + } + + /** + * @param array $list + * @return array + */ + public function touchMulti(array $list = array()) + { + $res = array(); + foreach ($list as $array) { + $name = $array[ 0 ]; + $res[ $name ] = $this->touch($name, $array[ 1 ], + isset($array[ 2 ]) ? $array[ 2 ] : array()); + } + return $res; + } + + /** + * @param $config_name + * @param string $value + */ + public function setup($config_name, $value = '') + { + /* + * Config for class + */ + if (is_array($config_name)) { + $this->config = array_merge($this->config, $config_name); + } else { + $this->config[ $config_name ] = $value; + } + } + + /** + * @param int $time + */ + public function autoCleanExpired($time = 3600) + { + } + + /** + * Magic methods + */ + + /** + * @param $name + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * @param $name + * @param $v + * @return bool|null + * @throws \Exception + */ + public function __set($name, $v) + { + if (isset($v[ 1 ]) && is_scalar($v[ 1 ])) { + return $this->set($name, $v[ 0 ], $v[ 1 ], + isset($v[ 2 ]) ? $v[ 2 ] : array()); + } else { + throw new phpFastCacheDriverException("Example ->$name = array('VALUE', 300);", 98); + } + } + + + /** + * Base Methods + */ + + + /** + * @return mixed + */ + protected function backup() + { + return phpFastCache(phpFastCache::$config[ 'fallback' ]); + } + + /** + * @param $name + * @return void + */ + protected function required_extension($name) + { + require_once(__DIR__ . '/../' . $this->extension_dir . '/' . $name . '.' . PHP_EXT); + } + + + /** + * @param $file + * @return string + * @throws \Exception + */ + protected function readfile($file) + { + if (function_exists('file_get_contents')) { + return file_get_contents($file); + } else { + $string = ''; + + $file_handle = @fopen($file, 'r'); + if (!$file_handle) { + throw new phpFastCacheDriverException("Can't Read File", 96); + + } + while (!feof($file_handle)) { + $line = fgets($file_handle); + $string .= $line; + } + fclose($file_handle); + + return $string; + } + } + + /** + * return PATH for Files & PDO only + * @param bool $create_path + * @return string + * @throws \Exception + */ + public function getPath($create_path = false) + { + return phpFastCache::getPath($create_path, $this->config); + } + + + /** + * Object for Files & SQLite + * @param $data + * @return string + */ + protected function encode($data) + { + return serialize($data); + } + + /** + * @param $value + * @return mixed + */ + protected function decode($value) + { + $x = @unserialize($value); + if ($x == false) { + return $value; + } else { + return $x; + } + } + + /** + * Check phpModules or CGI + * @return bool + */ + protected function isPHPModule() + { + return phpFastCache::isPHPModule(); + } + + + /** + * @param $class + * @return bool + */ + protected function isExistingDriver($class) + { + return class_exists("\\phpFastCache\\Drivers\\{$class}"); + } + + + /** + * @return int + */ + protected function __setChmodAuto() + { + return phpFastCache::__setChmodAuto($this->config); + } + + + /** + * @param $tag + * @return string + */ + protected function _getTagName($tag) + { + return "__tag__" . $tag; + } + + /** + * @return \phpFastCache\Core\DriverAbstract + */ + protected function _tagCaching() + { + return CacheManager::Sqlite( + array( + "path" => $this->config[ 'path' ], + "cache_method" => 3, + ) + ); + } + + /** + * @param string $keyword + * @param mixed $value + * @param integer $time + * @param array $tags + * @param array $option | $option = array("tags" => array("a","b","c") + * @return mixed + */ + public function setTags($keyword, $value = '', $time = 0, $tags = array(), $option = array()) + { + if (!is_array($tags)) { + $tags = array($tags); + } + $option[ 'tags' ] = $tags; + return $this->set($keyword, $value, $time, $option); + } + + protected function _handleTags($keyword, $time, $tags) + { + foreach ($tags as $tag) { + $list = $this->_tagCaching()->get($this->_getTagName($tag)); + if (is_null($list)) { + $list = array(); + } + $list[ $keyword ] = time() + $time; + $this->_tagCaching()->set($this->_getTagName($tag), $list, 3600 * 24 * 30); + } + } + + + /** + * @param array $tags + * @param bool $return_content + * @param array $option | $option = array("tags" => array("a","b","c") + * @return array + */ + public function getTags($tags = array(), $return_content = true, $option = array()) + { + if (!is_array($tags)) { + $tags = array($tags); + } + $keywords = array(); + $tmp = 0; + + foreach ($tags as $tag) { + $list = $this->_tagCaching()->get($this->_getTagName($tag)); + $list_return = array(); + if (is_null($list)) { + $list = array(); + } + foreach ($list as $keyword => $time) { + if ($time <= time()) { + unset($list[ $keyword ]); + } else { + if ($tmp < $time) { + $tmp = $time; + } + if ($return_content == true) { + $list_return[ $keyword ] = $this->get($keyword); + } else { + $list_return[ $keyword ] = $time; + } + } + } + + $this->_tagCaching()->set($this->_getTagName($tag), $list, $tmp); + $keywords[ $tag ] = $list_return; + } + return $keywords; + } + + /** + * @param array $tags | array("a","b","c") + * @param int $time + * @param array $options + * @return mixed + * @internal param array $option | $option = array("tags" => array("a","b","c") + */ + public function touchTags($tags = array(), $time = 300, $options = array()) + { + if (!is_array($tags)) { + $tags = array($tags); + } + $lists = $this->getTags($tags); + foreach ($lists as $tag => $keywords) { + foreach ($keywords as $keyword => $time) { + $this->touch($keyword, $time, $options); + } + } + return true; + } + + /** + * @param array $tags | array("a","b","c") + * @param array $option | $option = array("tags" => array("a","b","c") + * @return mixed + */ + public function deleteTags($tags = array(), $option = array()) + { + if (!is_array($tags)) { + $tags = array($tags); + } + $lists = $this->getTags($tags); + foreach ($lists as $tag => $keywords) { + foreach ($keywords as $keyword => $time) { + $this->delete($keyword, $option); + } + } + return true; + } + + + /** + * @param array $tags | array("a","b","c") + * @param integer + * @param array $option | $option = array("tags" => array("a","b","c") + * @return mixed + */ + public function incrementTags($tags = array(), $step = 1, $option = array()) + { + if (!is_array($tags)) { + $tags = array($tags); + } + $lists = $this->getTags($tags); + foreach ($lists as $tag => $keywords) { + foreach ($keywords as $keyword => $time) { + $this->increment($keyword, $step, $option); + } + } + return true; + } + + /** + * @param array $tags | array("a","b","c") + * @param integer + * @param array $option | $option = array("tags" => array("a","b","c") + * @return mixed + */ + public function decrementTags($tags = array(), $step = 1, $option = array()) + { + if (!is_array($tags)) { + $tags = array($tags); + } + $lists = $this->getTags($tags); + foreach ($lists as $tag => $keywords) { + foreach ($keywords as $keyword => $time) { + $this->decrement($keyword, $step, $option); + } + } + return true; + } + + /** + * @param $value + */ + protected function _kbdebug($value) + { + /* + echo "
";
+        print_r($value);
+        echo "
"; + */ + } + + public function _hit($index, $step = 1) + { + $instance = $this->config[ 'instance' ]; + $current = isset(CacheManager::$hit[ $instance ][ 'data' ][ $index ]) ? CacheManager::$hit[ $instance ][ 'data' ][ $index ] : 0; + CacheManager::$hit[ $instance ][ 'data' ][ $index ] = $current + ($step); + } + +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Core/DriverInterface.php b/lib/phpfastcache/phpFastCache/Core/DriverInterface.php new file mode 100644 index 0000000..1724374 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Core/DriverInterface.php @@ -0,0 +1,86 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Core; + +/** + * Interface DriverInterface + * @package phpFastCache\Core + */ +interface DriverInterface +{ + /** + * Check if this Cache driver is available for server or not + * phpFastCache_driver constructor. + * @param array $config + */ + public function __construct($config = array()); + + /** + * @return mixed + */ + public function checkdriver(); + + /** + * Set a obj to cache + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return mixed + */ + public function driver_set( + $keyword, + $value = '', + $time = 300, + $option = array() + ); + + /** + * Return null or value of cache + * @param $keyword + * @param array $option + * @return mixed + */ + public function driver_get($keyword, $option = array()); + + /** + * Show stats of caching + * Return array("info","size","data") + * @param array $option + * @return mixed + */ + public function driver_stats($option = array()); + + /** + * Delete a cache + * @param $keyword + * @param array $option + * @return mixed + */ + public function driver_delete($keyword, $option = array()); + + /** + * Clean up whole cache + * @param array $option + * @return mixed + */ + public function driver_clean($option = array()); + + /** + * @param $config_name + * @param string $value + */ + public function setup($config_name, $value = ''); +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Core/phpFastCache.php b/lib/phpfastcache/phpFastCache/Core/phpFastCache.php new file mode 100644 index 0000000..e584e85 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Core/phpFastCache.php @@ -0,0 +1,354 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Core; + +use phpFastCache\CacheManager; +use phpFastCache\Exceptions\phpFastCacheCoreException; +use phpFastCache\Exceptions\phpFastCacheDriverException; + +/** + * Class phpFastCache + * @package phpFastCache\Core + * + * Handle methods using annotations for IDE + * because they're handled by __call() + * Check out DriverInterface to see all + * the drivers methods magically implemented + * + * @method get() driver_get($keyword, $option = array()) Return null or value of cache + * @method set() driver_set($keyword, $value = '', $time = 300, $option = array()) Set a obj to cache + * @method delete() delete(string $keyword) Delete key from cache + * @method clean() clean($option = array()) Clean up whole cache + * @method checkdriver() checkdriver() Delete key from cache + * @method stats() stats($option = array()) Show stats of caching + * @method systemInfo() systemInfo() Return System Information + * + */ +class phpFastCache +{ + /** + * @var bool + */ + public static $disabled = false; + + /** + * @var array + */ + public static $config = array( + 'storage' => '', // blank for auto + 'default_chmod' => 0777, // 0777 , 0666, 0644 + + 'overwrite' => "", // files, sqlite, etc it will overwrite ur storage and all other caching for waiting u fix ur server + 'allow_search' => false, // turn to true will allow $method search("/regex/") + + 'fallback' => 'files', //Fall back when old driver is not support + + 'securityKey' => 'auto', + 'htaccess' => true, + 'path' => '', + + 'memcache' => array( + array('127.0.0.1', 11211, 1), + // array("new.host.ip",11211,1), + ), + + 'redis' => array( + 'host' => '127.0.0.1', + 'port' => '', + 'password' => '', + 'database' => '', + 'timeout' => '', + ), + + 'ssdb' => array( + 'host' => '127.0.0.1', + 'port' => 8888, + 'password' => '', + 'timeout' => '', + ), + + 'extensions' => array(), + "cache_method" => 1, // 1 = normal, 2 = phpfastcache, 3 = memory + "limited_memory_each_object" => 4000, // maximum size (bytes) of object store in memory + "compress_data" => false // compress stored data, if the backend supports it + ); + + /** + * @var array + */ + protected static $tmp = array(); + + /** + * @var DriverAbstract $instance + */ + public $instance; + + /** + * phpFastCache constructor. + * @param string $storage + * @param array $config + */ + public function __construct($storage = '', $config = array()) + { + if (empty($config)) { + $config = phpFastCache::$config; + } + $config[ 'storage' ] = $storage; + + $storage = strtolower($storage); + if ($storage == '' || $storage == 'auto') { + $storage = self::getAutoClass($config); + } + + $this->instance = CacheManager::getInstance($storage, $config); + } + + /** + * Cores + */ + + /** + * @param $config + * @return string + * @throws \Exception + */ + public static function getAutoClass($config) + { + $path = self::getPath(false, $config); + if (is_writable($path)) { + $driver = 'files'; + } else if (extension_loaded('apc') && ini_get('apc.enabled') && strpos(PHP_SAPI, 'CGI') === false) { + $driver = 'apc'; + } else if (class_exists('memcached')) { + $driver = 'memcached'; + } elseif (extension_loaded('wincache') && function_exists('wincache_ucache_set')) { + $driver = 'wincache'; + } elseif (extension_loaded('xcache') && function_exists('xcache_get')) { + $driver = 'xcache'; + } else if (function_exists('memcache_connect')) { + $driver = 'memcache'; + } else if (class_exists('Redis')) { + $driver = 'redis'; + } else { + $driver = 'files'; + } + + return $driver; + } + + /** + * @param bool $skip_create_path + * @param $config + * @return string + * @throws \Exception + */ + public static function getPath($skip_create_path = false, $config) + { + $tmp_dir = ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir(); + + if (!isset($config[ 'path' ]) || $config[ 'path' ] == '') { + if (self::isPHPModule()) { + $path = $tmp_dir; + } else { + $document_root_path = rtrim($_SERVER[ 'DOCUMENT_ROOT' ], '/') . '/../'; + $path = isset($_SERVER[ 'DOCUMENT_ROOT' ]) && is_writable($document_root_path) + ? $document_root_path + : rtrim(__DIR__, '/') . '/'; + } + + if (self::$config[ 'path' ] != '') { + $path = $config[ 'path' ]; + } + + } else { + $path = $config[ 'path' ]; + } + + $securityKey = array_key_exists('securityKey', + $config) ? $config[ 'securityKey' ] : ''; + if ($securityKey == "" || $securityKey == 'auto') { + $securityKey = self::$config[ 'securityKey' ]; + if ($securityKey == 'auto' || $securityKey == '') { + $securityKey = isset($_SERVER[ 'HTTP_HOST' ]) ? preg_replace('/^www./', + '', strtolower(str_replace(':', '_', $_SERVER[ 'HTTP_HOST' ]))) : "default"; + } + } + if ($securityKey != '') { + $securityKey .= '/'; + } + + $securityKey = self::cleanFileName($securityKey); + + $full_path = rtrim($path,'/') . '/' . $securityKey; + $full_pathx = md5($full_path); + + + if ($skip_create_path == false && !isset(self::$tmp[ $full_pathx ])) { + + if (!@file_exists($full_path) || !@is_writable($full_path)) { + if (!@file_exists($full_path)) { + @mkdir($full_path, self::__setChmodAuto($config), true); + } + if (!@is_writable($full_path)) { + @chmod($full_path, self::__setChmodAuto($config)); + } + if(!@is_writable($full_path)) { + // switch back to tmp dir again if the path is not writeable + $full_path = rtrim($tmp_dir,'/') . '/' . $securityKey; + if (!@file_exists($full_path)) { + @mkdir($full_path, self::__setChmodAuto($config), true); + } + if (!@is_writable($full_path)) { + @chmod($full_path, self::__setChmodAuto($config)); + } + } + if (!@file_exists($full_path) || !@is_writable($full_path)) { + throw new phpFastCacheCoreException('PLEASE CREATE OR CHMOD ' . $full_path . ' - 0777 OR ANY WRITABLE PERMISSION!', 92); + } + } + + self::$tmp[ $full_pathx ] = true; + self::htaccessGen($full_path, array_key_exists('htaccess', $config) ? $config[ 'htaccess' ] : false); + } + + return realpath($full_path); + + } + + /** + * @param $filename + * @return mixed + */ + public static function cleanFileName($filename) + { + $regex = array( + '/[\?\[\]\/\\\=\<\>\:\;\,\'\"\&\$\#\*\(\)\|\~\`\!\{\}]/', + '/\.$/', + '/^\./', + ); + $replace = array('-', '', ''); + return trim(preg_replace($regex, $replace, trim($filename)),'-'); + } + + /** + * @param $config + * @return int + */ + public static function __setChmodAuto($config) + { + if (!isset($config[ 'default_chmod' ]) || $config[ 'default_chmod' ] == '' || is_null($config[ 'default_chmod' ])) { + return 0777; + } else { + return $config[ 'default_chmod' ]; + } + } + + /** + * @return array + */ + protected static function getOS() + { + $os = array( + 'os' => PHP_OS, + 'php' => PHP_SAPI, + 'system' => php_uname(), + 'unique' => md5(php_uname() . PHP_OS . PHP_SAPI), + ); + return $os; + } + + /** + * @return bool + */ + public static function isPHPModule() + { + if (PHP_SAPI === 'apache2handler') { + return true; + } else { + if (strpos(PHP_SAPI, 'handler') !== false) { + return true; + } + } + return false; + } + + /** + * @param $path + * @param bool $create + * @throws \Exception + */ + protected static function htaccessGen($path, $create = true) + { + + if ($create == true) { + if (!is_writable($path)) { + try { + chmod($path, 0777); + } catch (phpFastCacheDriverException $e) { + throw new phpFastCacheDriverException('PLEASE CHMOD ' . $path . ' - 0777 OR ANY WRITABLE PERMISSION!', + 92); + } + } + + if(!file_exists($path."/.htaccess")) { + // echo "write me"; + $html = "order deny, allow \r\n +deny from all \r\n +allow from 127.0.0.1"; + + $f = @fopen($path . '/.htaccess', 'w+'); + if (!$f) { + throw new phpFastCacheDriverException('PLEASE CHMOD ' . $path . ' - 0777 OR ANY WRITABLE PERMISSION!', 92); + } + fwrite($f, $html); + fclose($f); + + + } + } + + } + + /** + * @param $name + * @param string $value + */ + public static function setup($name, $value = '') + { + if (is_array($name)) { + self::$config = array_merge(self::$config,$name); + } else { + self::$config[ $name ] = $value; + } + } + + /** + * @param $something + */ + public static function debug($something) + { + echo "Starting Debugging ...
\r\n "; + if (is_array($something)) { + echo '
';
+            print_r($something);
+            echo '
'; + var_dump($something); + } else { + echo $something; + } + echo "\r\n
Ended"; + exit; + } +} diff --git a/lib/phpfastcache/phpFastCache/Core/phpFastCacheExtensions.php b/lib/phpfastcache/phpFastCache/Core/phpFastCacheExtensions.php new file mode 100644 index 0000000..813980f --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Core/phpFastCacheExtensions.php @@ -0,0 +1,31 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Core; + +/** + * Class phpFastCacheExtensions + * @package phpFastCache\Core + */ +abstract class phpFastCacheExtensions +{ + /** + * @param $name + * @param $agr + */ + public function __call($name, $agr) + { + + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/apc.php b/lib/phpfastcache/phpFastCache/Drivers/apc.php new file mode 100644 index 0000000..36618fe --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/apc.php @@ -0,0 +1,137 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use phpFastCache\Exceptions\phpFastCacheDriverException; + +/** + * Class apc + * @package phpFastCache\Drivers + */ +class apc extends DriverAbstract +{ + /** + * phpFastCache_apc constructor. + * @param array $config + * @throws phpFastCacheDriverException + */ + public function __construct($config = array()) + { + $this->setup($config); + + if (!$this->checkdriver()) { + throw new phpFastCacheDriverException('APC is not installed, cannot continue.'); + } + } + + /** + * @return bool + */ + public function checkdriver() + { + if (extension_loaded('apc') && ini_get('apc.enabled')) { + return true; + } else { + $this->fallback = true; + return false; + } + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return array|bool + */ + public function driver_set( + $keyword, + $value = '', + $time = 300, + $option = array() + ) { + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + return apc_add($keyword, $value, $time); + } else { + return apc_store($keyword, $value, $time); + } + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + $data = apc_fetch($keyword, $bo); + if ($bo === false) { + return null; + } + return $data; + + } + + /** + * @param $keyword + * @param array $option + * @return bool|\string[] + */ + public function driver_delete($keyword, $option = array()) + { + return apc_delete($keyword); + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => '', + ); + + try { + $res[ 'data' ] = apc_cache_info('user'); + } catch (\Exception $e) { + $res[ 'data' ] = array(); + } + + return $res; + } + + /** + * @param array $option + * @return void + */ + public function driver_clean($option = array()) + { + @apc_clear_cache(); + @apc_clear_cache('user'); + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + return (bool) apc_exists($keyword); + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/cookie.php b/lib/phpfastcache/phpFastCache/Drivers/cookie.php new file mode 100644 index 0000000..1326241 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/cookie.php @@ -0,0 +1,156 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; + +/** + * Class cookie + * @package phpFastCache\Drivers + */ +class cookie extends DriverAbstract +{ + /** + * phpFastCache_cookie constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + } + + /** + * @return bool + */ + public function checkdriver() + { + // Check memcache + if (function_exists('setcookie')) { + return true; + } + $this->fallback = true; + return false; + } + + /** + * + */ + public function connectServer() + { + // for cookie check output + if (!isset($_COOKIE[ 'phpFastCache' ])) { + if (!@setcookie('phpFastCache', 1, 10)) { + $this->fallback = true; + } + } + + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + $this->connectServer(); + $keyword = 'phpFastCache_' . $keyword; + $v = $this->encode($value); + if(isset($this->config['limited_memory_each_object']) + && strlen($v) > $this->config['limited_memory_each_object']) { + return false; + } + return setcookie($keyword, $v, time() + ($time ? (int)$time : 300), '/'); + + } + + /** + * @param $keyword + * @param array $option + * @return bool|mixed|null + */ + public function driver_get($keyword, $option = array()) + { + $this->connectServer(); + // return null if no caching + // return value if in caching + $keyword = 'phpFastCache_' . $keyword; + $x = isset($_COOKIE[ $keyword ]) ? $this->decode($_COOKIE[ $keyword ]) : false; + if ($x == false) { + return null; + } else { + return $x; + } + } + + /** + * @param $keyword + * @param array $option + */ + public function driver_delete($keyword, $option = array()) + { + $this->connectServer(); + $keyword = 'phpFastCache_' . $keyword; + @setcookie($keyword, null, -10); + $_COOKIE[ $keyword ] = null; + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $this->connectServer(); + $res = array( + 'info' => '', + 'size' => '', + 'data' => $_COOKIE, + ); + + return $res; + } + + /** + * @param array $option + */ + public function driver_clean($option = array()) + { + $this->connectServer(); + foreach ($_COOKIE as $keyword => $value) { + if (strpos($keyword, 'phpFastCache') !== false) { + @setcookie($keyword, null, -10); + $_COOKIE[ $keyword ] = null; + } + } + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + $this->connectServer(); + $x = $this->get($keyword); + + return !($x == null); + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/example.php b/lib/phpfastcache/phpFastCache/Drivers/example.php new file mode 100644 index 0000000..7d34b57 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/example.php @@ -0,0 +1,128 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use phpFastCache\Core\DriverInterface; +use phpFastCache\Exceptions\phpFastCacheDriverException; + +/** + * Class example + * @package phpFastCache\Drivers + */ +class example extends DriverAbstract +{ + /** + * phpFastCache_example constructor. + * @param array $config + * @throws phpFastCacheDriverException + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + throw new phpFastCacheDriverException("Can't use this driver for your website!"); + } + + } + + /** + * @return bool + */ + public function checkdriver() + { + return false; + } + + /** + * + */ + public function connectServer() + { + + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return void + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + // skip driver + } else { + // add driver + } + + } + + /** + * @param $keyword + * @param array $option + * @return null + */ + public function driver_get($keyword, $option = array()) + { + // return null if no caching + // return value if in caching + + return null; + } + + /** + * @param $keyword + * @param array $option + * @return void + */ + public function driver_delete($keyword, $option = array()) + { + + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => '', + ); + + return $res; + } + + /** + * @param array $option + * @return void + */ + public function driver_clean($option = array()) + { + + } + + /** + * @param $keyword + */ + public function driver_isExisting($keyword) + { + + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/files.php b/lib/phpfastcache/phpFastCache/Drivers/files.php new file mode 100644 index 0000000..c8a46c1 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/files.php @@ -0,0 +1,306 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use phpFastCache\Exceptions\phpFastCacheDriverException; + +/** + * Class files + * @package phpFastCache\Drivers + */ +class files extends DriverAbstract +{ + /** + * Init Cache Path + * phpFastCache_files constructor. + * @param array $config + * @throws phpFastCacheDriverException + */ + public function __construct($config = array()) + { + $this->setup($config); + $this->getPath(); // force create path + + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + throw new phpFastCacheDriverException("Can't use this driver for your website!"); + } + } + + /** + * @return bool + */ + public function checkdriver() + { + if (is_writable($this->getPath())) { + return true; + }/* else { + + }*/ + return false; + } + + /** + * @param $keyword + * @param bool $skip + * @return string + * @throws phpFastCacheDriverException + */ + private function getFilePath($keyword, $skip = false) + { + $path = $this->getPath(); + + $filename = $this->encodeFilename($keyword); + $folder = substr($filename, 0, 2); + $path = rtrim($path, '/') . '/' . $folder; + /** + * Skip Create Sub Folders; + */ + if ($skip == false) { + if (!file_exists($path)) { + if (!mkdir($path, $this->__setChmodAuto(), true)) { + throw new phpFastCacheDriverException('PLEASE CHMOD ' . $this->getPath() . ' - 0777 OR ANY WRITABLE PERMISSION!', 92); + } + } + } + + return $path . '/' . $filename . '.txt'; + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + * @throws \Exception + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + $file_path = $this->getFilePath($keyword); + $data = $this->encode($value); + + $toWrite = true; + /* + * Skip if Existing Caching in Options + */ + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true && file_exists($file_path)) { + $content = $this->readfile($file_path); + $old = $this->decode($content); + $toWrite = false; + if ($this->isExpired($old)) { + $toWrite = true; + } + } + + // Force write + try { + if ($toWrite == true) { + $f = fopen($file_path, 'w+'); + fwrite($f, $data); + fclose($f); + return true; + } + } catch (\Exception $e) { + return false; + } + + return false; + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + * @throws \Exception + */ + public function driver_get($keyword, $option = array()) + { + + $file_path = $this->getFilePath($keyword); + if (!file_exists($file_path)) { + return null; + } + + $content = $this->readfile($file_path); + $object = $this->decode($content); + if ($this->isExpired($object)) { + @unlink($file_path); + $this->autoCleanExpired(); + return null; + } + + return $object; + } + + /** + * @param $keyword + * @param array $option + * @return bool + * @throws \Exception + */ + public function driver_delete($keyword, $option = array()) + { + $file_path = $this->getFilePath($keyword, true); + if (file_exists($file_path) && @unlink($file_path)) { + return true; + } else { + return false; + } + } + + /** + * Return total cache size + auto removed expired files + * @param array $option + * @return array + * @throws \Exception + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => '', + ); + + $path = $this->getPath(); + $dir = @opendir($path); + if (!$dir) { + throw new phpFastCacheDriverException("Can't read PATH:" . $path, 94); + } + + $total = 0; + $removed = 0; + while ($file = readdir($dir)) { + if ($file != '.' && $file != '..' && is_dir($path . '/' . $file)) { + // read sub dir + $subdir = opendir($path . "/" . $file); + if (!$subdir) { + throw new phpFastCacheDriverException("Can't read path:" . $path . '/' . $file, 93); + } + + while ($f = readdir($subdir)) { + if ($f != '.' && $f != '..') { + $file_path = $path . '/' . $file . '/' . $f; + $size = filesize($file_path); + $object = $this->decode($this->readfile($file_path)); + + if (strpos($f, '.') === false) { + $key = $f; + } else { + //Because PHP 5.3, this cannot be written in single line + $key = explode('.', $f); + $key = $key[ 0 ]; + } + $content[ $key ] = array( + 'size' => $size, + 'write_time' => (isset($object[ 'write_time' ]) ? $object[ 'write_time' ] : null), + ); + if ($this->isExpired($object)) { + @unlink($file_path); + $removed += $size; + } + $total += $size; + } + } + } + } + + $res[ 'size' ] = $total - $removed; + $res[ 'info' ] = array( + 'Total [bytes]' => $total, + 'Expired and removed [bytes]' => $removed, + 'Current [bytes]' => $res[ 'size' ], + ); + $res[ "data" ] = $content; + return $res; + } + + + /** + * @param int $time + */ + public function autoCleanExpired($time = 3600) + { + $autoclean = $this->get('keyword_clean_up_driver_files'); + if ($autoclean == null) { + $this->set('keyword_clean_up_driver_files', $time); + $res = $this->stats(); + } + } + + /** + * @param array $option + * @throws \Exception + * @return void + */ + public function driver_clean($option = array()) + { + + $path = $this->getPath(); + $dir = @opendir($path); + if (!$dir) { + throw new phpFastCacheDriverException("Can't read PATH:" . $path, 94); + } + + while ($file = readdir($dir)) { + if ($file != '.' && $file != '..' && is_dir($path . '/' . $file)) { + // read sub dir + $subdir = @opendir($path . '/' . $file); + if (!$subdir) { + throw new phpFastCacheDriverException("Can't read path:" . $path . '/' . $file, 93); + } + + while ($f = readdir($subdir)) { + if ($f != '.' && $f != '..') { + $file_path = $path . '/' . $file . '/' . $f; + @unlink($file_path); + } + } + } + } + } + + /** + * @param $keyword + * @return bool + * @throws \Exception + */ + public function driver_isExisting($keyword) + { + $file_path = $this->getFilePath($keyword, true); + if (!file_exists($file_path)) { + return false; + } else { + // check expired or not + $value = $this->get($keyword); + + return !($value == null); + } + } + + /** + * @param $object + * @return bool + */ + public function isExpired($object) + { + if (isset($object[ 'expired_time' ]) && time() >= $object[ 'expired_time' ]) { + return true; + } else { + return false; + } + } +} diff --git a/lib/phpfastcache/phpFastCache/Drivers/memcache.php b/lib/phpfastcache/phpFastCache/Drivers/memcache.php new file mode 100644 index 0000000..9ba830c --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/memcache.php @@ -0,0 +1,200 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use Memcache as MemcacheSoftware; + +/** + * Class memcache + * @package phpFastCache\Drivers + */ +class memcache extends DriverAbstract +{ + + /** + * @var \Memcache + */ + public $instant; + + /** + * @var int + */ + protected $memcacheFlags = 0; + + /** + * phpFastCache_memcache constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + if (class_exists('Memcache')) { + $this->instant = new MemcacheSoftware(); + + if (array_key_exists('compress_data', $config) && $config[ 'compress_data' ] === true) { + $this->memcacheFlags = MEMCACHE_COMPRESSED; + } + } else { + $this->fallback = true; + } + } + + /** + * @return bool + */ + public function checkdriver() + { + // Check memcache + if (function_exists('memcache_connect')) { + return true; + } + $this->fallback = true; + return false; + } + + /** + * + */ + public function connectServer() + { + $server = $this->config[ 'memcache' ]; + if (count($server) < 1) { + $server = array( + array('127.0.0.1', 11211), + ); + } + + foreach ($server as $s) { + $name = $s[ 0 ] . "_" . $s[ 1 ]; + if (!isset($this->checked[ $name ])) { + try { + if (!$this->instant->addserver($s[ 0 ], $s[ 1 ])) { + $this->fallback = true; + } + + $this->checked[ $name ] = 1; + } catch (\Exception $e) { + $this->fallback = true; + } + + + } + + } + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return array|bool + */ + public function driver_set( + $keyword, + $value = '', + $time = 300, + $option = array() + ) { + $this->connectServer(); + + // Memcache will only allow a expiration timer less than 2592000 seconds, + // otherwise, it will assume you're giving it a UNIX timestamp. + if ($time > 2592000) { + $time = time() + $time; + } + + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + return $this->instant->add($keyword, $value, $this->memcacheFlags, $time); + + } else { + return $this->instant->set($keyword, $value, $this->memcacheFlags, $time); + } + } + + /** + * @param $keyword + * @param array $option + * @return array|null|string + */ + public function driver_get($keyword, $option = array()) + { + $this->connectServer(); + + // return null if no caching + // return value if in caching + + $x = $this->instant->get($keyword); + + if ($x == false) { + return null; + } else { + return $x; + } + + } + + /** + * @param $keyword + * @param array $option + */ + public function driver_delete($keyword, $option = array()) + { + $this->connectServer(); + $this->instant->delete($keyword); + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $this->connectServer(); + $res = array( + 'info' => '', + 'size' => '', + 'data' => $this->instant->getStats(), + ); + + return $res; + + } + + /** + * @param array $option + */ + public function driver_clean($option = array()) + { + $this->connectServer(); + $this->instant->flush(); + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + $this->connectServer(); + $x = $this->get($keyword); + + return !($x == null); + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/memcached.php b/lib/phpfastcache/phpFastCache/Drivers/memcached.php new file mode 100644 index 0000000..a93735f --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/memcached.php @@ -0,0 +1,200 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use Memcached as MemcachedSoftware; + +/** + * Class memcached + * @package phpFastCache\Drivers + */ +class memcached extends DriverAbstract +{ + + /** + * @var \Memcached + */ + public $instant; + + /** + * phpFastCache_memcached constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + + if (class_exists('Memcached')) { + $this->instant = new MemcachedSoftware(); + } else { + $this->fallback = true; + } + + } + + /** + * @return bool + */ + public function checkdriver() + { + if (class_exists('Memcached')) { + return true; + } + $this->fallback = true; + return false; + } + + + /** + * @return bool + */ + public function connectServer() + { + if ($this->checkdriver() == false) { + return false; + } + + $s = $this->config[ 'memcache' ]; + if (count($s) < 1) { + $s = array( + array('127.0.0.1', 11211, 100), + ); + } + + foreach ($s as $server) { + $name = isset($server[ 0 ]) ? $server[ 0 ] : '127.0.0.1'; + $port = isset($server[ 1 ]) ? $server[ 1 ] : 11211; + $sharing = isset($server[ 2 ]) ? $server[ 2 ] : 0; + $checked = $name . '_' . $port; + if (!isset($this->checked[ $checked ])) { + try { + if ($sharing > 0) { + if (!$this->instant->addServer($name, $port, + $sharing) + ) { + $this->fallback = true; + } + } else { + + if (!$this->instant->addServer($name, $port)) { + $this->fallback = true; + } + } + $this->checked[ $checked ] = 1; + } catch (\Exception $e) { + $this->fallback = true; + } + + } + } + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + $this->connectServer(); + + // Memcache will only allow a expiration timer less than 2592000 seconds, + // otherwise, it will assume you're giving it a UNIX timestamp. + if ($time > 2592000) { + $time = time() + $time; + } + + if (isset($option[ 'isExisting' ]) && $option[ 'isExisting' ] == true) { + return $this->instant->add($keyword, $value, $time); + } else { + return $this->instant->set($keyword, $value, $time); + + } + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + // return null if no caching + // return value if in caching + $this->connectServer(); + $x = @$this->instant->get($keyword);// Prevent memcached to return a warning for long keywords + if ($x == false) { + return null; + } else { + return $x; + } + } + + /** + * @param $keyword + * @param array $option + * @return void + */ + public function driver_delete($keyword, $option = array()) + { + $this->connectServer(); + $this->instant->delete($keyword); + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $this->connectServer(); + $res = array( + 'info' => '', + 'size' => '', + 'data' => $this->instant->getStats(), + ); + + return $res; + } + + /** + * @param array $option + * @return void + */ + public function driver_clean($option = array()) + { + $this->connectServer(); + $this->instant->flush(); + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + $this->connectServer(); + $x = $this->get($keyword); + + return !($x == null); + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/mongodb.php b/lib/phpfastcache/phpFastCache/Drivers/mongodb.php new file mode 100644 index 0000000..2b8d230 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/mongodb.php @@ -0,0 +1,125 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use phpFastCache\Exceptions\phpFastCacheDriverException; + +/** + * Class mongodb + * @package phpFastCache\Drivers + */ +class mongodb extends DriverAbstract +{ + /** + * phpFastCache constructor. + * @param array $config + * @throws phpFastCacheDriverException + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + throw new phpFastCacheDriverException("Can't use this driver for your website!"); + } + + } + + /** + * @return bool + */ + public function checkdriver() + { + // return true; + return false; + } + + /** + * + */ + public function connectServer() + { + + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + // skip driver + } else { + // add driver + } + + } + + /** + * @param $keyword + * @param array $option + * @return null + */ + public function driver_get($keyword, $option = array()) + { + // return null if no caching + // return value if in caching + + return null; + } + + /** + * @param $keyword + * @param array $option + */ + public function driver_delete($keyword, $option = array()) + { + + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => '', + ); + + return $res; + } + + /** + * @param array $option + */ + public function driver_clean($option = array()) + { + + } + + /** + * @param $keyword + */ + public function driver_isExisting($keyword) + { + + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/predis.php b/lib/phpfastcache/phpFastCache/Drivers/predis.php new file mode 100644 index 0000000..c0654e8 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/predis.php @@ -0,0 +1,222 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use Predis\Client as PredisSoftware; + +/** + * Class predis + * @package phpFastCache\Drivers + */ +class predis extends DriverAbstract +{ + + /** + * @var bool + */ + public $checked_redis = false; + + /** + * phpFastCache_predis constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!class_exists("\\Predis\\Client")) { + $this->required_extension("predis-1.0/autoload"); + } + } + + /** + * @return bool + */ + public function checkdriver() + { + // Check memcache + if (!class_exists("\\Predis\\Client")) { + $this->required_extension("predis-1.0/autoload"); + try { + \Predis\Autoloader::register(); + } catch (\Exception $e) { + + } + } + return true; + } + + + /** + * @return bool + */ + public function connectServer() + { + + $server = isset($this->config[ 'redis' ]) ? $this->config[ 'redis' ] : array( + 'host' => '127.0.0.1', + 'port' => '6379', + 'password' => '', + 'database' => '', + ); + + + if ($this->checked_redis === false) { + $c = array( + 'host' => $server[ 'host' ], + ); + + $port = isset($server[ 'port' ]) ? $server[ 'port' ] : ''; + if ($port != '') { + $c[ 'port' ] = $port; + } + + $password = isset($server[ 'password' ]) ? $server[ 'password' ] : ''; + if ($password != '') { + $c[ 'password' ] = $password; + } + + $database = isset($server[ 'database' ]) ? $server[ 'database' ] : ''; + if ($database != '') { + $c[ 'database' ] = $database; + } + + $timeout = isset($server[ 'timeout' ]) ? $server[ 'timeout' ] : ''; + if ($timeout != '') { + $c[ 'timeout' ] = $timeout; + } + + $read_write_timeout = isset($server[ 'read_write_timeout' ]) ? $server[ 'read_write_timeout' ] : ''; + if ($read_write_timeout != '') { + $c[ 'read_write_timeout' ] = $read_write_timeout; + } + + $this->instant = new PredisSoftware($c); + + $this->checked_redis = true; + + if (!$this->instant) { + $this->fallback = true; + return false; + } else { + return true; + } + } + + return true; + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return mixed + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + if ($this->connectServer()) { + $value = $this->encode($value); + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + return $this->instant->setex($keyword, $time, $value); + } else { + return $this->instant->setex($keyword, $time, $value); + } + } else { + return $this->backup()->set($keyword, $value, $time, $option); + } + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + if ($this->connectServer()) { + // return null if no caching + // return value if in caching' + $x = $this->instant->get($keyword); + if ($x == false) { + return null; + } else { + + return $this->decode($x); + } + } else { + $this->backup()->get($keyword, $option); + } + } + + /** + * @param $keyword + * @param array $option + */ + public function driver_delete($keyword, $option = array()) + { + + if ($this->connectServer()) { + $this->instant->del($keyword); + } + + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + if ($this->connectServer()) { + $res = array( + 'info' => '', + 'size' => '', + 'data' => $this->instant->info(), + ); + + return $res; + } + + return array(); + + } + + /** + * @param array $option + * @return void + */ + public function driver_clean($option = array()) + { + if ($this->connectServer()) { + $this->instant->flushDB(); + } + + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + if ($this->connectServer()) { + $x = $this->instant->exists($keyword); + return !($x == null); + } else { + return $this->backup()->isExisting($keyword); + } + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/redis.php b/lib/phpfastcache/phpFastCache/Drivers/redis.php new file mode 100644 index 0000000..8ca4195 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/redis.php @@ -0,0 +1,225 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use Redis as RedisSoftware; + +/** + * Class redis + * @package phpFastCache\Drivers + */ +class redis extends DriverAbstract +{ + + /** + * @var bool + */ + public $checked_redis = false; + + /** + * phpFastCache_predis constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + if (class_exists('Redis')) { + $this->instant = new RedisSoftware(); + } + + } + + /** + * @return bool + */ + public function checkdriver() + { + // Check memcache + if (class_exists('Redis')) { + return true; + } + $this->fallback = true; + return false; + } + + + /** + * @return bool + */ + public function connectServer() + { + + $server = isset($this->config[ 'redis' ]) ? $this->config[ 'redis' ] : array( + 'host' => '127.0.0.1', + 'port' => '6379', + 'password' => '', + 'database' => '', + 'timeout' => '1', + ); + + if ($this->checked_redis === false) { + + $host = $server[ 'host' ]; + + $port = isset($server[ 'port' ]) ? (int)$server[ 'port' ] : ""; + if ($port != '') { + $c[ 'port' ] = $port; + } + + $password = isset($server[ 'password' ]) ? $server[ 'password' ] : ''; + if ($password != '') { + $c[ 'password' ] = $password; + } + + $database = isset($server[ 'database' ]) ? $server[ 'database' ] : ''; + if ($database != '') { + $c[ 'database' ] = $database; + } + + $timeout = isset($server[ 'timeout' ]) ? $server[ 'timeout' ] : ''; + if ($timeout != '') { + $c[ 'timeout' ] = $timeout; + } + + $read_write_timeout = isset($server[ 'read_write_timeout' ]) ? $server[ 'read_write_timeout' ] : ''; + if ($read_write_timeout != '') { + $c[ 'read_write_timeout' ] = $read_write_timeout; + } + + + if (!$this->instant->connect($host, (int)$port, (int)$timeout)) { + $this->checked_redis = true; + $this->fallback = true; + return false; + } else { + if ($database != '') { + $this->instant->select((int)$database); + } + $this->checked_redis = true; + return true; + } + } + + return true; + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + if ($this->connectServer()) { + $value = $this->encode($value); + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + return $this->instant->set($keyword, $value, + array('xx', 'ex' => $time)); + } else { + return $this->instant->set($keyword, $value, $time); + } + } else { + return $this->backup()->set($keyword, $value, $time, $option); + } + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + if ($this->connectServer()) { + // return null if no caching + // return value if in caching' + $x = $this->instant->get($keyword); + if ($x == false) { + return null; + } else { + + return $this->decode($x); + } + } else { + $this->backup()->get($keyword, $option); + } + + } + + /** + * @param $keyword + * @param array $option + * @return void + */ + public function driver_delete($keyword, $option = array()) + { + if ($this->connectServer()) { + $this->instant->delete($keyword); + } + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + if ($this->connectServer()) { + $res = array( + 'info' => '', + 'size' => '', + 'data' => $this->instant->info(), + ); + + return $res; + } + return array(); + } + + /** + * @param array $option + */ + public function driver_clean($option = array()) + { + if ($this->connectServer()) { + $this->instant->flushDB(); + } + + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + if ($this->connectServer()) { + $x = $this->instant->exists($keyword); + if ($x == null) { + return false; + } else { + return true; + } + } else { + return $this->backup()->isExisting($keyword); + } + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/sqlite.php b/lib/phpfastcache/phpFastCache/Drivers/sqlite.php new file mode 100644 index 0000000..8d45201 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/sqlite.php @@ -0,0 +1,482 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use PDO; +use PDOException; +use phpFastCache\Exceptions\phpFastCacheDriverException; + +/** + * Class sqlite + * @package phpFastCache\Drivers + */ +class sqlite extends DriverAbstract +{ + /** + * + */ + const SQLITE_DIR = 'sqlite'; + /** + * + */ + const INDEXING_FILE = 'indexing'; + + /** + * @var int + */ + public $max_size = 10; // 10 mb + + /** + * @var array + */ + public $instant = array(); + /** + * @var null + */ + public $indexing = null; + /** + * @var string + */ + public $path = ''; + + /** + * @var int + */ + public $currentDB = 1; + + /** + * Init Main Database & Sub Database + * phpFastCache_sqlite constructor. + * @param array $config + * @throws phpFastCacheDriverException + */ + public function __construct($config = array()) + { + /** + * init the path + */ + $this->setup($config); + if (!$this->checkdriver()) { + throw new phpFastCacheDriverException('SQLITE is not installed, cannot continue.'); + } + + if (!file_exists($this->getPath() . '/' . self::SQLITE_DIR)) { + if (!mkdir($this->getPath() . '/' . self::SQLITE_DIR, $this->__setChmodAuto(), true)) { + $this->fallback = true; + } + } + $this->path = $this->getPath() . '/' . self::SQLITE_DIR; + } + + /** + * INIT NEW DB + * @param \PDO $db + */ + public function initDB(PDO $db) + { + $db->exec('drop table if exists "caching"'); + $db->exec('CREATE TABLE "caching" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "keyword" VARCHAR UNIQUE, "object" BLOB, "exp" INTEGER)'); + $db->exec('CREATE UNIQUE INDEX "cleanup" ON "caching" ("keyword","exp")'); + $db->exec('CREATE INDEX "exp" ON "caching" ("exp")'); + $db->exec('CREATE UNIQUE INDEX "keyword" ON "caching" ("keyword")'); + } + + /** + * INIT Indexing DB + * @param \PDO $db + */ + public function initIndexing(PDO $db) + { + + // delete everything before reset indexing + $dir = opendir($this->path); + while ($file = readdir($dir)) { + if ($file != '.' && $file != '..' && $file != 'indexing' && $file != 'dbfastcache') { + unlink($this->path . '/' . $file); + } + } + + $db->exec('drop table if exists "balancing"'); + $db->exec('CREATE TABLE "balancing" ("keyword" VARCHAR PRIMARY KEY NOT NULL UNIQUE, "db" INTEGER)'); + $db->exec('CREATE INDEX "db" ON "balancing" ("db")'); + $db->exec('CREATE UNIQUE INDEX "lookup" ON "balancing" ("keyword")'); + + } + + /** + * INIT Instant DB + * Return Database of Keyword + * @param $keyword + * @return int + */ + public function indexing($keyword) + { + if ($this->indexing == null) { + $createTable = false; + if (!file_exists($this->path . '/indexing')) { + $createTable = true; + } + + $PDO = new PDO("sqlite:" . $this->path . '/' . self::INDEXING_FILE); + $PDO->setAttribute(PDO::ATTR_ERRMODE, + PDO::ERRMODE_EXCEPTION); + + if ($createTable == true) { + $this->initIndexing($PDO); + } + $this->indexing = $PDO; + unset($PDO); + + $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`"); + $stm->execute(); + $row = $stm->fetch(PDO::FETCH_ASSOC); + if (!isset($row[ 'db' ])) { + $db = 1; + } elseif ($row[ 'db' ] <= 1) { + $db = 1; + } else { + $db = $row[ 'db' ]; + } + + // check file size + + $size = file_exists($this->path . '/db' . $db) ? filesize($this->path . '/db' . $db) : 1; + $size = round($size / 1024 / 1024, 1); + + + if ($size > $this->max_size) { + $db = $db + 1; + } + $this->currentDB = $db; + + } + + // look for keyword + $stm = $this->indexing->prepare("SELECT * FROM `balancing` WHERE `keyword`=:keyword LIMIT 1"); + $stm->execute(array( + ':keyword' => $keyword, + )); + $row = $stm->fetch(PDO::FETCH_ASSOC); + if (isset($row[ 'db' ]) && $row[ 'db' ] != '') { + $db = $row[ 'db' ]; + } else { + /* + * Insert new to Indexing + */ + $db = $this->currentDB; + $stm = $this->indexing->prepare("INSERT INTO `balancing` (`keyword`,`db`) VALUES(:keyword, :db)"); + $stm->execute(array( + ':keyword' => $keyword, + ':db' => $db, + )); + } + + return $db; + } + + /** + * @param $keyword + * @param bool $reset + * @return mixed + */ + public function db($keyword, $reset = false) + { + /** + * Default is fastcache + */ + $instant = $this->indexing($keyword); + + /** + * init instant + */ + if (!isset($this->instant[ $instant ])) { + // check DB Files ready or not + $createTable = false; + if (!file_exists($this->path . '/db' . $instant) || $reset == true) { + $createTable = true; + } + $PDO = new PDO('sqlite:' . $this->path . '/db' . $instant); + $PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + if ($createTable == true) { + $this->initDB($PDO); + } + + $this->instant[ $instant ] = $PDO; + unset($PDO); + + } + + return $this->instant[ $instant ]; + } + + /** + * @return bool + */ + public function checkdriver() + { + if (extension_loaded('pdo_sqlite') && is_writable($this->getPath())) { + return true; + } + $this->fallback = true; + return false; + } + + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set( + $keyword, + $value = '', + $time = 300, + $option = array() + ) { + $skipExisting = isset($option[ 'skipExisting' ]) ? $option[ 'skipExisting' ] : false; + $toWrite = true; + + // check in cache first + $in_cache = $this->get($keyword, $option); + + if ($skipExisting == true) { + if ($in_cache == null) { + $toWrite = true; + } else { + $toWrite = false; + } + } + + if ($toWrite == true) { + try { + $stm = $this->db($keyword) + ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)"); + $stm->execute(array( + ':keyword' => $keyword, + ':object' => $this->encode($value), + ':exp' => time() + (int)$time, + )); + + return true; + } catch (\PDOException $e) { + + try { + $stm = $this->db($keyword, true) + ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)"); + $stm->execute(array( + ':keyword' => $keyword, + ':object' => $this->encode($value), + ':exp' => time() + (int)$time, + )); + } catch (PDOException $e) { + return false; + } + } + } + return false; + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + // return null if no caching + // return value if in caching + try { + $stm = $this->db($keyword) + ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1"); + $stm->execute(array( + ':keyword' => $keyword, + )); + $row = $stm->fetch(PDO::FETCH_ASSOC); + + } catch (PDOException $e) { + try { + $stm = $this->db($keyword, true) + ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1"); + $stm->execute(array( + ':keyword' => $keyword, + )); + $row = $stm->fetch(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + return null; + } + + } + + if ($this->isExpired($row)) { + $this->deleteRow($row); + return null; + } + + if (isset($row[ 'id' ])) { + $data = $this->decode($row[ 'object' ]); + return $data; + } + + return null; + } + + /** + * @param $row + * @return bool + */ + public function isExpired($row) + { + if (isset($row[ 'exp' ]) && time() >= $row[ 'exp' ]) { + return true; + } + + return false; + } + + /** + * @param $row + * @return bool + */ + public function deleteRow($row) + { + try { + $stm = $this->db($row[ 'keyword' ]) + ->prepare("DELETE FROM `caching` WHERE (`id`=:id) OR (`exp` <= :U) "); + $stm->execute(array( + ':id' => $row[ 'id' ], + ':U' => time(), + )); + } catch (PDOException $e) { + return false; + } + } + + /** + * @param $keyword + * @param array $option + * @return bool + */ + public function driver_delete($keyword, $option = array()) + { + try { + $stm = $this->db($keyword) + ->prepare("DELETE FROM `caching` WHERE (`keyword`=:keyword) OR (`exp` <= :U)"); + $stm->execute(array( + ':keyword' => $keyword, + ':U' => time(), + )); + } catch (PDOException $e) { + return false; + } + } + + /** + * Return total cache size + auto removed expired entries + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => '', + ); + $total = 0; + $optimized = 0; + + $dir = opendir($this->path); + while ($file = readdir($dir)) { + if ($file != '.' && $file != '..') { + $file_path = $this->path . "/" . $file; + $size = filesize($file_path); + $total = $total + $size; + + try { + $PDO = new PDO("sqlite:" . $file_path); + $PDO->setAttribute(PDO::ATTR_ERRMODE, + PDO::ERRMODE_EXCEPTION); + + $stm = $PDO->prepare("DELETE FROM `caching` WHERE `exp` <= :U"); + $stm->execute(array( + ':U' => date('U'), + )); + + $PDO->exec('VACUUM;'); + $size = filesize($file_path); + $optimized = $optimized + $size; + } catch (PDOException $e) { + $size = 0; + $optimized = 0; + } + + + } + } + $res[ 'size' ] = $optimized; + $res[ 'info' ] = array( + 'total before removing expired entries [bytes]' => $total, + 'optimized after removing expired entries [bytes]' => $optimized, + ); + + return $res; + } + + /** + * @param array $option + * @return void + */ + public function driver_clean($option = array()) + { + // close connection + $this->instant = array(); + $this->indexing = null; + + // delete everything before reset indexing + $dir = opendir($this->path); + while ($file = readdir($dir)) { + if ($file != '.' && $file != '..') { + unlink($this->path . '/' . $file); + } + } + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + try { + $stm = $this->db($keyword) + ->prepare("SELECT COUNT(`id`) as `total` FROM `caching` WHERE `keyword`=:keyword"); + $stm->execute(array( + ':keyword' => $keyword, + )); + $data = $stm->fetch(PDO::FETCH_ASSOC); + if ($data[ 'total' ] >= 1) { + return true; + } else { + return false; + } + } catch (PDOException $e) { + return false; + } + } +} diff --git a/lib/phpfastcache/phpFastCache/Drivers/ssdb.php b/lib/phpfastcache/phpFastCache/Drivers/ssdb.php new file mode 100644 index 0000000..84830cc --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/ssdb.php @@ -0,0 +1,191 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; + +/** + * Class ssdb + * @package phpFastCache\Drivers + */ +class ssdb extends DriverAbstract +{ + + /** + * @var bool + */ + private $checked_ssdb = false; + + /** + * phpFastCache_ssdb constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + } + + /** + * @return bool + */ + public function checkdriver() + { + // Check memcache + $this->required_extension('SSDB'); + if (class_exists('SimpleSSDB')) { + return true; + } + $this->fallback = true; + return false; + } + + + /** + * @return bool + */ + public function connectServer() + { + + $server = isset($this->config[ 'ssdb' ]) ? $this->config[ 'ssdb' ] : array( + 'host' => "127.0.0.1", + 'port' => 8888, + 'password' => '', + 'timeout' => 2000, + ); + + if ($this->checked_ssdb === false) { + $host = $server[ 'host' ]; + $port = isset($server[ 'port' ]) ? (int)$server[ 'port' ] : 8888; + $password = isset($server[ 'password' ]) ? $server[ 'password' ] : ''; + $timeout = !empty($server[ 'timeout' ]) ? (int)$server[ 'timeout' ] : 2000; + $this->instant = new \SimpleSSDB($host, $port, $timeout); + if (!empty($password)) { + $this->instant->auth($password); + } + $this->checked_ssdb = true; + if (!$this->instant) { + $this->fallback = true; + return false; + } else { + return true; + } + } + + return true; + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set($keyword, $value = '', $time = 300, $option = array()) + { + if ($this->connectServer()) { + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + $x = $this->instant->get($keyword); + if ($x === false) { + return false; + } elseif (!is_null($x)) { + return true; + } + } + $value = $this->encode($value); + return $this->instant->setx($keyword, $value, $time); + } else { + return $this->backup()->set($keyword, $value, $time, $option); + } + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + if ($this->connectServer()) { + // return null if no caching + // return value if in caching' + $x = $this->instant->get($keyword); + if ($x == false) { + return null; + } else { + return $this->decode($x); + } + } else { + $this->backup()->get($keyword, $option); + } + } + + /** + * @param $keyword + * @param array $option + */ + public function driver_delete($keyword, $option = array()) + { + if ($this->connectServer()) { + $this->instant->del($keyword); + } + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + if ($this->connectServer()) { + $res = array( + 'info' => '', + 'size' => $this->instant->dbsize(), + 'data' => $this->instant->info(), + ); + + return $res; + } + + return array(); + } + + /** + * @param array $option + * @return bool + */ + public function driver_clean($option = array()) + { + //Is not supported, only support command line operations + return false; + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + if ($this->connectServer()) { + $x = $this->instant->exists($keyword); + return !($x == null); + } else { + return $this->backup()->isExisting($keyword); + } + } +} diff --git a/lib/phpfastcache/phpFastCache/Drivers/wincache.php b/lib/phpfastcache/phpFastCache/Drivers/wincache.php new file mode 100644 index 0000000..44a6204 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/wincache.php @@ -0,0 +1,133 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; + +/** + * Class wincache + * @package phpFastCache\Drivers + */ +class wincache extends DriverAbstract +{ + + /** + * phpFastCache_wincache constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + + } + + /** + * @return bool + */ + public function checkdriver() + { + if (extension_loaded('wincache') && function_exists('wincache_ucache_set')) { + return true; + } + $this->fallback = true; + return false; + } + + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set($keyword, $value = "", $time = 300, $option = array()) + { + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + return wincache_ucache_add($keyword, $value, $time); + } else { + return wincache_ucache_set($keyword, $value, $time); + } + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + // return null if no caching + // return value if in caching + + $x = wincache_ucache_get($keyword, $suc); + + if ($suc == false) { + return null; + } else { + return $x; + } + } + + /** + * @param $keyword + * @param array $option + * @return bool + */ + public function driver_delete($keyword, $option = array()) + { + return wincache_ucache_delete($keyword); + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => wincache_scache_info(), + ); + return $res; + } + + /** + * @param array $option + * @return bool + */ + public function driver_clean($option = array()) + { + wincache_ucache_clear(); + return true; + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + if (wincache_ucache_exists($keyword)) { + return true; + } else { + return false; + } + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Drivers/xcache.php b/lib/phpfastcache/phpFastCache/Drivers/xcache.php new file mode 100644 index 0000000..3b2a675 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Drivers/xcache.php @@ -0,0 +1,141 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Drivers; + +use phpFastCache\Core\DriverAbstract; +use Exception; + +/** + * Class xcache + * @package phpFastCache\Drivers + */ +class xcache extends DriverAbstract +{ + + /** + * phpFastCache_xcache constructor. + * @param array $config + */ + public function __construct($config = array()) + { + $this->setup($config); + if (!$this->checkdriver() && !isset($config[ 'skipError' ])) { + $this->fallback = true; + } + + } + + /** + * @return bool + */ + public function checkdriver() + { + // Check xcache + if (extension_loaded('xcache') && function_exists('xcache_get')) { + return true; + } + $this->fallback = true; + return false; + + } + + /** + * @param $keyword + * @param string $value + * @param int $time + * @param array $option + * @return bool + */ + public function driver_set($keyword, $value = "", $time = 300, $option = array()) + { + + if (isset($option[ 'skipExisting' ]) && $option[ 'skipExisting' ] == true) { + if (!$this->isExisting($keyword)) { + return xcache_set($keyword, serialize($value), $time); + } + } else { + return xcache_set($keyword, serialize($value), $time); + } + return false; + } + + /** + * @param $keyword + * @param array $option + * @return mixed|null + */ + public function driver_get($keyword, $option = array()) + { + // return null if no caching + // return value if in caching + $data = unserialize(xcache_get($keyword)); + if ($data === false || $data == '') { + return null; + } + return $data; + } + + /** + * @param $keyword + * @param array $option + * @return bool + */ + public function driver_delete($keyword, $option = array()) + { + return xcache_unset($keyword); + } + + /** + * @param array $option + * @return array + */ + public function driver_stats($option = array()) + { + $res = array( + 'info' => '', + 'size' => '', + 'data' => '', + ); + + try { + $res[ 'data' ] = xcache_list(XC_TYPE_VAR, 100); + } catch (Exception $e) { + $res[ 'data' ] = array(); + } + return $res; + } + + /** + * @param array $option + * @return bool + */ + public function driver_clean($option = array()) + { + $cnt = xcache_count(XC_TYPE_VAR); + for ($i = 0; $i < $cnt; $i++) { + xcache_clear_cache(XC_TYPE_VAR, $i); + } + return true; + } + + /** + * @param $keyword + * @return bool + */ + public function driver_isExisting($keyword) + { + return xcache_isset($keyword); + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheCoreException.php b/lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheCoreException.php new file mode 100644 index 0000000..6314b6e --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheCoreException.php @@ -0,0 +1,26 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Exceptions; + +use \Exception; + +/** + * Class phpFastCacheCoreException + * @package phpFastCache\Exceptions + */ +class phpFastCacheCoreException extends Exception +{ + +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheDriverException.php b/lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheDriverException.php new file mode 100644 index 0000000..5f44b4a --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Exceptions/phpFastCacheDriverException.php @@ -0,0 +1,26 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Exceptions; + +use \Exception; + +/** + * Class phpFastCacheDriverException + * @package phpFastCache\Exceptions + */ +class phpFastCacheDriverException extends Exception +{ + +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Plugins/CronClearFiles.php b/lib/phpfastcache/phpFastCache/Plugins/CronClearFiles.php new file mode 100644 index 0000000..11d80eb --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Plugins/CronClearFiles.php @@ -0,0 +1,33 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\plugins; + +use phpFastCache\CacheManager; + +// Setup your cronjob to run this file every +// 30 mins - 60 mins to help clean up +// the expired files faster + +require_once (__DIR__ . "/../phpFastCache.php"); + +$setup = array( + "path" => "/your_path/to_clean/" +); + +$cache = CacheManager::Files($setup); + +// clean up expired files cache every hour +// For now only "files" drivers is supported +$cache->autoCleanExpired(3600*1); \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Util/Languages.php b/lib/phpfastcache/phpFastCache/Util/Languages.php new file mode 100644 index 0000000..484c590 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Util/Languages.php @@ -0,0 +1,47 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +namespace phpFastCache\Util; +use phpFastCache\Exceptions\phpFastCacheCoreException; + + +/** + * Class Languages + * @author Khoa Bui (khoaofgod) http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ +class Languages +{ + public static function setEncoding($encoding = 'UTF-8', $language = null) + { + if ($language === null || !in_array($language, array('uni', 'Japanese', 'ja', 'English', 'en'), true)) { + $language = 'uni'; + } + switch(strtoupper($encoding)) + { + case 'UTF-8': + if(extension_loaded("mbstring")) { + mb_internal_encoding($encoding); + mb_http_output($encoding); + mb_http_input($encoding); + mb_language($language); + mb_regex_encoding($encoding); + } else { + throw new phpFastCacheCoreException("MB String need to be installed for Unicode Encoding"); + } + break; + } + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/Util/Legacy.php b/lib/phpfastcache/phpFastCache/Util/Legacy.php new file mode 100644 index 0000000..a1c58f0 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/Util/Legacy.php @@ -0,0 +1,43 @@ +".$tmp[0]." = ".$path." BY {$allowed_path}"; + self::$stores[$index] = true; + return true; + } + } + self::$stores[$index] = false; + } else { + return self::$stores[$index]; + } + return false; + } + return true; + } +} \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/index.html b/lib/phpfastcache/phpFastCache/index.html new file mode 100644 index 0000000..4304a89 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/index.html @@ -0,0 +1 @@ +Visit www.phpfastcache.com \ No newline at end of file diff --git a/lib/phpfastcache/phpFastCache/phpFastCache.php b/lib/phpfastcache/phpFastCache/phpFastCache.php new file mode 100644 index 0000000..db1f066 --- /dev/null +++ b/lib/phpfastcache/phpFastCache/phpFastCache.php @@ -0,0 +1,55 @@ + http://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +use phpFastCache\CacheManager; +define('PHP_EXT', substr(strrchr(__FILE__, '.'), 1)); + +if(!defined("PHPFASTCACHE_LEGACY")) { + /** + * Register Autoload + */ + spl_autoload_register(function ($entity) { + // Explode is faster than substr & strstr also more control + $module = explode('\\',$entity,2); + if ($module[0] !== 'phpFastCache') { + /** + * Not a part of phpFastCache file + * then we return here. + */ + return; + } + + $entity = str_replace('\\', '/', $module[1]); + $path = __DIR__ . '/' . $entity . '.' . PHP_EXT; + if (is_readable($path)) { + require_once $path; + } + }); + +} else { + require_once __DIR__.'/Util/Legacy.php'; +} + +/** + * phpFastCache() Full alias + * @param string $storage + * @param array $config + * @return mixed + */ +if (!function_exists("phpFastCache")) { + function phpFastCache($storage = 'auto', $config = array()) + { + return CacheManager::getInstance($storage, $config); + } +} diff --git a/lib/ts3phpframework/LICENSE b/lib/ts3phpframework/LICENSE new file mode 100644 index 0000000..70566f2 --- /dev/null +++ b/lib/ts3phpframework/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/lib/ts3phpframework/images/flags/ad.png b/lib/ts3phpframework/images/flags/ad.png new file mode 100644 index 0000000000000000000000000000000000000000..625ca84f9ec596848d4b967b5556fda897ca7183 GIT binary patch literal 643 zcmV-}0(||6P)S}{rU-#^xW|9%5S{`_G8k=zVG=|5luWB>#JF#yj01oZa& zTu&JQ008~}|NQ&{0055x0sR95`v3p?1_t~7{`v!N`v3d-{`&g=`T6amqXIBA$0h)T zKokWRkpoMx|Go@O4FvhR>O1p+i7`B6t^3)y2dJ<#?4I?d4x-E}Az98Z`2`TmzkmP! z{`(J9{pauh-+zJD{JOYLhW+QSzkh#x`Y&wke*WLjpZ~u9`2XuCr@{^OJ|MkZb_UGR@Z=V>fB*iaq<8P{*B}2u-uMnu#J~U$KrBDL0bT$1_irEqiZcM6WEF7g<|*+% zzkjHi-TwRc=f7_t9|K*?`1?07lmG&V<)V$@{}>pUYZ(~+fDw@M3(WY1!T<=sp}f-o0K!lJ3<>Jc!&VtMslE3ph2^H3FGe(F z4|^}slF1@l1Nxc}^5hjjU=0la|37)k@b@neJ^1|l@87?_{{z(l6@kcKe}Db@_Y267 z<&g#201!YdV6*>2R0EZ*bYA%%2Vi7me5m>mAb?naPGtnD20MY__n-f`0mzg8{s9CK z*hzmtW)s%H$oS{aAAkS?JLxaT`2W9u;n%DD1Q-B%#%TuGpFSS|0000&5HW|5#c7{r(N){s9YCi84$~U|{$Glm>AD0*K|^yLU}ZO+eP) ze;~;4`#0mCzs$dX{r~-&?f370zkUTM{{OB1_1E8DKmYz*JbN)f0I>i8Q1#1Kum1f0 z{pZggpt}FRe*ORX>;JEx|9}4c_w(m__W!pfKK=atLxM{ZXbC_7vHbh@@9*EgK-ECy zzkh)Y{?EYhFEQc&-#`EN9{7Lq7*OE*@9)2U|NQmmC(vO40VtezBLF}U2%?OI(TG)& zJC>!~;SYmN-)zb6gc(7A-^Q(#z0vHb9!CJ#T#ii{@&pjek8j`pfX)5|H00+GpnLvt za{MtfWBC4qk%R63j~{=2{QLgr`wyTb*amx2hce{;{QL3a|BhWh#6A5bKmf5of*%NgdVm^$Zuo!gI_LQd|G=96g51yWl0Agf71rLE? q3XWqq@&Lw?Iyk0*94-cc00RK~MQKxxUU$F%0000= z`}|7!`YQYT5C8xIh~*yx1DgQ1_jkSr_QgN{Gn)!7=zkRT=l(yS{y%^Jd$>%yrP!)vVq~xpZ~xA{{HjZ>hB|)Z~OQoBLBm{yCqA0{k;C`&+mV~|1$jj15^wU zKukakbkF}kzkmk({q_6zmUTZaUWBOraqa5!?(V-pgMR}R{04?0&`AIR#PSE^hrdi> z;*7t4gLVFSJ$J5DTibsorq@%ai5h7B2l^1G@*gM=fUW@uAjYc<48V{DX@ijvE(11_ dg@FMezyQzuBHsGv+i3s*002ovPDHLkV1jTADB=JB literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ai.png b/lib/ts3phpframework/images/flags/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..74ed29d92616c86757d3c0ec04378301c8f591b4 GIT binary patch literal 643 zcmV-}0(||6P)ocPy#thD++(+#W%SS0Qvp^`1%0+_yGC)0QUL-|M&m^ z`T_tk0M7pe&iDc7fh@K74M*k&0{8f*88BG;;GY6ve?;&4{{Q>_{{8&^`u+g@|Ns5} z|C?Nv0*Gb#mu+n4z6ZW~#qjy_|6ji&ZeIEMk16oyzH)i@Pk$MNxLARR?a#kwpMS72 zF)}cG2M8dRUN*~81wCz%$A2$f`_IL|A|w6j^RHtc7{2`lYG-KkQ{(*knSqh%3kzr7 zs%s1kKL7#%F#yj00-^B^D;pD^^AqC)4C(?1m>@q26ceiU4*2{4`u_d~@vQ*ykp&A1 z3h$o${r~^~`vQo?Pm-bY@cm79zVQG5%l!B6)Riap%L`bGemnT<@2|iAfl64!%YWS~ z`p0YY_dig_cYpw5WYAmG*lPLk!;jy8|NZ#~MF0Q%{rCG1ko^Di`(Fhkd3F{?py0c9Vg{{gA~^#>F<|Nj5KapBR^@Bf~CXSi|t;lF=C z8-4)<5DUX^V9+rDCI3RuUy#}V{s46X!}R~}@4ug&fA|+@;n!b(1zCZ900KKMX({{sIILBZE*UQbc|SngJt$+`m9Kz!<;5 dBm+Qz0RWL%NC2%sdjkLf002ovPDHLkV1lARHI@JX literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/al.png b/lib/ts3phpframework/images/flags/al.png new file mode 100644 index 0000000000000000000000000000000000000000..92354cb6e257be2cade71cb825027ce8d9efc06d GIT binary patch literal 600 zcmV-e0;m0nP)PbXFRCwBA zyv)hK@{fUmi2(>${xJOc&A`II@PmN?BKC`c;SU)710$d~Kmf4-r6niy16BY3`}g0! ze=L9h&Uybegp-Ns@87v^zGQMU|NZla@z1Y+zkdVKmFbHB0*HkH4F0@)&iwE1;&GRLuzkX(OGXMMa``@o$|Ni_2x(y(Jn3#TnHT?el|IhC;U%pHK`}6Df z@2@|9J^lXU=Z~KvfBzi*^!4A*U;lv`e*Fge3Lt=(82$q_`~m9z|ND;$!(WEqze~6o ze}X|JFO$H(U+VvX#{c{avi3L7@c;n?3L&6l7=Hix`}emN<6i~#|7+iUWBL8-&##}m zKKx5zXJr5LA80nvkwCM6egFs{po9Y?& zL6B;olm7ns#=rm&Kp-3b{RIXY(1{??Ha=$NKfkS+|9@us$Mfg+93j@fzkY#y2y!;i z&kO(o1PWH5>zPHwn7|>;0Mz;WPl13iP!Y(WKYy4&4hBXMSPugOKmaiw2gk!dq!|4J mj$lZE0R~eis zC&uN{cDQ8`!@ZNLOqwtoG6nhzPx}SV-d6yzFfcIz75{(rngJyDkKrFw8iaoThHCr$ zo8k9wMhS^O3=9AP!~%5B|9}4(ASQtkLN$m1CV&5705X362095KfLKJhW;OjWdGY7l zzrX*$DuLu*pcFC#NdEr)SL!$SY=)lz0mSm_*B_u)|AM4JP)(}33zy1Lc&_o7? zKL7y)bkd){fBym<_v`mxpbeRs`+WRVot-5A{r$IX$CJFg13u{3eX_{0R++j zR}GCkSy{&4zk!krKoTg(2n-k?g31B}5JW!NMq0d0000q< zM7LZb@rX?|r)2RP-+aO3(7q?c3+$-Vv0E)PxM3SDV%@s`#GkZvw_x^WBa~uq3^l}t zWdf(j(=(>^SgETc#5#EZT(4ObRkfxbzP9G;yza0;Ygc8-_*?EP(ca#`l6-Z6D0{tL zQ4|~MCSi!9Q9YkW=V$ix#EqZ!rc?eWA0TwdVID+3aqrYUXUhCI)Ad5!(cP!BhhW$Ayb2&r8kK!bz*2`~UE W(Ndrek0Jg50000NL7TW{{R0! zJP>i}(MpzaSkj z=eqvqa`^|6{{QbQRQ11q3~Vg_kG}Z}5I`(hw}ncX8D4$|h64jUK>or*=^r$dfI%n0 z$3MKz;!F0w91GjkkV%_X`@4 wKR^hm`X5LwD9*v?4;TUC6Bz$&3;+QJ0QLcJ=WT{TU;qFB07*qoM6N<$f>3bX%K!iX literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/as.png b/lib/ts3phpframework/images/flags/as.png new file mode 100644 index 0000000000000000000000000000000000000000..32f30e4ce4eedd22d4f09c4f3a46c52dd064f113 GIT binary patch literal 647 zcmV;20(kw2P);Rmc0RI30 z|Nj5|{Q>>`0R8>{{QLs@`vM01^D6+%Ap+Y10Qw3Sr3ED)s=c}b05Jg0{{#dC0RR60 z)z$y}`~dv>0Q&p?0Q>ne0Khr|#}EMN1`-ttJvaFq6VTDg`OMJS*4F}vg#m0fQ1$QM z42u6QTCg3Kt8+Li1J#Y_zUd>4Lw{URe1tEjLbKmY(S0M7pd(g2qsJ0|z;{R`~d z`n<6E*3bb41p*Zr{{R344LkeqyaMyV01O!W0PZ0+TjlEQ0st`p&i?@b0Q{Gi0m{n% z`T6-B``1JQx+4kM0{rp_FfRy7P6Qq%_1|&<`|JPx`Uew8u)+%h2+ zdiAI7xt}_}e|$CmbI9$2>u h*D)|iF)#oG7yty8`q2#I8zBGy002ovPDHLkV1j5VEF}N{ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/at.png b/lib/ts3phpframework/images/flags/at.png new file mode 100644 index 0000000000000000000000000000000000000000..0f15f34f2883c4b4360fc871d7105309f1533282 GIT binary patch literal 403 zcmV;E0c`$>P)@|4`Xj5kLT%`al?B=W5I`&prl$WjwHQRjfmQ&G0jUOA z@&|Dug_Rm`2Y251|$~)1M2@@6mI}!8O6olw6y^Q z5X--d7nzS8`+x5q12kBmVFD!~j6c5_fMKno0(1^Q0I>i=is|LjXX40RRttS6cG0UZ?;7002ovPDHLkV1fxUnjZiF literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/au.png b/lib/ts3phpframework/images/flags/au.png new file mode 100644 index 0000000000000000000000000000000000000000..a01389a745d51e16b01a9dc0a707572564a17625 GIT binary patch literal 673 zcmV;S0$%-zP)>fJ3En$GhGS>sbE%%m3$AD)q?8M9y>88-}kR7#RKlk!P~Y_PLuF7~U~3`~nC7 zF#yj00ZUDdpLsm{7ajP|&HwoK0Usg|6%f4L_{`Mi{rvv-`ukf=Ed&Gs-sA7L!Q7*a zj{*QO0M7pb%?Sw^g@yy{>ihEY{`vU@3=8@G0rvO$i3mOL`~mv-`W+b$Mmr&io5dg< z5v!7q0*L95jt`TzK8Kd(Utv)OSp_aLv){6ccV+Z`{Q2+asKUU&aO3`Kpz6wW8wp`< z28M3{0mSqnB#A*-c*8%1=RD#sSOwMznKA3=e&iEzwo{cA=PgXK`2OQ}gqId83!|%* zA_Kz@fB*n70M7pdECCwp4H&@R`1|(w-}M5x*74i)0}%fAt;XafA{48))#>Z>?CD#}*e}Ret0tl$#*RMZ7}Jl7Z|M45`5*URzH9L z{rmSnPy;{!u>dsyO%meg+00000NkvXX Hu0mjfN{&}S literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/aw.png b/lib/ts3phpframework/images/flags/aw.png new file mode 100644 index 0000000000000000000000000000000000000000..a3579c2d621069c8128d7cf16440d5e45a3ab3cd GIT binary patch literal 524 zcmV+n0`vWeP)^}0h@W9fA9JOn#opKkr#VO<{LPm{QLg}EdH3Vb1Zzpp zqiZ+XNBm%5{`ViKi{bCTe}Dh~2a+HyfB%6Q|9}7eKVjxEfB<6Q<6r}-{{7;G=hB{; zLB+p+|Ni^;|DXT={s75;AoBO$f4@PDf8sps4FCQC1Q64wUw^h*hy4HZXO3U)@85qw zD*pci8U|DWQu_BVi2MUWKoOv;00M{w=;|*lY_o%kegGx^{`&*A{SSok_usF-P)oof z|A86+0*K|;FQAM6fB*FtoEI&bZG5mq328#as{}<$F zWDQ{Ffz1MH001phxCBNdC_2H6U$AI~GQbEJqg*Ti0R{kQ(yH?TRrCY^ O0000^@RCwBA zRL|V|7QjV)*ul;Rlceqc6W0{`~_8AeMg& z|LUXE8U8c=`}dziv6P|mEu%JjM}pqh@65md|Ni~w->=_)fBgZXe?Nc!ot?WGAOHX{ z0M7pe`uG9>{{ZXo`u+d?{`u+u=j#9V2j%Sc^797z|N8p+{rLO+`TP9&`u!FW2mk>3 z0#G;xDFDDQ2vg|)-@5Zw?JVTrAxNs~5&|AZ;5uWfQ`vUsMGm`>aGWoI7=P|oW>%~E z|Kk8dq@aG@HzdsB>fj@shYzX-a%=q<(frSYmfLIv5{HbzQ${~M_P_y6C&m>3uU0*DFd0+8#0#Gk)^|NLQBRAyCBeB9H+Wn{=Mr@*PJ_ZKV< zB>w@GFfafF5Ks>TBP0ZV{bBt5_ve*sKkwd`OiO#eX6=u=cfX!E#rX3NBT(umNC7Yu z0Ro8S7X#4ce?Xr63$phg)Bk64X8r&5``_O`AFf^jc>p5y?+^38KVKLa00M}`wU!~x z=I_1Fe}Df4D*yZI@1I|PfBgnRptt`0`2{iqMpRNPo|?TW>MPOFW(r1_!xlT-yep5|G*@e%^SFxf#DC32@n7=0M7pe00011 zNe=7o{FIuhzQ(sjF~t7>`T+s_0sj00{`>*}`~Uy^|Ni?^Z7Tcx|FW0O0tlpm;s5{T z+df{s^E1rDZN;v4VP>0|8GkY`{`&t1s2YfV{r~st=KUYCl59Xr00M{!Xv@F<41fPH zoWA^R$>xvtF5yd$xc~X{7o-}f=ig7DY9RXc``_>1K-c{N2q2&ahQEIq{`~z1RCDs; zw*~7zIJ!sAKj8J}&!7K)enC|K{|nUc|Mwq|27mwp*#K1f8;Jh=2byv8+}GKw-@AGz z%-rMu^XK3HKtBLA{Qvdq*I%FsKn(x^1k&&qY(LP_Uw?q)|3jz0PF?=a(?4bU?qHzN z5I+E=z&88?2q2IKpz*){`~&I%+VJc5-=Dw#?LYB#!lHLRL7BZ8-NZ5x*TXaKmf6TZ20qEQj`^F;~x-W z05U+*fBxNj@_W(7kJ(ujGnWgh$g%(Z2hso#K#U9wI~hQ+`3Dk_7z`j1tL#*2FTcgW f@C+EF009O7>dRn2w6d?H00000NkvXXu0mjfueTzu literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bb.png b/lib/ts3phpframework/images/flags/bb.png new file mode 100644 index 0000000000000000000000000000000000000000..0df19c71d20d7fdc06e1cba01028983439b2bdae GIT binary patch literal 585 zcmV-P0=E5$P) zj51&U|NhG$_=oZTA7 z0mJ{l|Ns2{|Nr-Y#^3*c{=WU_!>_-;fvROCxPdkR1P~M0Xa=C_KR`47{sXG+pY!Yf zqu=)*{%)W0>;M0szkdJy_507SKYxDz`3KYh5I`&-cY{^``2$3Z|NsB`^ZWO@y}$qd z{r%_nFQBC$4Is{6hz5WF0@(mI8^j0N`~Tl>LzQ1Ye}a&q>MtPo*RQ`|OMu4x1!@2Y zAfSdne}TsT{`c$8-(P?J0c~)w`~?J`zx?#Dg*yp^z&88=2q2IKpt(TRKniTbum8-y z1bBWi0e#F0wgIH{FVJ|Pxj-8L0tgsNfByUdJMs7b-@h3^8h-!E3i%CU{QCW$1*GB6 ze~`2PfHeF82q2&aU>N+96yg32bmdP5238q{|Gydk0=52OVEN6!@<&OA7Z~7wL16>b z@DCt>7#SFn85mf=5eY;LZ#OUe_l@D-2Zn#Y82<4v{9|E|jkRQ8_`$&N0jL_N03g5s XX0T^_9W~6o00000NkvXXu0mjfXpthO literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bd.png b/lib/ts3phpframework/images/flags/bd.png new file mode 100644 index 0000000000000000000000000000000000000000..076a8bf87c0cedcce47099c6b74b59f2c9d1dbce GIT binary patch literal 504 zcmVCcfPV}OzZe+)fYC1)`hmpw%>WR9L@~Dk00x0r!@5ksR;+hV0>ZLfIcBS@orh;x zv}95za5W)x_7^}bV3YqpdH?h;NZnuSC%?V#{+0gl`~RQckJx{&l>NO~;@7XgzkdJz zEh{VwR0LSuiyWGR{aKQ z00-R5+27mwp+3@G@Um)kt zzdstU{yWl9SIB_0ft3II3)BD*KrA2|{``^Tm;4PDy`=a1tTre>fEI#8 u{{2z<2NVVaum*qtVq}m+iAaI~Aiw~?reC_kVQV=60000gsB+3J~Dn`pdxgmx19A{hgns>oXkhsL8>j&wfIu2Rsv+R_ zAB5T1GyoL?1Q1BWpFe+p|Nf1n;TJ-~Kd@?uhChFRHUI<=#0HS!U%!8$YJib28yLY( z0tg_G2B7i3e*FSj@aq@44gY_`jRywQ69xu=0D?FP=vyd&x*M*V6|CV8P{Uu4OBfgc z0tjNmA4y3`us8lPG6H?}8%zRafgb(Cp!5f*6oP{xAq} rF#LPR@IoIX4%E%Yz;K6w0U*EtuUcjA`-_J300000NkvXXu0mjf+>pV; literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bf.png b/lib/ts3phpframework/images/flags/bf.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5ce8fe1237a18d6809a5570024eb108cb14a3e GIT binary patch literal 497 zcmV@|4`Xj5kLT%`al?B=W5I`(ov;U*021-Lr1*!W5(gW7O z@cTDV13&<=05Ky_HBiN$KMcQt819e>gHP`g_|Np;&&;0(C^$RHS>olZ*Q41X9v27}ar6#W9}0h{^{OajIK0?h(y z_zOe~zZd`lhy{p$|NA4!EeST}?;nUxkP49VfMx;1;V%&U|N9rH;SU2q05LM8GXV8K n(H~UyA0);fa6B_H00bBSQ>$p&WfB*UmB!B(-`wPhZ^#?@C^U5EbbPyl_g=3Hc01Sh${{P9=HB%4| z34*<3-m=C?^;L%miR{xQv2*hU;8+Y&0Dxf-W~KjsusWFAC4@j0#9_j;X5z6SjRhH> zd}sd(7FPhVFdSi!*Zj@;_Sc`kfByUdk|3A-`STmZ_yb~qxIp9o{E-p)bLt=iKmf7) z+|Ix(`{&<1pm88mK&}8g1WG~}e||Ik`OB#Cw~m1UAb?na(Zlrn4}+v6P!1@{@CT$5 z&-#|8y{IY2iKmf5omHz(^qAp)ym^JhN+jk7Vet{H$(Z8=>|AV=V zva&!M00M{!p&AHub^kXuF^G#pRQ&(-8^nMkpk@XJfB<6o_wOIv5C1P;X6Wwu|KUB< zc%X*g2-OTg#J~U$KumBafq;(A|A`a+OH08N{X($;=pP0KfB<5Fs|GR7oM!0i{{QYB zSm|$=!=N@Wf}I2qKuka<{f7YtBjf)QCj1u{`~UM7nrdKh0c`*VF+c!8{Q&j%sZ;-Z zd;dRshENS5{{S`og~Sg)05QIL^-54s5b7kL(8GuS8yfz9{>%Ui`+q-vfW*P*^_43C z0mK4S`s2qBh&}(NP5W0_$$%QQ@R)`GfB*vkck5Jby^kNv00000NkvXXu0mjfy0pt~ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bi.png b/lib/ts3phpframework/images/flags/bi.png new file mode 100644 index 0000000000000000000000000000000000000000..5cc2e30cfc47452d5bef949628e955a522d59e50 GIT binary patch literal 675 zcmV;U0$lxxP)$wbBBJ zMTk#;k4<3e#ePFE;{?TciOx=-NpIdg`t|#dcz^$YFqm+of7AU<7UGr+0t^5F#PsJM z(2)${Jd)SmT+KY5@!|h>po)L5-!r~>3uJux|0`&Jz~QGy6a*CiGyVtq1|Wc#zWw|0 z_y6C2|NnAvaR2)A>-NX%|3Ci{R?!hvH~jzU``ypCK7Rkq&&~h)FDNvCApj5nF#yj0 z0_X_*{{Q{_|NQXv@Av-qBMmR$teo!O*6-%u-J+8r3n~2s`s?uP{Qmp>{`~+900M{! zNC9;}eE(3IS9-C{62AZc<>Jx>%af}0@?r&05Jg0 z{{#T;0Os`NWh`ko4>SM%|Kw(60099Kad7|t0qcf^`tR-^sIP`Fhru7lkI#w#>Hq?W zkzqQ+@08y_|9v}t;{CEE%)%nTc=-MC@P)7S@!~g$((Ud|+28Q224FCZIGXF0FQ1zcbfB*dX_lJRr z;s3wCNa|2D02KoSAbE^!0Dxf-mZ69ApkCb5fP^9ydGO$cm6w`kGj(t#`M{tFlLo%j z*4%mm2&CaJSn02SzkrtfWBLcO;r%bLy5Gnoqrh)qC;lgG%);U`26kn-@hOg zU%!6+4+cOs(0HIde9xZz`}Onxub&LUB0x(30+2WcIRJn#2ut|?gWYu1Cf+!-K%B8# zdf?1WA}#uZ8oj7u>$I1i0Al&`=O0k%-@icgAIJnM0xA6maSq6BK-ECw|NZ*S`0Lj% z1_pot6puj;05Ax`F!=umqj7^frO?t|3^&I1kxUq9yECc+jQpY84SWH_0#pxl$?v~F z@*hy-KN0|X07U)z`4{NpU%#2aHUI<=%a31wK(7Du52Oc(|3O^?R1IN+RRjI-n*kVB z3=9AP#PZ|EACPLGJ%9cJNh|>9B%spYzZw7h1%?tp0I_@ndg9MNE>313@6R75NcceF zkr51-#U+7;F#`Sf7i0rK0I_`g_NQ&ZVKmZT`F#yj00W%wG zw4zx17##T6*Z=hN0Tvey7!|wG?K;V)0{r{_`}_X<{Qmp<|NQ*_`}@YSvI2-@&cAgG z*R?p0?_>V(@&EVlEP?{x|1esA`||VNU68t8zyJRJ_3zg&hTp&0WMzR?0R#}s1|E|% zRV}tRkH6o2#3;(nsVMXI-pgBmh17ms`TzTu!pfBl%*_9R;OEbWDJei#0R#}sGpDd$ z96Spi@~bn7G5z{~^_!ZLy2#s;5B~o8&G`G*|6f0Ap0WS=_rFd2C(ubi4FCZEF#yj0 z1a!1=2e0}MwgDi3SU@2S4mePl{012eQ4J-3 z|Mm`933BS6zyJRJ1;ztF0D(08|NVzmQWB*4&)@%$um=Y`lmy1W-@hDx{{qQh3=9AP x#JH7#;TJ3-|G=X54_FW_j)5c_0|P*S0RT0dOSD~(4;cUe002ovPDHLkV1hfJDkcB` literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bn.png b/lib/ts3phpframework/images/flags/bn.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb09849e9b5712e9cdd8a2c25035da201535cf5 GIT binary patch literal 639 zcmV-_0)YLAP)8n_8%|dG+hlCr-g% z|9<~wU<9iE{r}%DMphZ1Jpcj3%*DZBWo-@72DFWlk(-gj@!ngb-$#D&C_i9e{QvLY zw|{?r|NHlgiHY~?^IHG`05Jg0{{(k=dF}7->+9=cXlNG}79t%T{rC3_t(pA#`tSX# z4jKOh|NsB|;rr`({{a5__y7Wk5omBxQSrlv5B>nb??0VZ8qCZrpS+?}gatn>o%-+B zPl=+||6st##PspQ2Y>)#{Cu5(QJ~_^?faD-pMV;E|7MwLubjQ@B-gLso$mVYgECpU zxpfp2fEt*XnBKpC4-fz`0M7pb@zVeZ8g>5r-~k2r`Sbhd;{WmN02I(80QmHEv)Ik{ z>H7Nm`}_MdF)?FhWi>T50*LYBMFw`s0-y$le?J(QfB$Fr_2c8e-`_!j`0PG|ckZ*_ zzkY$-`sc5Tips@{7Xbo@1sGC5IYwrQfB%2{|MTZR!ygX*-%McG4cZmTY ZzyK0(F$K@T-Dv;-002ovPDHLkV1la=J3;^e literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bo.png b/lib/ts3phpframework/images/flags/bo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce7ba522aa7e948d581478432643c230eed1a658 GIT binary patch literal 500 zcmV^3LvnC{|x`%yaFl$ss8ha0V@6XKS=c-5cwO(_{}IL0ki=i zfLOjo{AE-9^-mn=h(G`Tfz|!{{r?|W+uz?{^yeR#!Nm9NlRN`J0I_^YV&Ikj@%Q)F z|Ns9m{0FN3^Z!3k!*8HnFvf4N5xas50L!z?-wXsAPk7$ ze<1l+iQ((fPYeJ7#KQ200VpKFA^{3Nph^G!{9^b86oDW}(Ek1R7ZiL9e}Ret0*H}e qBRB&8AR!3%7c6cef(%d+Aiw~vBYd|xMihYn0000j-HAXl7XJGjM{~r+i{r~sxzrQwG-&h#_Y&p;L z=ieV7_s^f-zyJOD{rC5(+EoAn1k&)I;s3Kw&;LT?{{2}P{Pq8T&j0^^J4?LvUd;UK z&+k8ffB*XXONL(tXahh1fi?X94^$0Q>Z$uRRO)Y4)uapmB!2(-ul(c1=C+V!kAF`) z$PCo;`_FHns{jIs3Fu;wy-Z(c2YwAz{&V4aXk?bejFO*Op&u`@i~PecqBpL@DIoojb8mb+WHGK%fG2>)a3=iVf_~v+`s?*1;!`?Kmai^I3i*ZIYtpN f{g(k500bBS@Ns{bTs^2gCr95H^s6Fo1}O2_S$Nfl}4;<}o-r{^R8Q_xtzX z|Nnu?|Ni^;2gm@D3=F@4EC#P1& zzuV{ie)#0qf2Lnh)j(~3VeG$ufPMl9Aczeh3x56n#l!R`HRQLfpFjVwfkN;%I39k( zVgne^e?jDThF>oL0*Hk{T6(^^8pGqq44*zhqZ1OZzkZ>_F(mFeH~<0+0A4S6=>Lb* QN&o-=07*qoM6N<$f&=*Yr2qf` literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bt.png b/lib/ts3phpframework/images/flags/bt.png new file mode 100644 index 0000000000000000000000000000000000000000..1d512dfff42db1ea3e7c59fa7dd69319e789ee12 GIT binary patch literal 631 zcmV--0*L*IP)?&DC3JV7l?ccjcgB=Qq4l4w|Q;eOZ4z|IjsEI`#PYSloM|AFHF zfy)2>XZQ=$@t;lU|F1tRzpnmg{PUOLH{-8A|F{4CvUU?d0I>k|0TuuM^_1cNUy$nm zzZn?*{^!*ACzSEy$NyhHzB0Wn`TXRU=!%=n%Ci4h82|!^32gR%glZtk^6USnb00o@ z{`LDW>%U)&BK_|-y-75RkKVi&O=|xC<=6keK+72b0*D2q;Xlai|Ns9mF#h@fM(h;aV>^!x1-zNu02XV-n@ zm3imZ@&EI0hChG)|NZlafdL?Zn1I0yH26P<=DUx29QQsc%}izF;QV-S-hYOl|1V4z zX|a-<=Kf#g*Z)7i{{y|szyJ_HEKEEM|C#>$KmJzoZa)M2-~Wm-e7Cl+vi)L|kY`}} z&A$F20RV*|BkB*O Rz6SsR002ovPDHLkV1m;fPLlut literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/bv.png b/lib/ts3phpframework/images/flags/bv.png new file mode 100644 index 0000000000000000000000000000000000000000..160b6b5b79db15e623fa55e5774e5d160b933180 GIT binary patch literal 512 zcmV+b0{{JqP)O=a{vExMP2%`MCSoB^FIcLe_%lf;|~%E5I`(IQNh}3Ao>6Q|DFUXMn*>AqQd`w z|1kXd^B;tM|Njjl{{hM0zwd6?1Q0+hV1xeud-4=Wy?p-%sO`^#2S61Jzk!N?s)6X& zzhA%p|N6}=D+{y%Ab`Lc{sL9~1=0UN4*CD*7s%9KAf+JHKs~=eB-8KTKvw|-5R1&; zzd&a|ob(5%^Z$Q=wHy9p13+aOpFRNu5F>N&`Tk_-7w>=n{RejQzkfh&Kn{rf10?_b z{tFTZibx5v&dxav5I~H7|Ney-|DWN1$%1FyagzUW0464;_wU~W1Q5$TW@eGxtUvee z3vAf*8|igK9~@*rr66bh|NrkNM8z+V zAV?>O@ek;bKfu6d00k6 zPW&$KB`I@TtA?2x@Q~7pdcWi#1>DDZ2>MuG0I@IumHuaV^&7|soAMvT0IK){RtF@1 zgH;3B;_Qq-34j1%{P^)BFE1~|NkA(gBv!!4$aw$$eSiRB1ga=2D|__l(H|iA4T2Ex z>lc`SQ9x2&UjE?0g8%`<0`lvhzd$}*14J4{IhY2@0~G@V5J82%q9;EBlXu0U&@_82I`BfBC|Yn~P8llm?myQ3_K18=`^X_ivyEfB<6p_xd%%#*GZ! z-C&_V5IGPDK|g;X*gypg3;+Sd!XPTj0Q7fK5>&$Y{sHB| zjK9DB00sa3QR4q|tc?L6fSCSm+3>HW`NxbYpP7GsX8sK%KS9yYPfWi)GJ)AFzdp14 z{>t>1fsFwmfLQ*Ci!yxq#8guv=_mQ;-(Rp{AoSPbXFRCwBA z{KboB21 z`ppP*BG3|`!vO*asNvt=e_;3gWsp&PA@E<2jYs6ilRy6-zmSku5?~OOP=5aV@4sI_ zSN#QP00zQ!JD>SpAKYgR_QStF|9~F;2M|Ck zz%U1e+FyoWe;B_0VVclq;}vp_kNy3hKMWrj7*{kqJbMcaQ;_Pve;I)x1Q0+h46j}> zeiN4E1lkA+>|e|*mzJsW^M3)l^W_^hf&c&c`Mv->`48yHFF-%s0|+3-*GLfwiRNz% m3}3(u4lrUsie?}H2rvLBbQv(L;??y40000e`b1WGV} ze#gbd^&bp=ef-GykAYc$@BhDltSqdeSy?RXJpcawy+5fJAb?mHcsPJ&{d@EF@4HtJ z)qno||1xd*zaM|Ot4f#|8A1AQ-~Pubz$7IBv;-i4SQvovfB*ge{fFVtAE4aNE7yPi z`^Eg=5yP(^Ur(Q5`TLjM%#`8#_rHuB|9}1hItd_vSpNO`#qjs<|KGp=|NIHm@MXqK z79K7}ZvOwQ9Df%s{`~lX*xI%KzW-!k{Lc96Cr|@G0I@Lq`0?-epMSr9|NHeD?&umBG}6iH(DSogJte zDERNk_kV0Zng09%Itd_vSb&=T{rt@+B*66dJ0my{fpNkvBm!jpV_;xnVgd@XDk?Dx zh=IJyzyJ_HjJJUrz)|>#fr0nRl^;KTz#|yK`2G9$BS%LzureS32rvK^u`=B;c)+&+ O0000Vbu`-)NXk-Y`p?2$B#e1 z{(buQ`}e;;zyJNd_UF(2w_nb@VUiUP2igD-Kuin_BL6vl`zgIQV*mR8|8G|2U!T7I zn`-)Ow$bxne}9LGfB5z5*XcJ*K(;aeSD>o^0st`p&i@1e@&c~g4N@7~{}B9V!5sbp z`j|K8<@Nq-&kt=P>@pAZ`T7Q#;RiPl^j;kSgdzX}05Jg0{{#R8=f>y-z~LG0`u_d< z{QLg=2?6_k)DH^)`=sFo8YTUi-v;{r`o8J{6Wj>^)c^vB2^cJY|Nnmc_5b@He}DY@ z_51JdufKoSslWR1>-W9)3`r;XfsXn0`_Fc($H!lT!V@5Xn1Igu{qOIuf50&L{qg7D z?|*(LofMGa_%_k-*{}b_0g?9mMSwvE)Bq4bEIr*1})*zF_+U)&LMdEMOb{Nb-vOh6FrNC(tkd{&4;N%g_D$&)>g> z0^hO}m?z%iIsOJ1mka;_#K>R{i_RZNQ42x8VJSe8f#Ern2@qfa%&RHCyvg>Q00000 LNkvXXu0mjf=TkSf literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/cd.png b/lib/ts3phpframework/images/flags/cd.png new file mode 100644 index 0000000000000000000000000000000000000000..5e489424884d2ec9e429f70d69af00edf242a077 GIT binary patch literal 528 zcmV+r0`L8aP)JkPqeYl28iLgD=0{><0$P44T5yOrT$dE?(KkwMFdoG^-J zGv9P)Kk|i5`lcNgUUAbboca5{hI)v&h!9!~`Yg)Ld}$VwYqqXn@gVLi>3LSVGm1W? z3qnDJAk6chH(u7f~FohUBCxfQDx8?5BQsCcprAnfVhO SHC~zk0000@|NZ~|iEi8i zh~?j3h8^dV0y&$fY|i?}`2XK;hJSzm|G$6#|L@;V)&KqZ3sU;$?>`_ZF38C6g#jRd z7@s_ODJ3Nev=9gw8UKSQhX4P)z5oCE{eQ>y|1j|H-+x9%#`EXT0R#{W1H*5i5@u$g zs{agN2m-%;fixfipeaCQpFe*F2q2cU!)f`}`5%6M06O*WzrTN({{8*;CkCkD_wPBk z=llWket~@P=ieVecEOTuB>(}$vTge|RYlbYUmpDW_v_E!KfnL|{{8n?G|>28zowm@ z_UrGj-+zAtrGTUoztWctUjPD#h2amw@BhDn5OHz(52cFPg zot=T34qlT?57Mzkg5?0ssOG0Ftp>paW8OyZ`_I07*qoM6N<$f+2k} AjsO4v literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/cg.png b/lib/ts3phpframework/images/flags/cg.png new file mode 100644 index 0000000000000000000000000000000000000000..a859792ef32a02b41503b5ab5f216191af397e02 GIT binary patch literal 521 zcmV+k0`~ohP)i@P`2b{=v9^Ktp0uZ001fLMSM)r!?%mH+B91 z=ii?+(ckX8zrd=&8g~5t-|*}I50LQ;KzRlRfB=Hn08;wv->)Q*Kc0LLgTdsU-~VfV z{r~#=|1XdRMzE6r0tlo5X#B6gzhXrG`18Y*0!g6iieLY~Kvgqz}`W zLWO>Z3Lv@r7+CdZpiy8Ae}EeP0z(_<2Y>)#Vfe-1#`njMUlORAfdMG=o8iiD#%aG9 z#Q!h|feZ$rzkeYJ=p=vuV)VVru?7U|?FE@Zm|i>fBlzF zc~M_qKf%h=bAcoS;}NE7f8|yFe%B9?;;8%o@BeG_!|(4qhyo=(h-XBmKHXpc{~y!A z`THH3fsDVeudko)ARzm9UL&JI!+~uEM*rBES1=kd6zV%LH0J*N$gIQAc0y}k9qTFv z4h1oVG?rB#zNY^8{QUp5wE>>R#S4NZQd1i@F)*?OF@6y}@zmk^!Gr7L9asuAf!ae1 z{{CbBBH^Az=(+fAb?mHzA!MjRs*H~|NF=I z{~td;@BjZG-hUVsqZ#w(|L@=b|NQ>{d(*K^00G3pzycQk`jp|{UxvSb;p*Vv_V?Sr z{{Q;@@3$6WKrBG#F*5vR z`2XiW&+Xo6{KsbP_-SF+DJF`pL$*;0gaz z7NKVhyo`U;4*0+SK#>rfFFfafF5DUYXKh;r+@n*scPQUy6`!63S zGXn!71H*qtMn-04W+o=4|487=moEST#KiEAA;m(3;Xgy1iO{3BKc9a12~-UM7^Z`) z1qdLZI~bN-c=z}J|D88K{QmR*&tHcBxB*ZDKmf5Ya0q<-!`is-{h$8~f7m(x{QZSX zH3Jv`1P}|uy*o^wIGI2G`1kie)4zYe_&EQ6|NftW0ohon1}3I|Z{7d|5aVBMp0ssVo0Ek~;W>Z#0SJsA+2j`G% zv|UVeYYs-#Sn6_J90h1VosR?LBU7{U1rQ6+R0f9sPrg3=`~UA>#=n0a|7Q67_y6zT zP{j57|G!`V{{zu)Mn+knB>(|98e;E17s>tTLaMG-~Y>h{r`!g0jL-tfS4HmF#P%V_xIo5zyJLP zk|2W%e*G8w^1X|KC618W{eAoCFX+EI literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/cn.png b/lib/ts3phpframework/images/flags/cn.png new file mode 100644 index 0000000000000000000000000000000000000000..89144146219e6fbec7eaa89e1bf4b073d299569e GIT binary patch literal 472 zcmV;}0Vn>6P)$bmtfBa_T{rmsVufH!rO2O)W0!b+P{TrwO zAb?mv&i(NBbu;G`sX*)cv$d*%>MiL-(QdhpkjakVmyu%k;sfcNRj;yhJaxT5MTg0u5&QfH8#Bf O0000XbC_7v4G9~kE;3? z$h?1GrT>4y7{3|*{r>Zd0U&@_82$jY{AFPH^Plk#hy=QU5o|Ds0oDLyFn~2M0QE2c z1P~*G;PKK9g@-SH{rUU<_aC4k|G@~v`1Kn|{`&=C|M{yZ!G37cNq_)iVfexD=MNA8 zh5p0Hf4_c1v;p<}|Ak}_F!%rh2&CcXZ-(!`82&@8$XA^gF6?xq}zd#y5N`WeVBbyB+frxZH0- literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/cs.png b/lib/ts3phpframework/images/flags/cs.png new file mode 100644 index 0000000000000000000000000000000000000000..8254790ca72f98d9e79d94bdfcb8839b1fd434ad GIT binary patch literal 439 zcmV;o0Z9IdP)|s3jQ%L{DG0k=ogG+0SLgMnA-pV!aytqt1(q3VU5g2cTadE%W~lnu`}m; z-504zOD10s2K8+~RqF^K7O;8$o;>*sR0>r0`!|sM`x~eNg#P~i_4n8BzrTI~MgGdl z@-i@d0|+1%Q&SyHO(9WHu7Cgk{{@--A8b6x2B1zb1BygNIks=N00WJ{7y^0)%zz1kWJN^&bany+5X-;o*O`wU`+x5q12BMq=+`e0f-vAB ze||Ik`OBc90(3J#0I>iAmFf2%21!Yv97q%>_xm>^i2a8FLV}HlAfRG^0AlbRVNjfdgKH->)<41fMG{Q1Z53yA)}NHF>VM86o=*Z=~EMTLRklg;&|8vY-+ zh5rBh|L@=bKM?ZoKOp1JpMQV<{rmmr|L=eQfFkpA_5cJB<7Wm2?&_*f&z|!oCH%Iq zVPN&@1^xa7gnxhi`TP6#zh6KNfByZH;9_972M|CY z4S#m*VEXjw_teQhfB)LctFzlGTKpda#8eRa1F`|=V+j!s1_l{`0AhU0z`&K2_5c3; z?|=UACdB;@3Hy%?Ffai9!1(6P8-M@;JL%7#4}bn}Wu*W1^8Nqk|9@zxfJk8Yg2Rge z5=!5G`~e6c79IwMkAMHSMfCl8%kXF6O^EHl82Alg`~}6s-@pI={`>#;Kf}J}IRF8~ zc$$I1RCFdZQi0L={}0epF!T#d{sWT?V3G+SzyRi{Q|!NXWpDrh002ovPDHLkV1m+z B7Bv6> literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/cv.png b/lib/ts3phpframework/images/flags/cv.png new file mode 100644 index 0000000000000000000000000000000000000000..a63f7eaf63c028615b2ded5878b5e14a7dbe962f GIT binary patch literal 529 zcmV+s0`C2ZP)*82p^=00=`tlmIeOf&y&dk@6oT z&CV8YCOfMzZ7b-;WHc4ffO0*K`UGc#9pHKW{n=1`s=Y4`sB0UGuD|F2(+4xFrs{6A+r|Fh*S$P>SRGs(%l z+O-QHfEa)O{tdAK=ovi517(5A-n@AO5I`)f0sl(Vm><0RZzq2;(QtLuuFT6X6;y@p zb-8u)9gSFj-3}5^z@SqUX4qfA01!aT3<}j|#!Osn3@^V(zxysO&hx#~ZN`f)vS*$d zpLlA?$HBnM&cM&k$j1o^^oKjY0t65vgZ)>ehy*ei5K#*ZyWju7F%Ll?01#jRixNU5 T4U6zw00000NkvXXu0mjf>cZT~ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/cw.png b/lib/ts3phpframework/images/flags/cw.png new file mode 100644 index 0000000000000000000000000000000000000000..2a0a66c6ca1309a6c038e8ae7b5298436e724545 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~o!2~3KHq6QcQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS>Jit;^O978H@C8bk__DKk=Al2=8m8y(7udqSgz=nZdngE8~((i;}=uD!e{CDohOw%nTuSm3}-ZQa1ux&EVUz~~Q*L}q{yKmai^Fs!d`Rbj9@@=xl~KZbvQ|NZ^*|L@=bfBpc` zKOp-38^rz{DEepVv)`L`Yyt=%76t}3hHqR?<~aN{I`!9n&u`}MKs~?z|Ni|Cihlk3 z1w?Q7fI4M?HUI<=3&<7!82Y{{Q;@?^mDN?|=V) z{r~^ll=rtu5CcE}F#**BRkyY(adR>Mc_H{~T5^XRV~oVFSgBv#s=t2z{grn0SJIVV z|Ni~D^5u8)5(a<(0%_pmV<{@S{pr)+Nt0E6|KyV7{&xHKulY}Z0S*2Ic2cIyuhSoY z&3*a@=p=vu0%~ALNs$10lZEA9e*W!WzkWV{%9A7hYlh}ewm<)V{rd@2{rBIm&Idr} z{RV0P2mmnv&i?}d08{q%2kh+oUtbt0DGA2L@2{yn^Xv%Q%_4hjZ2Q{KfBuh*0fWjOcoPYi>{Qk`ejtlm`TPy!Q@c0|K1n4Ay00P>u ul!4(JQe+}W>@P40kp+Sq42=5$0t^6?P(4CrvcmZQ0000s1`2Y9I{~ve$KWO@Yukru?KTxgz{s9CK3*7Y} zZC_6Qf4TJkuQ&gHzW)Di+kXz$|9k=eg(Cm|XMz~|=g%L20Ahjo4`KiVr|y5|U;qEV z`p@?NKb!P_9;^R?@o<+hfSm*oKp+h;f&YIQ*bg)L>I;A4WVP82*P-nR{;Wu@%{Vvyu7@~4txIf`R>OD+-01E zIfRil07L2S-Mat*#Q65@TRuKMWMi;}EJy%|ff@h;2;_%%@7_UT@edf{0+7H22rvMY Ws9gjvbyTka0000? z0048MLcfb{@Lpld*gfdL?Z zSQvhRtN^J#x6%GQNHxSfxHgc;AD{-1tAIKH0tl!9q}uD*5!1Kl8Kk6va!f$;fJ%WL z`2Cv^NdEc54D$xi27mx!VfgXqT8ME7!~2)OPy-`SXu#NiAkhzFFflLy1Q-A_8F>@M S6G{sJ0000h<6BFn%a z@b8~2SoNP@zd$;E{sbbRuHQd?{QCI=sNwhbA3*&Qe}GP900=;09NYi^fU@pUdVa9*13;+Sd!tjgXKhXQEMobL97(p6<{RLvMGBN!7 j!N9=G@a-1^K!5=NcXWu!7_DDe00000NkvXXu0mjfeQx^H literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/dj.png b/lib/ts3phpframework/images/flags/dj.png new file mode 100644 index 0000000000000000000000000000000000000000..582af364f8a9cb680628beae33cc9a2dbe0559f4 GIT binary patch literal 572 zcmV-C0>k}@P);we;9uK`S<6~zaM{qBL9B<0yBR7V`E_e2q4D) z|Nnpa!Ep64!{fLA8NdLj;oraifB*mg`;Xx-kn#6Fhzn-q&in!pKrBENJRA&WD*ySo z7*5@0`26EP69WTC_22)0z>5C-g{l_hVFa245I`UeKudt6h7^Mc@BgDW81BCO4-x~L z`sXhc{R3+I%fRsKA3y*x{R6rHsA1>M|6jifb2E4w{z{xB{`~z5CPC;Y7bk|9<`c2PXgjR$^CT@Hzz$KrBEfF#?VC z^aSdB^z+g5SJMxCJOGqNsQwQkfr0#&=?~CJ009Kjz|71H^!MIRd#Ajb^76;aUyQ$y z%m(TN#spBq-#`C>zGeUjAdrR+|30kwu=eoBL!3-pGMq9%bs!`E|ACMovw

4;Zk2 z8GbPU1Q5%#7t@Nb6*GKbU;u{yA29j{CVzn$|6qa)V3LCYAiw~8(_SNKujRx50000< KMNUMnLSTY(1rd4x literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/dk.png b/lib/ts3phpframework/images/flags/dk.png new file mode 100644 index 0000000000000000000000000000000000000000..e2993d3c59ae78855f777c158a6aae6c1fb5c843 GIT binary patch literal 495 zcmVh!ZNvLM`<}kPiIA3?K?Zl!VJuS0ABN12uI2v;s z000mK68GQM4oDR3?|C6;zBc4LR82Q1eETXSa+3nD0Ad8%4|Ml`Fn}2U{~ypshW{9V zk%{T!hYtV&#KHiVV*o?2zW>+&Bgm+K00G4EikX==E9>w5`yf~S`o*<00G4K6dZ++hy)_Bw{QPEdi2K7 l5H1Kw2asrHVqgFWFaQRwS@oh;XP^K8002ovPDHLkV1foV*8Tth literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/dm.png b/lib/ts3phpframework/images/flags/dm.png new file mode 100644 index 0000000000000000000000000000000000000000..5fbffcba3cb0f20016c9717614127b89db4c9664 GIT binary patch literal 620 zcmV-y0+aoTP)yt}{r~U(pZ|aU z{rk@#!dhJXj+vP`D&$&!2yPR?Jud5I`&tqo00z_V3?cpb$_)%#~li z5Bw?p&G@T5&5h~tB<;UIJ-`3_mgbQL+5iwhOdtm^{QZlh+Tg>NcGo{qR#UR9ewx4g zlzHy^uRp(j{rmOj?;oHBfB*n70M7pb{ow!s5r+W$=Kufw0RQ~_o$a1N^GPoTT-yiw z&XGVBSbe$r0&xrf|M~#~9P-zx0st`p&i?@b004Y@cH`sX`~3X;`}>j`1PlZ1WGkKd zF1~FN+w&9T}cJeJULy4V8r?0tNHM6WN&<2126ppbC05A-~q$vMCOd&N)3^rn&3WaiZo>@dB zxpL5=L>h@#UjVT%{Q39iA58Ths0J3s|NohoLF&Lt!8(8c18V>XAjZFc|1vT%{s#lF z^Kb%2CZ>-cJ^%y|<6Q;@;r#qR4;}z*|Nr|B$h_ab1b6QI%fu2>dIV_O?>~RRu82tM4=f|&Ke}8;oX5+eBw}y?G6)5=s|Nnnr@aNATAPEpaEN{Mj=m^OD z&%p5S|G&Tg{{H#<7bL;LEGj9<&cFmz{_j5mJbLs9Ab?m{m{|TZ{D1lB9Z2clKfnI{ z`ThHs^2c9)q;CF`l>Eoa3N#g>nv07INCE^93jWH3lKe}28-VENu!{qxMpm&svFOiawo%#4hT5U&FS z5Yzu(KY%Xz`RDh~-yl6N|NT~b@qz9A2lm1$Hf43G`D-AmnZXVNY5)izCWas1fbRMA z8?52ykLBVV-&q+tkKFpbbOC=r`2SzOfQo=l0_p*hfB*gk2q2IKpzD7Eo%H+Hk6%A8 za57|Q3rv;f;d1r)FDv_xg9F*eKs^8f#KQ37)2~0jMR){${rwHH2k3S7pO3`Z{#jf7 z{|`16Y&=9YkOT-IMh0&|hF9+yelRe6V}O#tcxPkSx96}BCoe=1&?OKCkOT-Y05IoG U$(*n^qyPW_07*qoM6N<$f?|9Y@c;k- literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ec.png b/lib/ts3phpframework/images/flags/ec.png new file mode 100644 index 0000000000000000000000000000000000000000..0caa0b1e785295d003869330fc4e073dce07e7f6 GIT binary patch literal 500 zcmV1sCzZm}g`N!}J$oTi?|G$4gL7*^D3`7C}Kmf5Y{CmeN)&f@km*M|^ zrvE_l-~a!AA&BAspa1{=fXIJ9!9O2vbOQts3j+fX{b%^|8m0my0Yd-4N`WN9@BjaR z=no^SIM8~400P%s(0a|A0FHhM?*o#se9Q zEZ;b|7ytr@MWm#zEz$bb`!9d~{{Q>$@1MW_!07MqKOpw+zkh)g(B$8L|49h*Ov!x= z5I`*NZ%IAX?Fbu$UUAQCA3`wxix_2=I& zAouq_SzðCcuS!~%52Kai0?gF&VORsRAR2~rJG2PFT1^!)w@)C_d-AAkU2Vc75Z z*R<@y`9z1vevIh)-p7{p`5C+7f|6l;f1_&?! X)GmJPc-xs)00000NkvXXu0mjfGFPrC literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/eg.png b/lib/ts3phpframework/images/flags/eg.png new file mode 100644 index 0000000000000000000000000000000000000000..8a3f7a10b5757b006948ea4436fb242d02dc9a4e GIT binary patch literal 465 zcmV;?0WSWDP)LAHVtk{r`=k{y)(2e*gi*sIRYISXlV_^=qKp{(!;n-+xj9 zUjemETFMXP0$m6sfwJP_;%#kh009JYeOg-Dy?gh5gTXH_fG|KLm<2Qhs6|CZ<>JMQ z009IFR-loRl9E6vpeV=!FaTTi8)D~Q7yv2;2q2OXK!5=N{?|@pNV(X=00000NkvXX Hu0mjfG@sA` literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/eh.png b/lib/ts3phpframework/images/flags/eh.png new file mode 100644 index 0000000000000000000000000000000000000000..90a1195b47a6f12c70d06cb0bd0e4ea88d7bfb03 GIT binary patch literal 508 zcmV`hKmn*~pz%QT=MPXRKmaj;?1ifa0xhloNlE|L zuK&--1mwx?-uoV+`qwW8u#*4+i1F_GyFwg7fByafIr{%Uh)$p>Ae%w(|Nq~=m~|`# z{`_H7QhIal96$iEC^sml1*F~kc<(nb4FCQ91q_kDz!3TkBLDsd`Sky<|4qNomi`s~ z`xoeVfB<3v8uI)9A4wibp!A=AfB%5B0nwj-e?TNKsQ>@@`|oeijK3Q@{{o!^5I~F! y*$fQd7#Mzm(H|HCnf(WhfND7yc3x%x2rvM-AWsdQI)rrq0000&(jx%j7OGE_~DVuFcQkgj@33fJv($pjj zgoNxWFM>pG#K4X+%S_Ys!f>f$mib36%ekHNec$;y5njB{!+Y`YzVGwA&4mTVh%ikU z03gD2Hn&LPeNu%hDG7PUvzrphs|^(-as$IIo1LmPya%Hc0Qn*6qc4XX3oKoa+Z)_XBQk8 znPA)XelBh#6J<)fj|w>7X+~Yun^@Bp4$+N z6L8rb{%QnJN{fql*fJH1L*2YjUlB~CXS&&LY)1V3h&68|x1_5-(4l3HUgs~3JvLXI z$_D=zL{dTnq9RK`-w~w|sCYqqA;@OoAE0!{9Gi+cF%zA>5*8OAiXWs z!A~!@Tb_6WJ;mn(q~>CYJ~Oq(|Mc`miY)G1d$)?S_lf*=dz3nd-8+hwz5w#U=!7L- z+Ve0W8Werm#o=KvYxRVVNtM9!poHk%m;Y{gxKdXC|Y{ fc0^aUlspXz7vm>S7OoCUZUIwXLGJ6E%Z+~lY(hhH literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/es.png b/lib/ts3phpframework/images/flags/es.png new file mode 100644 index 0000000000000000000000000000000000000000..c2de2d7111e3cb59cf6511dd2ab045e824bdb43e GIT binary patch literal 469 zcmV;`0V@89P)@|4`Xj5kLT%`al?B=W5I`&pe;NKW0^Ri&h`xRJ_x;0v zUa?=y?0^3M|NZ~}FE9c#{{3cP{Qd6}13&;Vf!z&M{pZWqKYu4MFm$tgedF}w=P#IQ z7-9gT-$11R0mKA$(qEu4%ok$*y!^wMRm*x;`R7|k6yu?K{s8?55I{^|9{?Tjhec2I zv+6&FhFWG_BbNVc|Ns94tNRJp!0`V!Py;{!F#$2enB#XZaohg-5%Tlk#oa&nzQW9g zl0Y{D4gK?n0U&@Fe=;yIr=|V7caH%YEYL84k`Tt9-wc2LGODP&y?7BIfLMT@X8Qey zK~fSFpuiXa$^kk7RCwBA zWQbH``0|MX0{;DB`1Ob3-!Fz=zZw28fY1*HhF@R=VQ?@21P}|ur+3w` z_wT=dfByab`{x&s{PXYspTB>919^Y{{Qd`I{N9v10U&@_7=ExZ{APUe{`KE~Al1MB z{rb!Jhml3<_uqeCzux)%bv7|NkFAvw@aK@N+YWG5`bsF#yj00sZ{|0ReUZ0OJ4u`~d&_lgxzd z_7grGtKje;)ax=32IqJ>VE_O6|Nr{|0Uz@6!2*a0?AgCSJ_s@V{`!jztXlEUzkg2h zOW%AK0ILQg2A~)NKmdU>0L=y=29PKt(~s@sMWK)O87MU|4G0ImPt*+y7s`{{I1L_{;G9FVHyv z0R+;(^!pEkq$JpwzYKqVGyVRp{reTr#sB4{{{Q&{G@Ah!GGGK$3=lw!Ux87Egcwk{ eXi`7`5MTfy3O%OUuKb?>0000FP2AE)Ir2{}>qlLBSs|`Qg(SfB<4)VqlOE;Q=cD|Nnn$ zna02W|Nj1E&`=Tpav_4q;M$#E00G4E4{SI@`q`VGKvVzz{r4XmU}R+c_2(Zz0I~c7 zs`v*r?Dty(;z&PFFXX zRA5t=4x{1SIibD)Vqy6A2V^D4P_SySA|L?j2ip1XFA)9%V~_%1r~w2J3=}{2Oiu1 f7(RXZ0uW#T>&I!FfdIJb00000NkvXXu0mjfj-u42 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/fj.png b/lib/ts3phpframework/images/flags/fj.png new file mode 100644 index 0000000000000000000000000000000000000000..cee998892eb316c3293ef2d52afec9218bdbbc03 GIT binary patch literal 610 zcmV-o0-gPdP)2C05Lr?a%1G+Sb3M_ z-f!*)-&mJ@lxC7weD@!u;s2li|9<}wjr{Zf&o8mqKR`Cn4*&rGF#yj01QaSLwCD}R z0w(ww8v*|PzTN}jB`Pj8{QK|!{{8;|gOCLd|L9jy6{oELG6Dcq@B)aDq496GGsCmb z5T7wXTnzN$?|=Wl{r{i6vr6{G)xV#={AXc)t!L+QBoiQjSb+Zc`=1dU2n>I~p8E|B z6OfY_{`1$ji1Pn`9_T5yZrhJfj0}g~00a;V(9A!7nZZWFG{8az7^c++|9dI@cmDl* z!Nvb)UorrL86bdIfbsI1fk}{;;V;BV|AE?oY(}v2K-{x07*6Kx`SfB<4-V2A|A=r2_C101z~ iU~vp#0R6xN5MThlzdwv9U#bcK0000}CO1*!he@c;k+ zfB*mg{rg{#hxPwIh8G{d0|o#7{Raep|AEAFCm#U_AQp((@4x;AD*Xo({rB(3@4q1y z(m>63@(hzyJPYWMEi%>@iRTXyxzU zAo>qT2S^W413&<={Q1WKlmMyz`(Hzv@8AD_R~~+N^7b200Z@m&0zc4{Q@7rNwftxJ z^$Q??fEu9g1Db8CAq;fMkDq@pJa`8*&sI~^TtyHla^%`8uswf)HUI<=3()%@Pl0^! zf8Ui?K|l%XeRX{Qd(n@juAHz(D*15I`UefB!>$cK-hRUqIf!|Kj}Y zKt+#Ue>r^ZHOMRf{y`i940V72V*2%m0pg)Q5O4ka4>U8*PAAEH2(%ZZ;ol#C0AiF(UW*)& q3=s4Oj6m#vP&UxAe?ZIt5MTgFMVEBke8_SD0000BE0lK=nzFYgc)d0A2*B+AFf z2joHok-@WP&j1351!6Wt`q9fjf1W;g`1ALFY=DuG5oiNI0I|ST{|2JJ|Ni~?`|A$_ zRt*pr0t5gt0M7pe4IJopi4@}M{rvp?{Qds``}+I+|3-D|`uqR;{Qmm<|NHy?`uqO- z{Qn;q1i_Qs0*LV@1A}N|@t-FT{{IC^{`vn0sPGp^)o&2vABgb_!eEtCyu9%!Kmf6* zGhfda5_|CT&#%8A#S0%rhKer*8VNG{57cZ3sU*g7is3Rq0I|G(Bf-nd3vr@r@vHy8 ze*OIQ@9-oMOb-A(eJ@7=Ab?mP;SCW2x*O<#U%#>Y7zqCS`2!F@APw*!ml9!S{vjhP z$_zA&0R;fLP(1(v#Q5^%OL#2G%0Af7VC%@R_vTF*lgG%);U`26kn-@hOg zU%!6+4+cOs(0HIde9xZz`}Onxub&LUB0x(30+2WcIRJn#2ut|?gWYu1Cf+!-K%B8# zdf?1WA}#uZ8oj7u>$I1i0Al&`=O0k%-@icgAIJnM0xA6maSq6BK-ECw|NZ*S`0Lj% z1_pot6puj;05Ax`F!=umqj7^frO?t|3^&I1kxUq9yECc+jQpY84SWH_0#pxl$?v~F z@*hy-KN0|X07U)z`4{NpU%#2aHUI<=%a31wK(7Du52Oc(|3O^?R1IN+RRjI-n*kVB z3=9AP#PZ|EACPLGJ%9cJNh|>9B%spYzZw7h1%?tp0I_@ndg9MNE>313@6R75NcceF zkr51-#U+7;F#`Sf7i0rK0I_`g_NQ&Zk)EZ(2O=d>QH$KN3zEi7S9u{+2K>GX4ds`2QcM z=+A$K-~a!^(JwH9Fn%*K{{Cdb01!YdV9)*qi~a#?`wdg{8%Z^Y!NB>w;@|&31~6!UgVU(k2*|8J(R-+sudaynhucHbwAMTnor{mwqO^w7JHzaBsT z{O^B8RYf5+LvDs&KmRKVd78=o{`1#HTiEo_OolaGleS)G+IQ#sUI`b*pv<`1zCJ=H0jd{{2S>p`ri%{LsXJ%FbMS z$#S`6f|?OG!^Jxczkf6Q`UNF{l0Sd`ad7zm>({^EzyAS6{{CgrkluOb3l1A>ZU2~A zK+FZ=zkmP!`TOVhpFbzBzFaPmD2$N3;+$pK?>zdet`f0002ovPDHLkV1gy;I?Vt8 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gd.png b/lib/ts3phpframework/images/flags/gd.png new file mode 100644 index 0000000000000000000000000000000000000000..9ab57f5489bb9ebb6450cb27f4efe0cfb466144e GIT binary patch literal 637 zcmV-@0)qXCP)@|2i2MUNJEAGBA`g{QJZ3ub1IpEW7h{~Z4K zJ3#Qa=Q1XS-;5h0el2+U>(Te$zyAID&HDTIKad810Ad2W{jW5`@9y)AKr^5J{4Kqa z;cw*UKQW)B)-pW&@%z{RUqB7N{{H&&_Ycr?fB*vd;rG8kGw%JlqX`uK!_D~nn&&T_ z*-Q+-7;nUX=6mt$`7e;3-;BTi{QC{m01!YRC;j>JdmqEEKMa4Icz%Tm{+4F_^}h({ z_1{sye%WyRp84|E^4Gur0Kxx1e;6150*D2Oe>41%=Kmef^V^IA7&yOx!2%AYU;o*D z%dq`!;`!w)_PhDb-(PS30!;@9Adn3rpZ_$9NHVegX88Y?;V;N+#{WPzFy?-P;*ar< zJ?CFrnZE^h{{CWM00h;Fvzl@K2fHp9I6dqaaxb00=Mu XLcuQ~?TP?t00000NkvXXu0mjf`7udf literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ge.png b/lib/ts3phpframework/images/flags/ge.png new file mode 100644 index 0000000000000000000000000000000000000000..728d97078df1d07241ae605dff2f2cac463be72e GIT binary patch literal 594 zcmV-Y0^8x|9^h-OG^F+g7@$L?BC01YQ`Wbb?43;K=s|G$6#bNnb!1Cx>Qe>QfY2m>?Izi&U8 z1o&Rm*8l_%%a6%3nS?}u4*37)&;Q?l7=Q-<`}?1Z>;KQLY|KmGQ@ZWEch0Jnt zUmm{%2p|@g=ujpTGX@n^21dqzKYxO4`1a@2NuYivJ4XgKw*UYBFf%g!{qd7YP>5~& zE`R_4F#yj00OjT7{QUg;`}^~|xBB|}`T6Q!vcs262Lz;t$n|1+qbnVARhhy8{z5C(*C%JTg?tEV3%;s64O@&5h$(1-*>2%Ak`A87BFlP7^( gh&l)WvH=1N0MfQja}g1cO8@`>07*qoM6N<$g4hNuZ2$lO literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gf.png b/lib/ts3phpframework/images/flags/gf.png new file mode 100644 index 0000000000000000000000000000000000000000..8332c4ec23c853944c29b02d7b32a88033f48a71 GIT binary patch literal 545 zcmV++0^a?JP)lgG%);U`26kn-@hOg zU%!6+4+cOs(0HIde9xZz`}Onxub&LUB0x(30+2WcIRJn#2ut|?gWYu1Cf+!-K%B8# zdf?1WA}#uZ8oj7u>$I1i0Al&`=O0k%-@icgAIJnM0xA6maSq6BK-ECw|NZ*S`0Lj% z1_pot6puj;05Ax`F!=umqj7^frO?t|3^&I1kxUq9yECc+jQpY84SWH_0#pxl$?v~F z@*hy-KN0|X07U)z`4{NpU%#2aHUI<=%a31wK(7Du52Oc(|3O^?R1IN+RRjI-n*kVB z3=9AP#PZ|EACPLGJ%9cJNh|>9B%spYzZw7h1%?tp0I_@ndg9MNE>313@6R75NcceF zkr51-#U+7;F#`Sf7i0rK0I_`g_NQ&Z{Ouh|k{s>tFQ`m@LnIf;Cf*`&?P1`-3%bT6cx$WEgd9G6kVgR==LoAR6di2*BPGS}KaKtENp#5NN9@rPcFv`RCwBA z{Lg>@|4`Xj5kLT%`al?B=W5I`(ov;U*021*0XgD3^5{teN< z@cTDV13&-@;@|`T5QYI@3O)ok?1DO<2trehc#kXh!0Z4iC6of!=I9L4Jz5Qk(jP`l zJOKo8(qFLXAF#IH8`u5XwDI@PAHNy@|4L4RsD^0x1N0+605O4m05bkR14QCiMDQ;; z>0h$aKjWi;+@CNFzZm}i25JBZAQt8_hOB_!_dovn^Y72^zrTL{{r&6TuiuWpfB*e$ zwD}j1{Ph<^0%eu?|D0`P00`k|9}4iHT+`$2p~p=WCoxfpgkZGj{YEt g{DC2GLI4Ob02tU}a;hkw5&!@I07*qoM6N<$g4!w08~^|S literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gi.png b/lib/ts3phpframework/images/flags/gi.png new file mode 100644 index 0000000000000000000000000000000000000000..e76797f62fedcbfca8c83c51951680d6a6e9081f GIT binary patch literal 463 zcmV;=0WkiFP)VoB37QQ+R{;bN6GSUi z+kb{HA3p-oUOth}-@kwP{0U71P%%INK{Y@H82)oDp3V0Dt?T`39Pi$;RTl%zL?{P4 z2_S%&kW~Z0x6qjPzkeV9`s>}VU!Q7P|1&Wm)PrpR2q2b!5Hle5FfeebsWZ)5spT9vc*+3O2}Iw&|Nr{sqx*C2&8&?7c>c4n{QvVC zD9-TjFQbI?+i42`0*K|`>)%Y*uQL4o{r~rGhChE9{`~$Pz`@Ka$@uLpb; literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gl.png b/lib/ts3phpframework/images/flags/gl.png new file mode 100644 index 0000000000000000000000000000000000000000..ef12a73bf9628ff5a67b81bd980d9c5d2b2c0f05 GIT binary patch literal 470 zcmV;{0V)28P)J{teTOL@z0+>00G1VRSiT77W_YWkm2*^|KGm- zfAHXcOY8ruSJ7+$Itd_vn4oTd_U!+mLkz$F{Qvdq|L@-*^6S_C%a&nk00990)`(*=-xesBS%qG0|gf?f~#iu^9N|j9|i`10Ac}ZUz>% literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gm.png b/lib/ts3phpframework/images/flags/gm.png new file mode 100644 index 0000000000000000000000000000000000000000..0720b667aff506d7892c5c301af04e6bbf932751 GIT binary patch literal 493 zcmVwRhhvIeu00_fCKU~B)yH$s9sXS^B!W{?M(W&}hPbMwO z;*cg65E@7haJ!!XVgYOW|Le(9kkY?@fpY);{sqc`6amR!K*q2CzkUI^Y_hUI(*XjA zMdSH%VNp?r|Ns620Z1T|}fB<6Q zlw#oF`Oo_sVk+2%KTsoq3?TP6gz@)3Ki_`_=6?VI#CZSdeQ9y&f57m8xf%uh13`xW zAjrhTbmsgSfB<4-$Y)3kNW1sx-tWJ^f#}!YUqA$5fJva>FJQR-`S({vK;>fVMSuWe z0mcW=Ig;FxKxv@ppTFP`1*!N0BL9M&0|dYz`1hCL7Xv^5F*2kxF#KQuvOqEU3km&! jiTr^fV1zR<00bBS-TrJ5MX@2w00000NkvXXu0mjfGz`_@ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gn.png b/lib/ts3phpframework/images/flags/gn.png new file mode 100644 index 0000000000000000000000000000000000000000..ea660b01faefde01ad2527a6abcf7d1a5c1b0526 GIT binary patch literal 480 zcmV<60U!Q}P)@|A6>41A`El{SSoRd}EL3@|NdrR`1}9=ZwUJP z@AvHwzkdDu1yn7|BMY<#Ab?oFX8(t({tZ$6>;L~hU=2XVuU~(E|N0Bk07O6y00G1T zbT=bV^`Afg|NLS2{ReI~M8m&-NE-fuGynt;*hzmtW+Q3%1=j#1fvO=I{`~y|)Bq4b zU?=?r84r{KY4``%041R|`~%zYhXEjfz)k`h|LYgXRlk0r+3@c_)IERx{rUUv4^RU@ z0D&|xgN*;p0Mzyy>QQ8EKn=iP{qyfH5CNS85I`UeOuzpyNJ@hA{P_#yFfjaPWc&?| zr{By>f0X$D{QV0G@4r9|{}=!Qi18~pg5ikaD#Jf9Xfy-Svu_Nh0nj)GNi#731Q-A_ W8E1tdJ(&;y0000P)fLk0D%}*I7ff3uKv?i+N*~ULWZ>4 zW5%k%a3T{@*`z6pma6eF$JtK+F@C*&o=d^t|Ns9GOCXH@*Z?CVuP#7nB5Y|1kmu|NQ^=?>FP0zrYX%2q2aZH;)P`n*#-K1r9WbfYOYN z??RUX1P~*`M}`*mir*mb{sxCVG>rbhqT(MY2L1y54rHu+wi6(L7#SX-$0bVa{(;3h egu%oB5MTiLH(5{VMZMqv0000u-`~Ig{`~p> z=MRwl_xtx>F!}G#@4vq{&D;bKKrBFA+{}CzK0Nsg1pog2{{I_D14*DX1pWH^3y6RW zSzcL&Zwvqd#Pa7K10w^wlkmIISh(4kI3GTJhN2$mzJLD! z0*D0|DC|s(0(}1j8UFtJ4HA_S5@P=M@9)2VV#30}-~k05FvNkXnV5ck`2-L^EDTKl zn1259n3DG7^QXUm{{H**3#f!E|`n6Mz6>fhdBj29g(UfB*aM-=Dw#|NaG$fByXc1LXhxFC@THKjjKQ05P&# zA9gbr+SsEBRPB^?1!&T?30hEBFhHFGv5AR^>DH}B00G4E=NHV45I6k$@0N4rAH*g9 z{zDN+_&*OP%Y{RC0Ro8e#fvv0A_7PTA~XKMG0?q08}8kE2oOLl>koag&}IJi^WT4% zN&g{c!yE%t3}J9_Fdy0V1t5S}4xV|TB*XjR%dfvcU;YDm6wdeu;Q~GU4JP)J`S$qz^!f=A3G?{>83*rF;63vB|NHv-`~3j>`uzF%_#GV;x3_Tu z05Jg0{{&$2KZ#9J4f5RdzRdzC6Ae5p=(o+T{uB7y^ZNSwN=h$cVmJ#62-nx+ob%l!TKAE1V~I16T$|E1+i#l`u&y!03t z7ytqQF#yj01Tv_Ick58z;rzG}_y7F-%)*RRAv1X@1^f8;`}+eA4+yBm#PKA>w6y_L zQ&a!|00ICp0M7peY0>I$KneW(?7rp&{QCm?`vUdU)NRiG`uYI-`}gbY@;Dai8vP{| z4h7uY=>Px#0*Hx$0pxvffc*LW3+PFp{}_J#0ty11^n2!vLjod>mX_Jy|Cz2{eHy3% zAb?mH7=HZ$2N_TWSP@V&gaITO7A`uvc=2f<_uIFxDk_pd4FCZI)bI_+mz3lOl7E0f z{^u`PGlT&Y`3GeD{rm6lUtS;y)Bq4bz=+dkVE6#Ehk@Y-82x6z3jhKP0OI&0DF;s+ Q-T(jq07*qoM6N<$f)`^cRsaA1 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/gt.png b/lib/ts3phpframework/images/flags/gt.png new file mode 100644 index 0000000000000000000000000000000000000000..c43a70d36424b66f1627216ad988cd23a4be9285 GIT binary patch literal 493 zcmV|9}7f|M~m>&%ghRD!%{(5DNnfNcHoNKp}=7 ze;EG#|IZ9j4hBF)cVB@t{Qmo2T96TF4?qC109^ty;2+Qi2B0xObN{1)Uw^{8{`KJ4gZ0H zP(J{D2s9NSfWR7{27t^!x8NVhN&g{E`Ueb#e*ggla?+pwj3Cv27=VUhwE^V&zaVoN z82$hR5DUYve}Dck14V%vK+Z)23?LgAK*7ZT@-zbjKmf4-BkT7+CNVw+pd~+kF)%WM zL>VCz0|PT7gS-eZm>Gd?16jcE4PH%g~!@=<9&!2x_@aEGW jS9>vVD)6R*AQp(xFW-Wt{{mJ2|NHk};Jc-)O#kPzME?5A`1{ZQ-#|To{!0im{$XGM z2p}eq*?*y`{{RjC%V7Pj<(}hvo^Z>=hrJ-xK=d0#0&M^~2_S%&fR6tAp8=>2$p8C? z0q7F5H=q9h`}OesIT^OczkdG%sRkpU6i@>|0I@Lq1-k@j0La}yZU2}V|JX_T{{Q!% z>EEtDfBydd!vuBJUq+w?fB<6o2X+#W53~WS;s2M<|9d72Yseq)`11cZJHs!aS-+wF z1}X*!ASNLG{ST%Ze}QTk7ytqY@{}>p)on&BOV)*xm;om>72$%sP!HhqD7ytr@1teTu4J7{m`^Wh2AGglm{~UiA z82u;s5_9PZ|FH1)~3de={)r1*?FgUtsi`kx>?C4?qC1 zfX)8@|LZe*d2K>lZN0{{I4*^b@QIr~;(tA5`a`e}Ddhy$ldQAPvkwr9kh3`~WiS7Zcc2 zh-#qDKOj^7{QC<;3||-k0tlpm>GvN7NlB2NKYyjTe{-?^h8PLd@aHekus?tQg2Ee! zfN=^CK#X6(5e!Gd)(i|h;JEn(j5jcXFhHq*fkB7?Aiw}&uW^ngBcx#f0000J&k9ol;AaCAG*Vvs6lsG2f+AJUecp&K4&zS7@MzJZZ+RCHJO2~-cn~)8*ZB# z%#~(Seaqctb3On>xdArM!+zLfe2=iS%3k1HK82I)yo62#|&;D2*%o~N(LQ$HrxFU=@<#wgQDty7s|5?>qxBTrc>UoBZ!}1le z#)a`Pq~$aEPO=D0fO80I7h5SSMqU=q48*j9Qb*%7#+Pi|ervSf?0bSFwKsAPn1FO| zKH_&kh#AJmvOUSnl~!1AmcaNJM5awz`0DF46>zWZuCh$z(7uBp0to4w2iu-uj zV9oc#M;CkJ!OT_8;~(;r&Cw`0K3r=(%@VWyiIA#;S}+n)^}q>|)QZ|IaYyyY!;frq z6mATysX~aM!z!n$rJ$=27fpoIr3iB{q|Gr32uDRa3PcNj==OQGHve|07^1DbtUgzuEQ=j%rDF literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/hk.png b/lib/ts3phpframework/images/flags/hk.png new file mode 100644 index 0000000000000000000000000000000000000000..d5c380ca9d84d30674f05b95c2f645b500626c07 GIT binary patch literal 527 zcmV+q0`UEbP)00;JD`K-EmLvOuK( z0R&e6?>|)a-@i;iz|8zVIqAQ;I)|_@BNM~FU%wy-s0ZjAfB<3vx(uZH&mV?Ae;64V zIcsYEzkmP#{)7J;N}0Ju0muUq%~ z^Jl0Zz)k`PASO^y0(FCg{s2v4J zf4+RlSW)rg;lp2_Kl2?q^5yYkpazCNzyJOD%k=jzP%%INf#Tuc?>~%^l1w0DfWH6z z1E^V4lvz;l%d1x`a&jQQ{ROE8h7C|LKmaj5WMKG(8n4KVKd5of#=rm&U;y%qJ?5>3 RVzdAN002ovPDHLkV1mTk^F06n literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/hm.png b/lib/ts3phpframework/images/flags/hm.png new file mode 100644 index 0000000000000000000000000000000000000000..a01389a745d51e16b01a9dc0a707572564a17625 GIT binary patch literal 673 zcmV;S0$%-zP)>fJ3En$GhGS>sbE%%m3$AD)q?8M9y>88-}kR7#RKlk!P~Y_PLuF7~U~3`~nC7 zF#yj00ZUDdpLsm{7ajP|&HwoK0Usg|6%f4L_{`Mi{rvv-`ukf=Ed&Gs-sA7L!Q7*a zj{*QO0M7pb%?Sw^g@yy{>ihEY{`vU@3=8@G0rvO$i3mOL`~mv-`W+b$Mmr&io5dg< z5v!7q0*L95jt`TzK8Kd(Utv)OSp_aLv){6ccV+Z`{Q2+asKUU&aO3`Kpz6wW8wp`< z28M3{0mSqnB#A*-c*8%1=RD#sSOwMznKA3=e&iEzwo{cA=PgXK`2OQ}gqId83!|%* zA_Kz@fB*n70M7pdECCwp4H&@R`1|(w-}M5x*74i)0}%fAt;XafA{48))#>Z>?CD#}*e}Ret0tl$#*RMZ7}Jl7Z|M45`5*URzH9L z{rmSnPy;{!u>dsyO%meg+00000NkvXX Hu0mjfN{&}S literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/hn.png b/lib/ts3phpframework/images/flags/hn.png new file mode 100644 index 0000000000000000000000000000000000000000..96f838859fd2aed975f5f4134050fdbc0486ce1e GIT binary patch literal 537 zcmV+!0_OdRP)yNpn^YtfB*U? zE6K^g@B<)#SlTBTcsfda`|@_0R#{e$UZ3l|Ic6l=B#}T zWCl5lg}RFY8S^$g{qgfJOdc2ve*glAv3c@IFK6|y-~NDH^$#cn3{a5k!L9^_5>O@B z$^W^zSlTD;0tg^R28Q0WdbfK|zW)9V43odV{`~*->+kR1AO=tbO#T4}-G3E1?u#4x z0Ro5x7#++k42m+GppXWk{}2W^;6Y*k7i(1vOT1`b$6{=&w9#5#oJ b00=Mu*}Zhb7k&Za00000NkvXXu0mjfKokPk literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/hr.png b/lib/ts3phpframework/images/flags/hr.png new file mode 100644 index 0000000000000000000000000000000000000000..696b515460ddb670acb7e9de4438aaf21fc5fb77 GIT binary patch literal 524 zcmV+n0`vWeP)@|4`Xj5kLU3fF!G{fyDoR{}_Q36Vv~H|Ns5{^Z)NZrr*Dqe*gae=g)r_`DNuw zfB*t(VEF(4$y0{Ee}M=nS{mB(NB;kxJOBSE{F_{n`2Y8>|G$0##TjK~fi?gH5EIz! z|8Uj6|NiY-l-<0|OxA@1MWlzkmOI;leLR z$De1;{4g{7^y$;LZ{NOt{rct07a;lk`E!5(f@%OM1_PkhC8`eE9egqWbga z&p;hdpFRZ$Adm)#>fe8W4*Ct02B|hR1*-n0rS<;(dx&bFRY1n$$BzL5h>;=uaC^Mc z(+@v?|NZ~x@4w%F|9yUZW7+yTpo@LH>e(bUfFghX{rmIxzpNnpimU?w0mQ=a>kp9s z8>AGffmuv7DrFgv!3yU7{`2q8f1ngFoPlxn4AoS-S!;e2821p2q{((sbfB+=MK@k8U5Cg$|VB~~? z2XKWZk_lAZtGhi{|56nPieMKY$Bq=4KgZ0muK;2JYWn}5;nka8K-GUCa!{rJenZIL z|9<}gF~mh#ftCOS5DU<%|Ns8~1)2?0{RgZLWF&|Ls)lL+iU2hL1Q5&LKMX(>AUTM^ zNU9+S#0FXN@8@rz^Zx+^5DWL07wmsTIe-5EX@IBzTJ`52%kO`z5F362$-h7b*KaNc zh6exv#P}EJiR%3Sk01R1^NZmZ(C**=fB*Xb3rzn04HN{CU^bJS()(Sf00M~R4FdxY z(0f3MKYtkh0!g5OAQFsz{e$TF`x|H}%fCO*7#IKo2o$W~FaxWA8VofRr202h8w1#j zz=!|{Ah3qte;CCj89_$={rBfLBSbS$5J>(7`GW}-*g)q41Q6q6a2)=FMdm+9l%onl dL?8elzyJ+{hsuy4pm6{I002ovPDHLkV1hP90PyYjz{{0J*12TXlP$`i71!VmC|LYf!%PK1iv=Z0I@Lq zVgLrRB#$I8Q2qeT`3KSlX8!?(3s3+U9e@9T1Mx3N13&;VGFUSJ^?=Y13Wr{r~&-|6d^ahvDC! z|G)qK`}^nrA0Ybu|2K$nd)6X=0AgWa1{?O`IRi-PU$8V7{r&w9sOb0ae<0Pr|Nr{M zEF}%J0U&@_82&N|Y=e#y>zg27mx!0Xgp5*S}vr z{r~fq0csvl>92pk!P<5VDfB!NI2#TFQ3lKnzfB%Al=06ZHfFW+c#KiRe{d<4_V)^&)A0s0pNIe5S)eu>r zF8~6F38(?TQZ#J<0R*xEXct5e0}KG|WIzExE=U%r7$AT^8h-rv@ecwRzz_$3Xaxu` Y0RLik?wUgPu>b%707*qoM6N<$f;0ZTz5oCK literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ie.png b/lib/ts3phpframework/images/flags/ie.png new file mode 100644 index 0000000000000000000000000000000000000000..26baa31e182ddd14106e67de1ac092a7da8e4899 GIT binary patch literal 481 zcmV<70UrK|P)1Ab?mHSU}=WzCQi??=KL1`SXRBmG?g! zeExSVgb4YXfjasA0Ybs`#&c5^XvcLUqDM3{{9AP00d^^H2e48-+%sM)d02u=%hct8G!N( z3;+QH((o5-_OE}xfO;@2_y=+i*h!3FCjkTyNW*WSt$#tPfB*dj3@CIxKqoQ$2DuvO z1O^6x00KJ+r1UogVe!Ksu!etsL5P6?Ab?navG)7lA4zUWkT?GPWdcP410y3N0|YR! zFo-FE!v&-P=p=vuVq_>~VE6=zV^DnmVAx)=U5ZNz6vaS)0m(NHWW2-wfs+9Q00bBS XO2cxg3=*#z00000NkvXXu0mjf|9Z^l literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/il.png b/lib/ts3phpframework/images/flags/il.png new file mode 100644 index 0000000000000000000000000000000000000000..2ca772d0b79b255872cde2fb29060bbbbad950f2 GIT binary patch literal 431 zcmV;g0Z{&lP)WlqUuh`uiUU82D1+EBLb>EWz|Nj3k zj6%@>aVJ0ku|Ql5RsEk~{?`9D9{=ZO{V&1vKX2lHHJSgJ0SFC1p8y096I?Y|?05b{XcgblKTHZ zfByjpAQrd=h&?HOAa>`R|6Hv9XB30N3RxDY7$AV4en1PH(j<7uAT&Tc4G=&q@-F{c z8i9e$01Rv(35=ybe;NM%WdxES!M~uG0dj%y@b5pvikg1_0mOLw_HE>d#AF}?ph|!M Z0{|%qc@l5wel7q2002ovPDHLkV1m6PxaI%= literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/im.png b/lib/ts3phpframework/images/flags/im.png new file mode 100644 index 0000000000000000000000000000000000000000..7c028b64301d951ec084bb9d10ae4073af46b9fb GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~o!3HGD9`RZNDYhhUcNd2LAh=-f^2tCE&H|6f zVxTHv5N2eUHAey{$X?><>&kwIU5Z;q*vsHuEdv81qo<2wh{pNkJ4X+EnBT~BpvC)< zy|D1%cs{-+whao!`({ZQ6>x4c<~WgI_@~q~Jt6J)xetmYqZpxp(-_Pydm=e|h4MQ>V`!SheQBflqIx9~wOPaq_^2aF-Ux00G_5J(nH1W}^IvYRf()C%fvmO1KYTc8*w%maE{{%zpPH7~zuY^=Kcs(hW1H8- z)h6F{=85a$^Rm_w|Ke?reTcp}=SPMMyZpI(DGzMxIoC7xOj+i_aIwWJ!H?Od8W=td Mp00i_>zopr08{CeCjbBd literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/in.png b/lib/ts3phpframework/images/flags/in.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d7e81a98d705da8d7054e77e7d311805659678 GIT binary patch literal 503 zcmVl^KlW*80IEmzVa(K3*_6 zG7fg0I9Zj&0woGah`r_&Kwu3FK=xChQigwjfh>?7kc!_h@)sEWW@MKI+5iwhEdRtz z89B8WSj7JS|MwrL=l|b3uZ7Osk^B4auaUxSRgtG4v;Y11_x}$gi|9Y8?EnG9`1|i) zCPv2p|ADsrhuF4k`@Nr^zUpfTpS$xp!A}Wj4A3Yb2~_s}<0pUsVqyY2p8>1`g1&zJ zsVvR4Yya)fUw{4wtNss>0tLxGfB<5GmnRH!zxweH8<(Mq0N7e&^ba6_ z7#WHgIs!VLeti1p-=9B!fB*jb=l8$ge}LrQ-#`%%`S%Y9{re-sFSERHIY0ohF#KVF z2*K4Ml>Ykz*ZJq)UtlmW{9*tIAQm77@e!0?Mfa}mS!7%=x2KmY(S z0M7pbTSu9imq`K?75n7n_u%3MB_szB3#hl|A07@E6$<6-<>30S5egmg@cSkZL+Ix9 z0st`p&i@3O7{&wE8wU3C1oia^`T7CwI=ckcau@Q71iA1L?!*;{7^Nm*&$?GLJd|NjPb3=0E0KfCb4 z#nAu(05Jg0{{-+tQ26`)!^rI-{P_e371QGlF%9qW_x;Sb;QRdk{r*xJJ*zy?_rWgu z&$=$1lYjzR(%pFe+p|Ni$IOoG{eTR0XemVUia$~;LXXZeDO z0096o0M7peXKyl1N+90g@dyY5ARP|N&gc{s2^<#+#>?j*9u48*^A9U5-*(6(kqBPq zC^|QM0*Gbi#3NFYyicBf1{wyk;Wx-spzFb0kX0fvuWZ`CzyHEKd)}3G%ew&r05Jg0 z{{dZPvvzMc=Kqyj=IHYZ2nGGUe(f)J7ZwZK+v5TV s=!a5B6v9X#`iH^&14cjw13-WQ0BQ>oQ(TIK+W-In07*qoM6N<$f`&OQ@|4`Xj5kLT%`al?B=W5P+gNxB&nJfgpGfTOih;e`=>T z5jZ8;?_>v5xi(~iU^udv!6f5$jpNVh2O?$m1OPDr&i?@Y{r&#_{`vm=wBfh>{Qda+ z{Pz3$!R5rm=Ee2+`0e-d?)LGV)1LD5^!4@i=jZ1F2;wA$U5|F$_;B;f&rf1p(qbG! zCte(9VPa-y5lGpbZn4ZFhzcT9$vfdqmuG z@j$ZG>u9())mkwqmYHSd7eFi*FJ3%$?AX0~_kM%HFED^GKqQ#;=g)7T_f%9=fX)F3 zAdr)QMoCIaf{X{6{|BNG$o>N%f#5F;02KoS5XlH2zyJ$0KZ{``H1_}i002ovPDHLk FV1nFR>VE(L literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ir.png b/lib/ts3phpframework/images/flags/ir.png new file mode 100644 index 0000000000000000000000000000000000000000..c5fd136aee534ecb59914e336cad18d18ead2a4a GIT binary patch literal 512 zcmV+b0{{JqP)r;gUH{1e{Y(x2_S%2fSMQ?7@vH7`tSc=xS~J*|Ni>>`_JFszyAFKs{8d9NdA)L zm1AIH00;mv0M7pew_3Ln1`-ek5ajjb8VVZW^Whu|9pCfc910uY_2L}~8{YEX9t$4Z z@!Kj9D)d(L0*LYN-@lBEj6f&-|Nox>4F7-s`Ty{t|Ns8~x3>Pz!S){pfXY67`UDU_ zOc38f#US*GW&hv2{?Eqpf6;>f$N=n5fB<4bR}BO)G5?=F{eR-b|HMQT_5c3^H2?$< zb1geNgNn-kGiMln{`!CM;{TsNL8PAke-;*?JV+Z*eYOvhv$==KunT1sF?AKYlWZiGf7_{AKv_o8k9wMiBcC z1B3*kzkfmK*Ds)AfB<6r3XWMgVnF4hNdW;sfB^vU;z%SnI0)(h00004s{hykP}!$Xp8x`g@iqg4NJaUd z$B+Mm%>4cD_iu*Zzrl=O|9^qF|9<`Y547mdFIIVlOMCYL1Q5sui18rvfj0Ph3vJwt z)dnUeruXmP0|XEYv&@95W{1bGfWG{@sKWZ+FOVO6s`df7U=M&0& zKrFzJ{`2=AL>j0Rra@ocD)4hB}-f* zN`mv#O3D+9QW+dm@{>{(JaZG%Q-e|yQz{EjrrIztFq(O~IEGZ*N=lh=;=qSyMwWku z27eMOW_|f0uQeek^e_9g7KH|eq{JVG0^UaP2Jy4}|L^JL_3uCblfVD@mnS9t`~UCn z|MT(+KT|KOfz%~1cG~~^_2vEk{SFNQ?Cjg~|NsBWy<%2im{vp^YdeS=hk-Cyo6U`Hgs{l4FR(w#{P`G0;a3G2@YSmzI~fu(ZE1> L{an^LB{Ts58L6#6 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/je.png b/lib/ts3phpframework/images/flags/je.png new file mode 100644 index 0000000000000000000000000000000000000000..5849880b2e24b41f0c2493fa056966a89221c3ef GIT binary patch literal 471 zcmV;|0Vw{7P)&`<4h|q`rIrjv$UM3d({qX7E|Nqxg&2GgR|NsC0@5j%tl1lCgXua>7 z4N(cz@c;k+pBJt^G539>ZukA*=||mJPg>HxA3Xg?!{M=+&yRCg;K~^P|NqYb1OI>j z`OrG))4Y|OZng{zjNf-1;!Ow@tSw+KQG^SViWjK+4}4D{Sc9FI}SWlv3_D5 z@bl6QxH4EMO<8bXQ0rxE{@=Inksa{&-G`TP1@{EBK1`Yi)$se_ljq*?52TDfFW-pi zVhH%WV$%aD<7Zy6zaKnid}IdH(8MNz3m?&j7vpMhv*zs?-1g N002ovPDHLkV1hlX?SudT literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/jm.png b/lib/ts3phpframework/images/flags/jm.png new file mode 100644 index 0000000000000000000000000000000000000000..7be119e03d203695325568174b72522124bb2f12 GIT binary patch literal 637 zcmV-@0)qXCP){QLU}0{l%6`#}@@_VD{9F|q;xF#yj01gpgWJ+A*m zGUYZW{T>4SpXmF{^Zon(`}_X;`}_MY3j1pm`Wy}V&B*(tydCWT00ICp0M7pd0000m zGd#J&@cQ@tG$8vu6a54J`qTCN{{H)06Z&u*`VIU0o2UAMn)~_v4c^|~0*D2u;qTwS zKYspMz5CDEtAAp>e+Mf5)@1$t_wR2Fu3uNK{_0!&`}CDxK-+|V{{|`s2p}dP{`2SW zZ!oxe=XdSY-}fK=Qse!l!T0O_!(X}WAk`4?=MOLh7ytr@32Xz9{pZ*3U(#Z~DiVIl zOa8j^;n%JAKjo!$j^Xni)Y$z6WB&k?3JlvV7}WR}00ImE Xyv9Bjb9W)}00000NkvXXu0mjf@Xt#6 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/jo.png b/lib/ts3phpframework/images/flags/jo.png new file mode 100644 index 0000000000000000000000000000000000000000..11bd4972b6d5f134045d4e8ce134601ea9b5654f GIT binary patch literal 473 zcmV;~0Ve*5P)M00|Ni~>`!`VeZ#eh`f&K#;fdS|pfB*tJ>FU-06DIsWc#z@uumAs9{`&>~&q~MQB#;&cfB*tH z31sk|Jq+K!KjiuK-^&B5YLK~LCjkTy3s3{|pFco7yH$Tr@L>D>cm1y|D}MvS>F@7f ze}Db{_vg<)5c|)+zsmedM_Y~p1Q1BWd$vDo!X?isvq}Pk|KA^w>VH5L!1(y{_x~TD z9$-NK{r~sxzrPHB7ytr@v6F$JJdlAwh=Ji34E;f3{DCq4fk_4ifB*vkxQ1J~H9>i| P00000NkvXXu0mjf0T$ba literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/jp.png b/lib/ts3phpframework/images/flags/jp.png new file mode 100644 index 0000000000000000000000000000000000000000..325fbad3ffd3075a4a84d8d898ad26ef7d3e0d56 GIT binary patch literal 420 zcmV;V0bBlwP)9whYk?f=!Q|Ns8||JN@lTD;`{R1ZWk|EGa3dAO8ObDh3E3Cb;oH_5X#1 z|NHy@|M?558b}5Q|Cf`4hZv9q2p|@?lb|{i68>{>{ol0lx`{mi O0000=G`P)0NEt6k^VGA)9E1hT9ocRoN>wSfaWv)?-raRm?)Slj<6Po6w} z{P+i;{`~nD6~!ec29@~tkBNo($fnHz0mS%{fq}QS{_m4#|Ns2?|K~SQUHy-* z`HU>AfB)8feAoK(-@hL}|Nr_0bQ_Dj+^xMk00K}f2RQ&hFc1JYLgN3=lsJ)M+p3uR zWC5ydAM*!ly4H1hsiEFvIU^1)HH_GYz!QMsIYt5i1c4ZMLC4DfztdU-q)WGxxg;|L zi3%uAJ2fm~N8&I2%AdL;`{4^9#;e!&D=C-&Lk8;9|NlO`c*HPy9@C${KeOWnB;`P2 zATRu9VP-jSWFD-S8kh{*zoh2aO#pT8A2W0o(w3 z|5bef!~)j#|KF3RAf-U``!@sYUq;#A42-}3UHtv;%kTfcfBpOQ3nmTE<|1vNq z0ns0zZx{dq2xP;5h!=kY&G_~A;V*`-zy9<8Vi5ZI|I4p`PkusG11$kMn1KNxfWUtE z{TpHc!>?a|&i!W8{>5bQ`~TnHf3N=fz5nHq%!z>% literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/kh.png b/lib/ts3phpframework/images/flags/kh.png new file mode 100644 index 0000000000000000000000000000000000000000..30f6bb1b9b6c5bf355f67a17531fa73beafa6639 GIT binary patch literal 549 zcmV+=0^0qFP)P;@arD~1pHxO_zPr1&>t9y%wPZrAQpyS3=Ms1K-T|%K*j%o>i_=z2W0&D^Y8ax zhQELQ{rLl7|Ns5_-|C4+00M}Gf#D0s|6k8u{RAokD*W^JKSaeZAp18+HBcQ8{rdA) zTAYJ{;SE3lu^j&CtN66?*W<_k{(=kvTJiVSPc{h&pyuy)ZZrJ(`}gOM|G$0#rP$=; zY#H_d1P~L*>3>1SGXDMzbOE=49-9EaM0J&9T`emwH;=g~P!Ocy*DnU30tNVGcpOKXK;IXGH`G*aB%Pig@_cF{AXeP^XnG~{rU5QfdL?ZSim;?VE{Sy z7c(ax6CWR+s|%BWAkauYPfsQR0VXz9sPSMM00M{!7+@fO0fqklmFn#S3Nf;>{s#gU z7DjgV{|pRrOO`MKef9?wUO?vn1P~(w!@{x_lZQ{f0d@UhVEX<0FF08K{sNMJKnNIa zzrX(mdR{@6d*A9+009Ja65pra?7SkZU^!3-{)PrTC`>`Y0b%_6|LHH#J`sQb0@|>a n0T_AEh(trkF%3aX009O7j5IT?Rho+J00000NkvXXu0mjf2r}#E literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ki.png b/lib/ts3phpframework/images/flags/ki.png new file mode 100644 index 0000000000000000000000000000000000000000..2dcce4b33ffe1f40d490cb1a2e03efe22ea56155 GIT binary patch literal 656 zcmV;B0&o3^P)8t@U|NsC0_uv12fBrK4XN%za&+zB_ z{P)aXfBpaa=ii?{|9}7d|NHmt1seeZhy|?e|DWei8UFwK|L5=jKYtkh{P{kK2}m(L z`||tm?|;94|Ns5#-_IY+QnEm+00M{!r2OxHhJR4iK=k+TZ>A77)*#lue}Db^^$Tb= zko*TE|NI8J3LpSOb8Z9x2m%4nC$a{*Gqe90@enzQH`ta%tA$7d{Sv5iP&~x@8dMF~ z(;dfh&fyCHF#yj00oRR-9sTeJMNR(n^YxvS3U`G57YhIQc>VqR{rPSM@(uBDU=5F+ z{|5&5hj_*A{sI6o0M7pbe|x2IYDAmA_)R$v7XIy^!u#*~18PtP{r&xWnDPVw`hiji zey{rM^8J)$5m8%@0*JA9=57`qRaXnayHCFDJ@r1pR}vUfQ&&Fu_xE3Vu+-%{U$!58 zQy3!)avcufO?mzv0@a%gl z1HS+N7!IC&H?5`#AOHX{0M7pb00(zPR4BWp;5AJa{{8^XwcY>#`eSh{`1tyzm&^bF q{8L{tuE6I1;oe7AG|b!z0t^7P6ga05`yJ%~00001r;P)}L!W`0l>rF;|7So3KrTcC!ho;=0*Gbf0S0@;YBwbYEs=i=3_$ev|Np-X41fOr z{{tp}0~v7g%iT=?0mQ<{9!@# z-(SD~{`vL)_wRqdfBpaan?+XX#@15+0mRIp%kY{1qrJR?xs-YLhQhSVzk_f6;`sgh z57Tc3#$ODKzZsZ*F#y?2e^`Y0-&}tV5I|5HD)yDjz7nmS1pU~~aVF#A6wD&YtO00ImETIDprOD_2B P00000NkvXXu0mjfKOhx^ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/kn.png b/lib/ts3phpframework/images/flags/kn.png new file mode 100644 index 0000000000000000000000000000000000000000..febd5b486f3f90056637b23caa26d838fbadd7d0 GIT binary patch literal 604 zcmV-i0;BzjP)h(K@ANy8uaQvQ^_nWcs z*Z-GL)eL_?t_B7*Kmf7KbznHW;LqhtK<$TZ9ej=*TGLweOZn#S|EVB#AOzI#2dDw) z)4xDJ00a;t5ND<*{rU5ogY7p9)8EGU->gS|Gwk>cG!LX2Y%nmu8NlfEl`8-N#0ZOI q248)K1w0H4M?n#d6+r+%fB^s&Q!OA|2rzyC0000Cs@aq@DuRlQczuzDZ5@KTj2q4CTH~*`MftCON|DS>3-+w6c z9|(gO{~7-O`v>9vKX&5_Kmaj*WME|P@B8=S6~kYUnG7sU|G#_z>G}QV|KC3#^7rq5 ze}4b}^_xjb^7)+E00G1Tlwy4KiiwGVIVgltUY_yi&tI!o|Jl8p;n#15-@icU*KbCk z6Mz3^`1ON{fdL?ZnEnBs@%JyYzyH5mxBfr4|7*t%=AfYeY;6C2{Q{!DKY#uG1wwy- z+}}Xm3;+QH(!lWNFN3HElfM4XRjZk-tp4-xFo=l!{|(XrbkBdVP9XXJ&!0aG3;+Sd z1adCWtuJ2w-n@w=AmGpT?F`?)|9k%Y-_M^QML&N+RfBB+Dh3E3pdT0*fp#rf!j_o$ zUs8faP3>oG?f*A#7{O)(oeuE;(0C>wVqgFWAQqr4|9E)*J$m%5y#o|bz~KAxm4S=v zKP&4$usU!k0b}RSKS4oYgaHH)%c-;9wWL`3={r0|OreK!5=N4TMk7RCwBA z{P_JV0}}Z6?;n_iu%H+Q{s1vR05Jij`8c?M=-GouSI=K${m;n9!7aeW#m~(x$j!^i z%zXLG*~fS9it_V|2?zl-00a;V#NgMjUvJ&I^~3uQB4T37ii-d5-u?gQ&wmw_XD?o^ zUAsm=P*7M%NJdr`Xazt3fo%By|Nn;%AAbM-ZD?YwEG7BBxA%WR!T-FR|8><0Vxn$d zUT@yKdH&)BP#Mq$fB<3y`hl076R7Rgt5;mSyo`Gea7>-}|M%}Nf0%y${3VbO@hKwm zAWK?W z+TGp#<;$01Vv<1pdP-ancZ!Qkd3bmLHK?nrgX5I}Ab=R3zkQ1wk#LIP517FKVgLC9 eRt>}e0R{ktF&Q^6#MUGL0000@P)xg`upqGzh6Lx zERQVE2><~E((v!!|G%IA{@M07EI*V_ln=0RjjdN{oM|h)7EO z{{7?6U#34`ML_iD4-=RKMg|km5|FijfgS(|AV#2u+YAh13=HqUkqe1m1{eb(!T=Kl c0)PMm0G()MDW>>^I{*Lx07*qoM6N<$g4p`a`Tzg` literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ky.png b/lib/ts3phpframework/images/flags/ky.png new file mode 100644 index 0000000000000000000000000000000000000000..15c5f8e4775b2b68e0360c1f4ff1f37e61611276 GIT binary patch literal 643 zcmV-}0(||6P)0{QUt82Kj15^L?rd`uqE`oNxjFF#yj0 z1e^fI2i-9Q(8&Vs@%;Dq2on+Z{QuwM00sm0@b~}!1OzrH#hk+X+Tt}E6bb+U_yPbi z0M7pcvI;2tA|wYPA^Z*x0300&EGqR875n@A_WS?(`uvdQq*oFTSt1&p=b-!h{Qv;{ z0st`p&i?}PGCBeX39IA)-~tZg`v#`;6$Tz2`uqI%`ThF(|NnA-^wR?L!uSAshx_~g z0000205Jg0{{tif814-hz}E2g`1#%M`@GEN)${-9=jr|Z1Nim(?*qH%B39iMC&AR2 z>ggNf+PVUWW##cF5^jI~{P}h24a1+`jKBZ?zJB$|uU|aB82|la`WF`1`0wBUe?VZ- zRnf8uXahh1u`n?Z4sYX?@pO{;x_8g;0tl$# z2T+})81L`j|Nel$KOpz_zrTNegN!(Tli}h6pb7teeq#K~3=Aa(fB<4-V9 d!wLWb3;+YKCC*ol*cJc)002ovPDHLkV1loTFLeL_ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/kz.png b/lib/ts3phpframework/images/flags/kz.png new file mode 100644 index 0000000000000000000000000000000000000000..45a8c887424cff6eb0471f5a1535139b965e241e GIT binary patch literal 616 zcmV-u0+;=XP)g01!ZoKn?Fz`<^k#?O_Q1 z`=8<8e}=z6_5c2{{QvRuAM+0emcRf01JR%V|9}7gKjGLHfB<4)U|@L0AiL;yFw=jq zasNSbGXHKG{NMlfpWxpAK41Q^gEaj6FV4=$@arEy05LIu!2gi{Gync$`1$|;-~a#q z|NCw7f0xa__4fbPn*ZDO{onuJ|3JnA^#F|o2q31v4F4ql-&_7K9cVD)|KCjiesKSP ztM&iLe}uy71TZ z|DRv){|H?Bqoe!x=x?wkfBu3(0w91`fMN3Vzue;gQUCt^|MZVr;{RRU{|DCmOS=6} z@%2BU5C8a_|1a6~&;1YB8$b;J0mKBvKpP_e^#OhS_CN2(f86)~DtrE)Yx94t!v9OT z|MTDe69TJd_zQH>zdry01PrBrQvYt3|7!q-C@@&>{!_XBPnqZ6E6@K61pYml`M2uu ze+NdW22lI}1Q5_kK)3wPV4bt}e=;a?{xkdrMUmL?|7pP3c>kYY0b~&4U$AjN34j1% zWLW#P{SRLy(>r)v0s|Y${(}_LK*N79FfcIy1Q-CnX{(%t#68R664G@6GIJN-*24NrwB3rW+v!ydOp^Ef6{n_)(btIFVjHa=pdp6)} zz^!@$h^2tpKUmc4)64h&|Ni?2LVth#{QKwk-@kwU{{Q=j;qSk{fBpc`pWlD@C4}l3 zHUR_xF#yj01d}3g9TFqw{rUX<|NZ>{`TG464+L38761SLpR(uO=J){s0Q&s>`~3bJ z6bIb^kphT`;m^O{e;I!LWBmP>@yDNkj7*H>@iN-VTtHNrD96Ue^ySz8pMMxZ=pRTg zKmaiT)&Bj@@b3@E4ZnZ>72sjw}n?8s2>T{qV)lKMV{%|NQ?241(YPff@h;hz00Cp!a_N2HF7% zr}y9gR8D)C;wJ_Sx5=xYz5MWpi4mv-=yjk&KmiL7K#aZ&_9^w5@1DH=3l15e)xUl~ z-0=7B?|)4HL4sh3f5LL21siGr0*GY=!$H*K#ZR;BJ~dv8zS`wDeeIR3;>1y|KC3hK=RLTAp0Mf zWcc&@%EC-|_DIyI;S5 zZoa(f#*3@};Q;9GfBygi2&4h78pxV*WHQhn|Nk-k{`>pa-{0-~I{v`b{|EZ(4?qC1 zz%@X;G0|`0)6dV-R;K{9rJ5v|%9;KB_nVP{8R7~c2@pVx*BKb3t8)H6dH@UxP=NgY z{qNULVEFv`^^@@rIK+N~gX`}f7I~!;+fM-m5DPFa{(t+%C?E(7W+q^;{`t)a3di3} zzd^yz010JK%>4cT^&8LzfB<5=h#HaqkRlk)Wq^@D01#jR5K~0vg#SK#00000NkvXX Hu0mjf%Ubyh literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/lc.png b/lib/ts3phpframework/images/flags/lc.png new file mode 100644 index 0000000000000000000000000000000000000000..a47d065541b0d998da832e1981b479097a9b36aa GIT binary patch literal 520 zcmV+j0{8uiP)#-NSZgwz{Qh@GSmETWyIzrrS!pkkqDDw`CEYY|A1Dxfxke?UP9EWUlaZ{K@! zXYTRdfWyFioqIm+xW_M=V(hYvbO?~5ZHkEt37xh?rY1@PqM%`P+i2w@_wXJJO#nzA zheNt~+w1B3H|fqGMx=x!lms6SMy;uqQ4R``NH&49%B_cditsDHI6DHXLNubec}E0~ zb8dy|txA`rTkZMN{rCM3FV`MqlfuwZrz$X1)+?ntF|UvTkD3M?FxSaem74ag}Vzy5sA+`s~GjvDl6>(E?=UGu{=w?r5#MJIwhn?GrT#s zeRSo}v&#TUmcjL&mxEF>2%EIxN+SI=O=izlM$T5JH;yv-C%^zTfK|9CLa`qJ0000< KMNUMnLSTZ|5$YcR literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/li.png b/lib/ts3phpframework/images/flags/li.png new file mode 100644 index 0000000000000000000000000000000000000000..6469909c013eb9b752ca001694620a229f5792c7 GIT binary patch literal 537 zcmV+!0_OdRP)sI{|q~>>TZ2$qp!tjsj_dmuxH+6SkQv+K1^Dpafp!@&+{r#8W&u@@le*a|z zk$)I|{ss9HAb?mjKkZQc#wYOgBhb-*|NQ;?=l9<~e?U(E{r5MB3DgSYf*4|4g1Z=I z0R#}sfv@v<-|;cLeDMG8um8XQ{Qv#?|LOa4L zz5|hefaLFgf557NB#8Tm`R|`M3=9AP!~zPVKOloaN+E`UP5lQo8*B+s^WVQre?jpA z5I`*dz#16EB$cmd~05Jg0{{#R40Cvt10RRAw z`u+d`{`)-+^5ON`yb$y0|Ni^@{rvm=`~3g>{0I^G8ZGb(2;2e)q~Yi9|KETAVEFqN zi2na)xwe&yo%hS1uZ%yx{Q;WF%<}W+mp@!0-yWP|m)B$h2q2(_|NnsK|6hiGKudoE zUGn9{-`6*oEmQ72y~rUb^YQPmzn@?Idwh=T>wA{p3V#3shzY3p@87?_|NZ&@_cy~| zpmRZP`1kJ@JI^@8AFa{s9OeCZO+ts{j1?4b<=-Nd5zA z`2GL?FD{9%H@9*#{QvEce@o8hC9n7wpg7QIfB<3vS^!iHvfyK-U8V5ZH#le}4S|yXQB^Nt}W|`Q?5-JjKDn&hY;a$Df~n9-WqyG5z}b zHWMS`AAkU20!9tv|KD6JO#c`e|FbePFfi-*z4`Hxjg_16|9{rM|5*PsvHt$a@%k1I zzvO#v;hz8j#Q5o)BT__yf)$AVfxHBa6HpujodJ?%_y>%AV59*A7yy`5b5c`Z!JhyC N002ovPDHLkV1l?nIh6na literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/lr.png b/lib/ts3phpframework/images/flags/lr.png new file mode 100644 index 0000000000000000000000000000000000000000..89a5bc7e70711575c1ee3b83cc2be7f0e1fb29c5 GIT binary patch literal 466 zcmV;@0WJQCP)2Y|A4{o z-@kwT`t|eY&mTX2eEM}Kp+i!_T03wQQEla((gZiC0cs^;{c3|jAkj>009Kl@aN){ zC);;k0UG-E&);Xo*&wq)roznr3pD=Ezu&)DC1p;}S_BY4jKAN$W)>8Nm;toyKW@Ot z#Ps&Y4S)b*xg;zq)7SR<*)x!NAa?^@4{|ZkY%l|8FPQu15397y$%U%{0*LYZ>zAxx z8}J(slm+_X#f@tK0mO1iR9wET{^!#tU}GSb{Q3uSG}s1+e?a74b~(9Y%Qpf95aZY9 zPuWDo(ENa58%O|%pI^NU5I`*FB&GkLM&}>Ys6}P~0YHEO0B+J}4VS0Fk^lez07*qo IM6N<$g3a05u>b%7 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ls.png b/lib/ts3phpframework/images/flags/ls.png new file mode 100644 index 0000000000000000000000000000000000000000..33fdef101f74e38e2422bb85dc8a31bbf1da326b GIT binary patch literal 628 zcmV-)0*n2LP)NT^udI6UX~&u_xA`izWBNXkIq%g^6Y%Pu{8|3yZC zlYxl^Ab?nacHcU-^!@XD|Nj5|`r(zVwkHE4BV6^xC+~{3+xxCp=yQ1PA~B|JUd6PoU1p;PCnS`uh0!`Wt}y4#)fi z@&Nb%0tn=Xmv@c{N@*}KFh0M&kIP1>YRmQG4?lbdn);9N*SEXB6{h}Te)Ie9Zy;d! z#Q+chF#yj01pfa18Ye&*C_n-K{_XVnU82h1@caA!0{i;;`V*Y{7}WX#^!xhz{Qms? z{`>&^00Ic4fsvW@|G$6Ruf6C#{P_EiKfi$f`+Dz}{FL7;Z+`vz4fMjFe?Sd?fe5Gp zAb>y`UVr&E@!c&!7K){sO`Ozkh)melY+9 z5KGUl3lpbYV0iHf6xF}JF{n*t;A3C_dhHtn&^!?O1t$N2Nj?UE00RIWZBXJNY9>Gc O0000 zKY#!H`S<7dzuzGA_xCRl`Rmu;Um!M^l;`6=xPSp5fLIuQF#P%V7sv;y25A5(1xW+7 z{Q_w~Xakza@Pz>&fLMUe`uqRSpZ|Y=>VQIE8-4+ehiC(l5cdDyKm=3_5I`(zTN!l! z|Nj0Es0O49Xx6_!5M^MM5b`fj@gGk4KbtNx00a<=83P0Vn?HYFf{Xx4|Nr&tKga?w z11|FC_y0eCSvdcCFfafF5XlYnMRN&=-B{`>{W0U03nA0WvHB!TYz`r< y{$OAL2q4DW;E4VQBmbdt8IZ(*2pDGo0R{jiB6maa(%qQ=0000! literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/lu.png b/lib/ts3phpframework/images/flags/lu.png new file mode 100644 index 0000000000000000000000000000000000000000..4cabba98ae70837922beadc41453b5f848f03854 GIT binary patch literal 481 zcmV<70UrK|P)?-#?r-wgj45C|ZESQtLMVW?~Zs{a4) zALIXj41fOq2a1uNeOQW%&E= z|DQh$fB%40fEE4z10q3;-;ClCKpOx8h=su~@}N>;ErcnO|V^hXG82+5i4Q+5aFU0|N&GK!5=N X;lz1sunOP500000NkvXXu0mjf*7env literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/lv.png b/lib/ts3phpframework/images/flags/lv.png new file mode 100644 index 0000000000000000000000000000000000000000..49b69981085ff54568907cd51a56a1e5d8b01ada GIT binary patch literal 465 zcmV;?0WSWDP)TuF);jrk;v#5jAUY900)zv`N|Ns31p}&9s{rUUv@1OsF{`>=? z-@pHYNg(_0@82^wZ2|}&78a1v|Gz$Y3Q`J0Kshi8lm?N%fQ(=Ne*FS+xn*U6mIDM3 z3(!4({{8=rtQsf{G!?8Agn$gN2Dab7KQS->1Q6rPlP7uPKoY|E^ZWmwzrbJwx)~sVSb)*-|Mwp*NlCCVfB*i0>4ZfB zFhYR-gakS;`Tzomv6O+~6D%TsAw}vh)M$o8KMw-~K!5=Nd?b|fr64Ht`Q}{`DrEPiAAXl z<>lpinR(g8$%zH2dih1^v)|cB0Tm^Ax;Tbt1Si*=Q}{E#k?FvXz98l@;}5~lm=?U& zG-}|S|Gl`lShA(H^<$er9P_@~-^V_DdmG*6-Y@sigk?QrOiWCI_=%GzADT2wykw9t tC3C|94FSnevC4oHhRgr$|4V2bWZ-9WJz&bE{tsvygQu&X%Q~loCIBwYQ&Ru{ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ma.png b/lib/ts3phpframework/images/flags/ma.png new file mode 100644 index 0000000000000000000000000000000000000000..f386770280b92a96a02b13032e056c3adfebfa18 GIT binary patch literal 432 zcmV;h0Z;ykP)@|4`Xj5kLT%`al?B=W5I`(ov;U*0{`Ko0*gOW1x?dnY zU=0kve*-lD1P}`lGXhluRs8wG@Eb_}{{H{>S-s!?{`@vR^^5K2FR(pO4M5WY0*DFZ zqCZG(`2G8?)UU4`zrJw%x*-cw4MBhY08Ix7Ah47E{sH^x7s!U+ztwL3`tbkv-#@=J zum1jzWCJ7ENdN%^b`n?!@|4`Xj5kLT%`al?B=W5I`(ov;U*021*0XgD3^5{teN< z@cTDV13&<=05Ky_HBiN$KMcQtBo?#b8i1w)1P}`YD=UMn?0*)P|9^oV_=9jUlG7n1 zgOt?2g9iZui1GF7*Fr)<|ABx33~>V{CZ_AxuLA@S%fEmBAbhajaRP`eP%%INfiyrk z1T_G`pFe*90tjjYTn_{=GBPrt03a7C3lKmc4KH52_yY$2zyM+rgbiXafFO_o^aD@| aAix0StzUbk+v2SN0000M*00(~<{@!wBU}0eR!0_)M!#^$%c|o1wA4mpD0t66HlA)zv9Z3HD2a;wKuKxf3 zKLhV?#{Ykr|9@us&njQ==l`GI|Ns8^_xsQFIm-b82&94GKf}}4zyJRI4@Cd}JZ511 zyk#BtzrVk|eZKww|NG~I-~WGu5Q~)bF9rsH0Ad2#1T-6>`p@704FCVWdiqyE_S>%? zzn?t&`v3p0|G$6zgQ7n`R{;bN6VP4%{xkdmsRoGvjXbc2Q*ib#i+$e?>}LD_@7KRy zzyJOE_4n5=pu>QE00u!g^X8F}1){Re7)$pq2>GzRP>hz5WF0{Q`{ z7%1`&WXbP;Kox)fFb41M1d2p!76A1_RfAN3oCFX+Kn)0M$tdvjY9_8yH^9QVjn@nfZP*{rmI(|3?O9b~d@cVAX$tK?&3V z5I~F!3@gDg2t{v?r~d;6^$&1NgV7;ZP#i-L5C8-i0C2iwRaxXp%>V!Z07*qoM6N<$ Eg62#MXaE2J literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/me.png b/lib/ts3phpframework/images/flags/me.png new file mode 100644 index 0000000000000000000000000000000000000000..ac7253558ab939481a85cc06dcc4d73503afb9f0 GIT binary patch literal 448 zcmV;x0YCnUP)l$FJ^m&#tWvBA4C(n)b76qu^ z3TjKJi*u=MzAs;^(tHqT@6cZ|8AHxz+0T%zR}I9mkc`8faCz48MN^H2?$<3((yN)j;^`52o304M1f80R+|X9}Iwu*X+N)@cjDs2c+lU z?_dAGBv9wyKfl@e{~Tjr001}1@4rADVDj%Tpgtwu-=}^u z00a=o2DtG+^6#HtKYxRyQB?o^{pa7Gzs!FaUNHa!5W-0i5^f%h1nT_t=O0kRUm#-m z!vGLKP#fTS#5u+P{rv~@0nkXGhChFS;q~V)5d8o97pUPE13&;VK7Rc89~k`k^9M+( vx2Q8b0Y@nl1JFDW`UNKcfk_?)fB*vkB(P&2-J7g<00000NkvXXu0mjfGX%sy literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mh.png b/lib/ts3phpframework/images/flags/mh.png new file mode 100644 index 0000000000000000000000000000000000000000..fb523a8c39d40401b9abcfb144a73cbb2d76b286 GIT binary patch literal 628 zcmV-)0*n2LP)qpJCu1p{#SASZ=3QjbPhlO05Jg0{{#T~{M_a8tGL(@4F<^6===Qr`uzX+`vVjI z>RSuFND|!tCprH%UjG&-`Tqfgkh}r_F#yj01OWa1_W1j+!`}P+|NQ*@_xS<*{r*D) z#ao8o+xY#_%t@QS?0~yfBW+Wj{_zyJQv%*f2f%)|)v=^uaq0&4j6{l|Ybrbh`TZ`2ITPyN!~wBya;Q+wXO z|N8qsP{n^H7N*~zApZOB??0dhfB*t%@KJoPW~Y4ZyXB%oU++J<@%!g5F#%3te)iws z==lAQ0jwGr+CT&T00a=DyL0x5XB;1||6pKv0FF^K^bd&vjBEyg00RK!=O6aq+V@KU O0000p}y_ZxVsQQo9l8qD!tQ%&&F2zEbEdU-v3mY$p-gb*;wwp?LFG<9EeNpZLj7Q z>zeacpNZ>XJG@0bcmXcALo;Ad(L@#C92p0~G#aM!FfF0T7^YIJVVFaIl|0gRpSyF_ z@0dgJ{oT}qqUk#4;-a-U9Fej5EJ`tIE9)E!qDfL@GEq>vEDI}q@P8EmenIHxu!*Cc zLK?@%1j7u0A`mPlm`kyT;5daWs;EH!+LV0IWO0Zyj4+sHxRqia#e9ki#cu?^6YQn9 zOfZ8&4uw%r9nR_}FG?tJ1sBpnAsdGMh7pA$Mlm3ABvgz9aj&GrxaT8{1^YB+UzPEM zQQ5*0;(QnblJRNh>E*%16wccXl466KPu9Lk=M%$}%9~Z3xs9na6KbZ+^U;AoQ+JVg=BO3kgY$Vuu?iP6r(sQV=EH;Iwf|hcN2Nd zPl_EfW;$kS3zh=H4ojC&X!7Bd!`~OdX{VxLp z5dC3f{KL%rhe`hT|Cm4jfB*jf2Sk3^vlaWpqpU2@27mx!VE|eEAE^2d5dHlPWd8sE>;L~hU=6?i|N8~f1J=Os z`!`SnKmdWv|I6_A575egAcQa*n+Bj_fB<3vdK75(@4vtQ{Do`yh0yRHtQw*LD9^wE z5I`Uef5B$|`u7W{2T22%M6!Vq>?D8y0%`dD|M#!IAl1MB{`!Yz!@u8fUYwuxex*z#9GlH2{4I3~mO10Ac~g+V6jVB)KI)-uUyEi4o|t-;5vX%A}^sE-TCS?;peW@7xO)zGwLT z>;Lb2Aa%cf{|B;v0lB{!Wo3aj00dxJ4sHN|VGstA{YR$4C2D_2_!cBcz(8l;%*ju; z_5-pDt^fjRVEX=(;rPk#zkV}({q}dw+K<0~Gco-B2UY|%8?FIpIzRw{{P+Lo&pY>j zzJB!i)2Bb*-v4F%#qjU=2Y>(qX<+*Ohe1*jY|LMVKOmiqU?UkIBoO`m3qe4|00G4K6*VFmu*EK13J3rK Z3;;9iRuWt9^;rM_002ovPDHLkV1h$@)rkNA literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mn.png b/lib/ts3phpframework/images/flags/mn.png new file mode 100644 index 0000000000000000000000000000000000000000..9396355db45a8ee040c790782209868acaad4b85 GIT binary patch literal 492 zcmV@{}>pU8D26lh`@M2^zAEy;6E4#hyVhJ1te8n&0xjw|I|OmAOC9q@qGTx`1kMs zKYtki{9$JJ&B6L7=Kt^CKt7QCvS|}Q0D(0y{Qoccl;O=^hIfA-|M~k7Bn3tPfBpOS z`}Y4|zknP@Sy`YB00G1VQttboLEtY##9yH3?>`J++5f+PL6rRiQNLgu1_potVgX{t z{eKx$|NOuHhk@zOpC3@OA=>```VZI207MK7009KDfg$woe}>=xgMa@Q`TZMV99$1f z=+7UZ=>P!)^22||KmS$!{Ac;a@cR$YT8L7JY6c`57{N{g2q3VN{s9Bw7X!l|paf9r z=TC&I7$7zP9Rm!dKY#u(FaQJ)NCWfVKmVWoW?%y` zm}dTA_#*kA1?Z>0QqoLZAnySq0pv->KTM2<4My5DUW>hHBSpkmCRUfavf4zkmMxWn=;={POe9 zpZ~vq|NHajABg<@_xGlmn*aic1*nOE;s2AbPk~B-;NSn>+$?|p{{I~>&N%tu*Drs5 zfBO0B*Z*HY)v`RYKsx~fhzW>+Cjb5S_s>6&>Le+qufKnBv&bF%YWVBlABO)w3uGC7 z{rLq%Ks`WL0R#{e(EPs)fB*akI^_5N|9=)g|N8#t9~WuY61}otf4Tqs`!(tD7m$X( zzkdG%X#fZyCZKPCW&?Et`7z=QS^~DI8Yx-=nhgK{7whGvYgWDg!B8&G1k~{7FHk81 zKmY(S0M7pd06qXVA~x>%?)v`v$?y7WENVL!I|c#<=Jn<;5-<%03=swpg4MkH|N9RH z59P$=0*D2u3CIB%01Ag+CC5rQSvY_G`T70NcNRvLcR${}{{9+hIZ*cRKadRo0R++j zv0Gk5I)>DQ+oULEUYSww-@{VeP#&z`*?j4i%tcfB<3v#u_k;CAlSmszIWE zes6rZ@!!9HKqG(r{Q)GE1e8GG4GeUkhF=T-0mR5q%E0gm7LmVTk@^EErhg$tKMw;y afB^u%K|axUkwLit0000IqP)p`2X)eQ1zd`|Kz0p8A$%R z`bTZnZKVTj3KfeK`2Ky*5B&e@&)?sFfZ#7s13&;VG5q=W=RZg_5SU54z5Mv&imk%` z|FW?#NF2HHZpKX|PKHOnfBpIO`|qDW|9~0*0*DFdzkh#$Hv9ux`j_+Xe->2}ZC)XM zHV0!dKPS0Ur}$Y}|Nr_6wBa96o`K;HKmdVl_zUt7(16dMgtTSeTz>rQ?Z?ko?mjsC zklD%d+V79-zd^=BZ2$-$7GT%`Z2%eY=kMmT9MVkp=Y%mFes6g9+4CZ|=Y~?>`*tz_ z9Sja7kOqJN0yzojravHW{k?SeQ|&okK5=fKkARLe6n%c?uEf?;0ubZ>f;9XA2p|@Q zU%!CHiwJW41sn7GB}e}J|9v$?qW1mmH(Bw2pdk4RbO%twKY##YWMEE2jz}>2 jgG53Y5Cqh~01#jRFwa04;J&RL00000NkvXXu0mjf4K^ZQ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mq.png b/lib/ts3phpframework/images/flags/mq.png new file mode 100644 index 0000000000000000000000000000000000000000..010143b3867f21e7791b8254e806b325c13b2895 GIT binary patch literal 655 zcmV;A0&x9_P)$g8u&g1qF5h0Q6aJN-{1Mbz`Ie0O|k$ z{sM@ZfzzR@(T$Ir&B098+eukMUeMl2+1^x^fq~&CAE%jZ{^O4ipMU-bgNM&P{`mQajg9%ivrm#DT>t+4XJGgP z5I`&pzyAFG^Do#{Ra%^vg_(hwneqG2-w+MozW@IH=Wj)-86P+6=Wo9l8G*Kb0|+3- z*{jc+=}JF+`H7W<>HmKQUT#*P1{)(8poaY?9|3*x^4(W97N$Rc|4EDS&R=&5Ab=SE z{{73y$Ox4F`Rgyx2o@H`|EK_{=f}^#%*+g|tSlctd;ka_mU(L~nd(ak@UpY9GCq0n z(2rN05Jg0{{a96b$W_S@ACL(Rxa}M z`G=gy|Nj54lTiQv|3YG^`}+MH7z}h_G^4TA+~V=RqI&{}iQ&&bpkF?J{dw&CQ*JI+ z5dlsHn4|vw{rBhJzZb8+p1k+fF;B_XE! z42<9q0s8Xa-+!garV_&3Uw`~!Vgv;%KmY+HvKbgYfiyF)F);jMVE6!x-Ip&{0DU4U p=>-f&1_my$6wo1$fFwYG0RX)13*@;vt7rfK002ovPDHLkV1lt{Hh%yB literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mr.png b/lib/ts3phpframework/images/flags/mr.png new file mode 100644 index 0000000000000000000000000000000000000000..319546b100864f32c26f29b54b87fe1aee73af21 GIT binary patch literal 569 zcmV-90>=G`P)rBb0vs`uywH-=91j zzd+y*NDt6q00G1Vbl+cwzkmM!`SbO+m+YTk|9<^u`R%^_m*bXSzgT|#{QE0P`4>nh zQ2ZZA13&;V0loO=-(MgHX#3gMzn3`u{`KzHucyC$Kl}CT<*&8wzt6n|ss8o%*Pp+C zfTjZk5DUl#p#T2<`NjV0^vyqym4EMy|26&E??3;3WoiC?{O#A4)4zWSfZPMI0U&@t z8bCe+8UPFj-d`E(e|zZv%GLeN@c(z$rC;+8f>rHtN0D&|BIS>OtI{AOC zx$tZGDUcCBkzc|7XAeyp!)#=hy@sHz%Z8Nmi!G71?uGb{rk^vkcEsO8W!D1Ad0T=%Vj%gtVfB*vk>3V2g53(}_00000NkvXX Hu0mjfpCtxQ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ms.png b/lib/ts3phpframework/images/flags/ms.png new file mode 100644 index 0000000000000000000000000000000000000000..d4cbb433d8f9fe49f06585dc46ee15593e3e621c GIT binary patch literal 614 zcmV-s0-61ZP)w!6%f1G{My^>{rmp(`~Up={s8&+{Qm#<{R63-jRF8M0M7pe zp74|h+7SfEy9M#{{`U3;5)b(M0y+2j#`QMv`vWF8IQ;$o{QUp?{QmLR&;S7V0*Gk~ zkHv3!H5OU9Kb*Y(q-ELEH9vC;U*%N({+r?VuV26a{F4-VqagC^H&EN}zd$De1Q633 zj~FHafsLPJA96}RV-#A)X~v?X^MOAye@1K9u&05Jg0{{zeC zH7h(V@9+2F_Xpti2mAc}=j!&)<^S{h0sZ;_Q7ob~2($C-{v8Cq(%>lF*}4LV2^e-j zV}AbzqQAd@=ogUu2Vs2v^{02G<;{1(|2aQ(EV23Z2N=G800M{wXv^O}|A7VrH9+)$ z7=NG)mcRcxm)QV402KN07wDuv009Kl@CPU%A;t|Pff9c}I{*Fo2PXf3L>PYm|M&kd z6T{yxzy1Ri0|XEw14AS%MzNxQNDQDd27mwq0OfZ^Ej7^!+W-In07*qoM6N<$g6Gg9 AMF0Q* literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mt.png b/lib/ts3phpframework/images/flags/mt.png new file mode 100644 index 0000000000000000000000000000000000000000..00af94871de66cd0fbf0ca8e46dc436d66e2f713 GIT binary patch literal 420 zcmV;V0bBlwP)Io>l+&W2PtRx|M&0z2M_*#`0)SN z@BhDl{r~j~sG3n$7H9)N0I@K!uxMy#e*W?mp&AJO{R1fisrv=e1J=Os`!`SnKmf7) z1AD;K%nYO&458rnZ>;h=xCZfJy-Z2&4h30>ylo1|%C8 z!A=4QAQtS#|N9SD2R9xVP=6R000Ic%Boqz*|DhQF2dLpM!{5I^H2?v`f~*=Iag5B& z3^Fndyu3hzLFn&a2m+c75I`Us-n@D94+enNf~Z%o__l08i(?1?2rvLmwOi|Xk;8TX O0000wT1SlAdCl&Y)$gVg>5qW^#Y{{Qp$|KC6V{`~n5M8AIn z$$wxH$UeVm6F>m5>|$VWV&MJy|4IE<4N06-W9 zfN4+=|Nq*iiwn$Q2C&jbT&$=TD3oCSlHOGFpRx(;BY;>KBo9alsWSh04>T2o{{H<9 zq`@}){__Va_~#GM1}QP2gACgN0*LYNU!Z^U|NnpRpWzq7|3Cl#|Nj5~7gmyqN$LIj zQvdWzhA$B4g=Z%5I`U&fgJtk z_g|1x{{H;?U6oxK=&at?J**6@V8K5Oe}Dh~1#%?NbbtT?*#Hy-y7l+}Kff9N{$pgg z_WK6Ie}+7v0!uEdpa1{-Wc>4s0pu$NunhnK1op#kuysJ&8U8Z;`FH#0jm^LQ>Tv2E z__*)izhD3U{sq|pR0FmFAb=oF`osMFS8?ijP~d>v`s?4X6Tl#928Ghke_*>=zWoA* zI8ZS_0I~c5+3<%^Qj!Z4rNibE_6+kRtZU3J46>yw zfWS`r^B2emYGC;Nhw;yEHn21ZK^Q<1%m5k+3<-b$0%`dF7h*inus>j>Kov-;VIf zD*pWek$*v<0SxiK|NjEzfQkVEh>@X`f#DO--wX`DVCWAL;}4AS4@`0~00bBSj-y@M TF2~k{00000NkvXXu0mjf^ET$> literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mx.png b/lib/ts3phpframework/images/flags/mx.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc58ab3e3552b74d990d28a0f500e9eb6209dfe GIT binary patch literal 574 zcmV-E0>S->P)LFc1LT4cKmY-iGBrkbGB7ay`}g

Nzrc?B z!}RAj&|v@p#KiFL&)@$*g@6D2{reke@BjagZ=QH_|HA5tt#{7v1Ul>guV4Ru|Ni&u z_rKqNfHnXG5DUYvUm)ZE{sF26>H%tC6A=5t#K$G4Eg-G_A87rbKVS#^0WyF#00a;d z1Ca3#WHt~1jRI=eclPL04k1AU{avRH{sTG*Y{UOQAO!R;KmY+XFaVtnvEdic(+vNe zOr8IIc?-17*~$Z`|L?Eg|G`fB4^+g!01!YxCouq>54Pd=FQ6a({TC7tjr5F#2GXxz z|3N{=01TJ^fBykB00a<7!|#88#Ce2)8ovGh$;ikEv=>5x7)(s8a literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/my.png b/lib/ts3phpframework/images/flags/my.png new file mode 100644 index 0000000000000000000000000000000000000000..9034cbab2c02704b65fba6ecc4a7a1c1d053b6c5 GIT binary patch literal 571 zcmV-B0>u4^P)Z-xKO+5@Bcl`=5c~|G$6#Vc^TNX8-{JF#yj01a@w+002>mTdm0Dxpa1{C#)An)W@eTA0)PNwiNI_f1h_u_DTrb&+z}xv;{Wj zFGj0Cgf$G002ov JPDHLkV1gu%1+M@A literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/mz.png b/lib/ts3phpframework/images/flags/mz.png new file mode 100644 index 0000000000000000000000000000000000000000..76405e063d43f2f3b5b9cae4f76d9f1c73cea25b GIT binary patch literal 584 zcmV-O0=NB%P)d!0?ZO;SY>NM!#Sr3j;s^4&s;y01$=&U~zyG1R{bN8&$-J2AZUB z{O~2s*67}BSLujJh!l{(TM_&V!SLx<0I@JIFetqFAHVYHe9OP56#xDPD){#oDEIpx z82$bI>))?me}4fPvfQ#j8vp_bq~ZU+|9p&pvv&RstuJJil>ZL~|9}umff)b({bONa zxh{1bAb?oDF)-ZV`m5;k=ljw>l6BpeezVN}{rlPPKR~N~|N8X{NCKH4>bI<%?516t z00M{!=o3jd#=5m)yzyNB8Nca#eH--U&Fe3(f#?+keR}os(@P+F`}Hl*IRF8~0t}ng zTmN>%etWlrgX7)b6MtD}{$cp_8-#v?7>pqD50GU3{f7;x0U&@Fe=;yIS7rUbaG!zu z&%cJ+j{>}Z{{3SByBg&1|Nj_3~Dr2e;Gh>zZrofD5`!lF#Z7{ z2B7#~hM#}`y~Cfd!0!j9&~4{}>qlfDw=_GI6Spo7yiaSL1(R34j0qF#yj00RRApetkm@ z3-<^I`04Bn_xS?+{Qmv@{`~y?GBy}bOB@O2-v*J4|MK+z`}_2=vH}QbDjOT??*y+^ z9PHma=D0k1DE9l$Z%$6;=mbqObq>D1hX4By|NHgp|L;GHwRPtz;{gH)sDXiz;U6Qz zZ)V2dKoY20MNK$ASA+NcQ;z)f|Igq2|MmO7w9@DL!aL%O?v>U60R+_W>(dw3rWqbw zY+vs`7YYhj_p}$}TRP+a`c40T|N0L!J1pWwjPJac>kgmX!|;RwAb@}x{`~&)@5xJE z5fO>bZXLnj?>WlS|KEEGbn$;~p|4%Vx0QJ+&gI^Ic|%b^5f~5v0R*z)@87>RPSSB9 z(mdPNGA>^D|K~4|Gn`ysXGCuKcCvq8=a*k!egj?m?>8{m00Ic8fvd1cUx@iTXM5HE z^H)FyGjo2eEq-FG+;YA1)Puvn7=8hj{`vO@7;-=x00M~R?)3!82NxK-n*V?K1yZeJ z{HiGaByR2;1J+usHUNMy48Q{FH!M8N)Pwp9Y)wTFo81A^+}-HTA|tp~ zO4-IyLWKVGUAVv#KrFzpU^vb8&*4vW%&*_Sfp-5+Oa#jR{(FIy5oqwQKVTa`BqPut z3=9AP#KLU;d%N^MnR|>Uj{MoN{TEQv&!4|iQhqnS=35v13ux)DKYxDz{qyJVA7#|2T literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ne.png b/lib/ts3phpframework/images/flags/ne.png new file mode 100644 index 0000000000000000000000000000000000000000..d85f424f38da0678471ef4b3dc697675118bc7e0 GIT binary patch literal 537 zcmV+!0_OdRP)pTI(!tjlO;rIXl@1HV&)cpmDGW-GR z`3+L^2aNvy`v2$G|KGnEMPz|W00M~d>&=s#l8OvKbwEpiHvESG2>tK>|GyyO9|$ut zvpzk42q1u17-V!9I61+pfvQ08|BZwH@16Yr=jVTUlmA*S|6zcEk@1g`6+i&7`~&$E zYA%%V^7j8zTmS$3{hyirzos*?2B2bq00L=%yMh7Y5+V8jy0-s+|NJkm_8$o_f}Hjb zAb?n|yt|^zto-ZmuYW+t0)q+&<<KJ>BgrQT zRQ>1Q-#;J~AoS`6pHRCwBA zU~p!DquG0BzkdJv^XJbWKYo1u_VxSs@1MSW+P$@#L70Jomw|zm0SEvBhy{qNbE|>m zzkmO1v}^=;1(+C_zx@3C{m*wPUMbGM98Q`}5v}3B|NZ&>_xGmUO#lG|(!jv*|H=EO zfB*gc{`33)|Nj{n|Mza|{r>BFYe?(AfB!%K_3?Y%L>Z^l>!7{V2qIffI$#e zz5f4~?wt{_jNwA=k=tvV8m;04hxH1ClNsp|jaD0d_yUND;Sa;#|9^ps{`~n3bQJ?5 z!?D}Pp1*tk?A@~oJ0>vvX8@W7)C|%C)&LMdARGSt{R`y$`S<5P0|@TEy!-p_?>~S2 z1lj|1A`hhCuT1`^U1^@yK06yzfAV)~#?EnA(07*qoM6N<$f^cITh5!Hn literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ng.png b/lib/ts3phpframework/images/flags/ng.png new file mode 100644 index 0000000000000000000000000000000000000000..3eea2e020756c41abf81f765659a864c174f89db GIT binary patch literal 482 zcmV<80UiE{P)E-@kt#fq(zr{dlL|rVbPb$^K$sU||3VAQpx%4ArjH z5b1ya|A`BUvomopF#HDspdkBi_LzW}KMcQr|NHa%@9#}BHvt3?3s4hK`pMU)fB*dj z0+88E|KR{Ac>Ck+U;lpn{`Xs!M;2%gKmf4-G0FZ zKmdWv|I6_A&;LJv{(%shYM_N64Szv401;3FKmf4-Jp?oxsQb@fum%PU8~*%)I0>i# zs2CuCKpOsn%?64Bg>X9QKhy?*00Lh)xZD#`v33$e!wuw~|G$AI0?h>~ z1_&UqlR!#;Ll9OQ{s1-n2ZahyF+c#Z0Amdp#**BUAaDHn%gD$G3dPzV3};X@Gl13bFaQJ? Y0MmSSAW;3b&Hw-a07*qoM6N<$g7#LQDb&XuwlP!fzJKM z`1`}(|Np-K;|B=>MS!;cWf13KY+LvSAb?md?)rCHn)&`aU^p;VI>1V00D#b-){yc34R8!1Sqb680000@|4`Xj5kLTv#?55wQzKoX38|NjLffBpXj6#2y{D-E;(Ab?mHL=FGT z$TP6>fK>na1GWK({(upf0nrSU5*Gz(00%w4e}91F-@gjtJbOE500a;V!?RECzy5mt@dro_q~YJM-#~SLe}OeX zNT7y4|NilCvNABd0|+1%hF>6clER!2lYo}|1|zVkK=SXOfB*mf{rC6ZKVbL)odgg- vjKFAPVE6?_e~=h|kVO7~qZmRm0R$KTYszPUy89K;00000NkvXXu0mjfB$dbi literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/no.png b/lib/ts3phpframework/images/flags/no.png new file mode 100644 index 0000000000000000000000000000000000000000..160b6b5b79db15e623fa55e5774e5d160b933180 GIT binary patch literal 512 zcmV+b0{{JqP)O=a{vExMP2%`MCSoB^FIcLe_%lf;|~%E5I`(IQNh}3Ao>6Q|DFUXMn*>AqQd`w z|1kXd^B;tM|Njjl{{hM0zwd6?1Q0+hV1xeud-4=Wy?p-%sO`^#2S61Jzk!N?s)6X& zzhA%p|N6}=D+{y%Ab`Lc{sL9~1=0UN4*CD*7s%9KAf+JHKs~=eB-8KTKvw|-5R1&; zzd&a|ob(5%^Z$Q=wHy9p13+aOpFRNu5F>N&`Tk_-7w>=n{RejQzkfh&Kn{rf10?_b z{tFTZibx5v&dxav5I~H7|Ney-|DWN1$%1FyagzUW0464;_wU~W1Q5$TW@eGxtUvee z3vAf*8|igK9~@*rr66bh|NrkNM8z+V zAV?>O@ek;bKfu6d00KAO>U2WB)$@6+CBT{3ok>^7H@y{~6FRKmalQ{r8#S z4^Xx6Je&%q%J2@E@iKAb=Rv<(vO+e^|@#56B5{*~0Mg z>UitA{$OPI`~Tk`F#7!u z%=q{3H<BYX7n!%ts-g52=?&#%8AHvnDo=g*(t zU{@(f3Lcro01!Y-3_uV4{%5PNloM>zp5gQNALI8Qf4=|z|KsPsAHV+n`1$wy&wt;4 zfzU5-=mP{0FqD1)U3&iJ)0b~Q-+l7x$B&=CVSxxV`w!S3U{3%6(9;Y60R+_W0~jFx z85t(6Ir{$dcL`zMKcF!E2U7}WK$QOlX#fZyMg|5;)aZo7D4hMD0TloQ7ytwk}@P)zKX@%&%DST%gMqDF&nJ<9}^SM9e)7=i1G97Xij+!pc8>s z0}TKw06GH{9{>J;c@WiqflO8wrXNp!0t65X1H*raF>uuoM}q1_&UQ zzyE+PV}l7nR6|1@Xe}f}K_mmn5Fh~%KtLN9K+(a>4Du5*FzA2|{m;zE0Q4slD+}03 zKxJTefLQ+k0*D1@+|Oq}Kt_TL2e}yHXa<;L{(_vY&wh05Jg0 z{{*4}t^?F62l4F%@9_Wk_yrLV`1}4n_xiEzG57obO$p>A_3H5V5SZl@9UBk;0Q>>~ zF#yj00jE58tx;oDUfVAO7ZA^g)(IQ}Y8G-;ZB^s|2r!a55}@Ef&J{K=uDyPxdE3 z4FCZEF#yj011R$m=m{0X((d>7`QGgNy~*X%==~K4{m|+BO$p;F|LeE!5zX@o6aMwK z|0Cnry8?)1@m-dDW`RF^3@85Z{`vj)H~*iTuYX^D_V?HC|LG63e*OOQ`}ZH9+h%{4 zR^kLX2_S%&fc^l6#;?EsK&pTL{q^hL&)-1T|AVLlN&WsC!*d@f`}fa3pay^dVgYLS z^XKpHUw?lCNw6Z2x?f-t$o}(Zg7`(je_wy{{yg~C5U2qlfPfml0Rvc4oChck)CmlD zkWL^2sNoMV9{$c|wex0wa`2z{-@pGE82$nT5F-OaFayIEq`3SAj@n=l%a5DE9w9oI(Q6o;?Ey zAdrT?A3yTg*!;YE_t&FGSk*xQBO~LVKYsuMhzTgoEGG70+cq{OrGLMF<5CR)KqmnN z5F^kJDrIFqfoA{t^Z)mszkh!J|NR@N^WQHZ-f!>?ZqK&!y&{zFIzP0wn(b{R37FH0BS}Z;-(ZzkW0R z`o;JgO8$qifBy!003d+C8bA*C^$+NzU%!${e*gOW>-S%fvmgwhvw&=%Tb23#oNZ?S z2p~`>{R0^fayL*XP!U2MkOb-c1#;`ZzkmMzW%$Pc5I_(c{;*0)g6#S8SB49$^)FDx zACSQydx0wd{{073{+Hno13&;Vu4Mp{AoLGRCWbOF`~tJSfrb9TxDaUu27mwq0KDvZ UcsT?Vy#N3J07*qoM6N<$f`X&bC;$Ke literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pa.png b/lib/ts3phpframework/images/flags/pa.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2ee9a780955566cc7dc2f59ce175f32d3731a0 GIT binary patch literal 519 zcmV+i0{H!jP)|lVPPN>g8sl5Km-s#EI@faK0YA%?%RKmGG2!N|AD;!zX}Teef|3X z_wWCI{=muCn>GOi5DUcMXP^GBIsf;|&;M~|Oj3M||NsC0d;h_|&!7JP`u*?sum8V( z0aY`}$^xwd2q2IKptK|}gRT_Am!FIf)j$AI2PS_*H2ec11_potVu8EfUYUvEKf`}G z`1lE^`X5*+Sm$r%-@k!200a<=Rv$}~=bu-f|Ns5_|IfewfBrH6Nrpdvs+^pde*a>f6qSrfAZn~Z?Fa+2||AuS*t4lqXH%-ra!^K00G3r z@ax~7e+)2nAoSxWko^y??$?YNza~t8coi7j009Kl08{<<|1U847wF+XP}P60U;n*# z@9*{N{$9Nb{(u|?WHSN*&{SqkP0pSkP;|=6L&63i zfEXEE-vV95@Z%rDuRkCLl>P7efnPxL{(%8d!yj-G00Mvj0{}mHT?%@XEt>!U002ov JPDHLkV1lK4=}rIu literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pe.png b/lib/ts3phpframework/images/flags/pe.png new file mode 100644 index 0000000000000000000000000000000000000000..62a04977fb2b29b96d01ffef3b88b6bf2ff05862 GIT binary patch literal 397 zcmV;80doF{P)@|A6@UbB2E)`X3x9D*gjYK@dOyv49j-R|ARv|Nb!om5Pi12buAo@&A8j1_q}8 z|NqCt{Qv#?|DQiV^2?@8009Kn!0`Y7lcx-S{{qo}h;n3b`}Y4|zksS4Wo3aj00a;V z*zEsM)xUqEs|Es~YOn@|-@kzx00M{wh#7&Zfj0j6!|?mhe@uX+0cadR0D+zK=P$&S z1T_5l12i2VfWS`r`v>f&U-&gJf}I2qKwu{UjsNuvF#Z0+ASnqp<}V}$85tQ7(W#^a2}2+PDh3E3#;-`R r2#VqJ=b;e{^dbb<+Crtk03g5s0zF}bJ8sS=00000NkvXXu0mjfKVzU% literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pf.png b/lib/ts3phpframework/images/flags/pf.png new file mode 100644 index 0000000000000000000000000000000000000000..771a0f652254b4e891fc73910aab38967864da54 GIT binary patch literal 498 zcmV3lobsI zohi_A`bB&J!~)dH$ngL7lczwX3_#UDxxarw>LBRX?|;94{rmL`$Yzn1{l&ll5I~Ht zU%nI;6$RPw9|VBf|1(_vbYnFmA3K-0+yDOt{~_StKSoBzkDopP1Q63dnCt(82%zd$ zpFb}6I_2!oo##J&nDhVtbEpQW0tSEpVuGrMivIupee<8UpWZS`Uj1hH_v_am-&g;K z1CTfW0R#{WvT7jw$rQ2Wy6P|4+kZd)xq2}(*g=aGrk)Yxu73al#Db(Bq?4D0-N;b? z5G()RfB&pB<@s4TkY)e;`2!F@EcYcO{->q=ymt>64xqsK^^5b@FIO-F$h{9?`~e2* zUv?FhqZcm%1P~}#|Nj2NBq_=8`#0mCzd$+0-@loGnqiRuWPl>)F9-k?0|XG`aR!E8 ou!#JF#Q1|6-w+1S#{dBa0Kx%7Vg$%BF8}}l07*qoM6N<$g2}Akn*aa+ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pg.png b/lib/ts3phpframework/images/flags/pg.png new file mode 100644 index 0000000000000000000000000000000000000000..10d6233496c10e52ead975c5a504459fad68ffb8 GIT binary patch literal 593 zcmV-X0Hv=@BjaQ|F{1AU+wpQ{$Kys{`!CTH>0fVKL!SX0Ad1a`1cQJ=-<7ptZmH9SAnX3 z{Q_yQ`~BbL*ME+m|K)!Dcl`DLKS&U$0U&@t8vg$M`{&P}#F!W*N5{%vzvln`{r&f^ z{||ospYrSf)?fc0{rZ3A*Z(w-2B2bq00L=XVPVP4%$zZO`i8aZ($dmav$E#>{B`Z@ zum6{R{h$5&|A}A!9e(}a_zUO_pbY>4IGm#s06-9cg3a4XeYH!D;D>_*`V5R3;NTtr z%*@^Mq=>M$LXVh`5jChqDeaf800L?F_U+sH_3M8E<^KOyQ&ao$p literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ph.png b/lib/ts3phpframework/images/flags/ph.png new file mode 100644 index 0000000000000000000000000000000000000000..b89e15935d9daf25173f89a36d8111824fda5db5 GIT binary patch literal 538 zcmV+#0_FXQP)N~0_Y@w0Al+04@f;`V3_{@|8#9F=K6XQ9UY*azdwKd1Cc)=8bAo50U&@_7#Kb? zFg*PK|H1$NTnr5Nxw#FRn^&xvtbK0f`#-;cuKxpc)t_H~fDExeq6ZmH0|XFI1NZ;` zGXMXp{AUnj_^>}zDz4B$`$1}#7U0X2MOU=aELU!7sY=Kr&1{QvMCSsj$|2dLpM12Fg)7ytqY zsKMy}yX6c^FZ@4woIyZ63`(G)I*8l(j07*qoM6N<$f~Z~XumAu6 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pk.png b/lib/ts3phpframework/images/flags/pk.png new file mode 100644 index 0000000000000000000000000000000000000000..e9df70ca4d63a979e6bcea2399263c081ce5eaeb GIT binary patch literal 569 zcmV-90>=G`P)`{xe>ko^7oADI06=l8$gn`UeR2p|@S@&Et-|M&0Ts{jIsi2(+HrUEqpC9EW^)CAOaJl^^9-%lBC8BHP081)#ChQGgo zHUI<=3(zGH+B`^Tu)Z%{q{PpYS&%;j+KluCr=pK*_e;5D)h=t+bKO_wx|Lu6N(?HnJ zN6`lu;-7zgR^V0GaA(7wCwqWF2h;!%KrH_l5Kj8_?>EpHKuhXR);;uSzaAK9KY#yZW@T0p zQ2G1+FVF^n0AhUp{`o&J`0?k5YKbbt8;l4>j(9ExfB*vkhsr>Vq>*li00000NkvXX Hu0mjfu=^7c literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pl.png b/lib/ts3phpframework/images/flags/pl.png new file mode 100644 index 0000000000000000000000000000000000000000..d413d010b5b097c4e0a4604eba86dad79567ed16 GIT binary patch literal 374 zcmV-+0g3*JP)Ab?mv3iQ8Nu5A{|6C_|Nk>HFfjc`0biay0|+1% z2B@O{$c+Ek03##gzdwHf0*Hl3)etG5lK=vU1?W~rxN0!OqXDP`Ab?mH`1u+7`u;zA z_W#cxhChG)!_n{G|9=A+zrl>(AX#bYe+w4^1Q6Is4VEzI_51&? zUm!LT$@u9L&^Z7B1a=Zw2xL66B2?87%l|Mi00a<712fnG3~)u5+Wr7F{AKw27w8;- z00L=X`u&GNQW9*;Uxq&*os3{386YGO{rw9;K*az7#P}5)xp2gQ%0-g`0)PMm0MPYZ UsK>Njp#T5?07*qoM6N<$g4HXSwg3PC literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pm.png b/lib/ts3phpframework/images/flags/pm.png new file mode 100644 index 0000000000000000000000000000000000000000..ba91d2c7a0de26e554979f6351d42a1a4e22de3b GIT binary patch literal 689 zcmV;i0#5yjP)#D@XkmAv!xDMMW8-mEZ&mK%|%B2@pa*J{pUD?(_ahBqlG}*TVt; zF#yj00{;H~{{a8``T70x^5Ww5-{JLFN2dS#-{Io#@$TjE?&8zZ_V@JO>E`A8`2YX_ z`~rx{;oS`3$G61pUY_{!-0$E2{{CbX;QPSJ^3BEStfg-V`n;-q6+;t+Xyw zOy)sS^8$#8OF-b~@87@v{bTs||L?z_Zq~=&zGIaZd&b4_{m<{;|Ns5^`sL5xKflEU zUjXHSP67x3F#yj01nBVe84eEr00I90|NZ{}FelkBB-0BC^O2xB85HWz)f)HsDi#s! zMLEolpK<^Iq5=Rh0M7pb=>Px;h=2d#;^FNBZtPGJ5bKP1n*{>+?(qio_5k|){tF25 z`1Sjqtv%A&PR-7P0st`p&i?}b008M5=kFQg|M&R&{r=?U4x6PnhWa{`B$w z?CApk{rU?E^8f<&008;|hy@rKe}6M^a>=kt2+9et{QJXr>zQ5ua`W#$8JQS={rbi5 z^Ctr{)6XBj7@3%V17i>%fEb@MFnj<<;{gVSbOv{z8Q{qN1r~!u@ISB;HgNQF0R$KT X>6#2<48ze<00000NkvXXu0mjf$fQr4 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pn.png b/lib/ts3phpframework/images/flags/pn.png new file mode 100644 index 0000000000000000000000000000000000000000..aa9344f575bc92f4c1a5043e6e7d0a8b239daa64 GIT binary patch literal 657 zcmV;C0&e|@P)$VEDzr@CQtS7ytnPF#yj0 z0a0(~eP|Fd7x&oU`XvhO+V>M05dgm54eRCe)!y3s`vCp^_5Az)`uqj_{QRhrlmY-T z0M7pd#W4OILofm6?eh2PFCO>E*7Xbx0@lgN z0st`p&i?}F01o{F1pxvB0QdR->g@pe`}O_>?Enew{R9F2_ajBO3l7@{2K78Gz9u`z z008^~05Jg0{{!Vds4FN77!dx<`5YAi^1B#F4D8&$5I)oT72fOU?&Jm;1k~m5*82ws z?b$fe$;$!&F#yj01PbEP9OVUy$0!>9?*iHF&;9=9{2wXIjNS}D{P+g={1WdM4-6DF z>gCe?_5uI^`T_tk0M7per>)2uHb&;=(#87${QNQl(a`kw{Py}Y1nctu{`UwC>ihis z2n-DS{QU(1_5c9-0*DC|aR2`P{`;Tl?{DV6zkmJy$H?%Xjq&fFKfk|zXRCbma58?r#f z00G1Tv;`<3F2eQa&)+|P|NZ&>@AqGj(m#Ly0)zk56?XydpHmj-@bfY4J_0oTA3y*x rGBBhgM-@1KAF z|M~a-&+mVKfs8*8^zZj?AObQ@tXl^VK#U9w|7)um9zFT5!1JoGnu}M2{r|uJ|AC@F z;y(;9F@d=M{=L6-3m|})KsNsS&&$bh^PO0375Ag3EPt6;7?_!X=sz3*^#j#0Gyh{@ z0SF+Vlm3Z`F-Ar(?BDbM+y#c5od5B%>y$nn=KcNZ|L>nr$AC#T8JS}%RsaMLPy>UR z34^BAe{Qb-hmSD)`~82~jewZHH;sOtVgB{~2T_Lx z^M7~ue_6TzObGu%0nl7VM#g8?t^ouP$PYjMFazDG!uzbThlgL98zlelKgeJZ;~z*Z zSPxJG!?$k$0R+;(`ulr=6P)M00|Ni~>`!`VeZ#eh`fLE8Q){QmQQ=eGY$|NqCtU}^v=1iBd@fS3^8`SJ7r@8ADF ze*C{^(f_{w|JZZ@y#o+HED**2KY#i6{~yEeU;qEJ{Pzp|pOucqNnlF=0*DFdB%s0n ze*I+l{{11(um4^iSXF}z{SS2YKY#!N`GNV*AE2Jys=p_Au>JnK{@0e3zk%WO_xG>A zzkdJw^XDIk{pa6bWqzfjEk^+Y2&CaX+n+VzlIN6JC4s^J?+-}zKcEU=eEj+Q{|`_P zFrfeb|NHmfUxq&n00G3<$-qz^$iN`P!0-!({va{_z!?9)Bm)CLfB^vHpj0t%_B3$- O0000op82)aAGO;3n0AgYI#891D4N?IF|Ns4E{L7&Dm*MXp#y@|U|NLS4 z{rmr)KmTFmmra`h0*Hly38?u0llM=#nEx6H|J4!x{U`A6pVzdvv{Rd?JIrI9L zs`Fos+0`Q4a^C}Q0+k5A1KaQ$>Lf<6lK=vUh2aka(D+|}f9<^YM_B9kC$A?0Z|}3+ z`ptao7t>dSY6f5^0o~2O01!YR8-T_G^}PA>yZ7X8cHUpx=KlJ_{+sC?ST#h$AD{+c zSpNn30U&@_7``$5{_{tYU-I{#-$2g*Lz_YNHw#c5FVJd`!65YaF9ZRd1Q0-s48{x$ s?-&?Px# literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/pw.png b/lib/ts3phpframework/images/flags/pw.png new file mode 100644 index 0000000000000000000000000000000000000000..6178b254a5dd2d91eeaa2a2adf124b6dba0af27f GIT binary patch literal 550 zcmV+>0@?kEP)~nkZ zGRMDC%7~XK0S0Tx8Wd7-QG59jKr9SQAk|O4{{Q=*0jSzY^rW5C9>)KFc0aM(`^fh9 z|Np=Kfe=u&EDs~YzkdJ$!~!z=-~YcrJO42FE6xsAYGq`2_Mbt_P-yG_e|4Mhx&31J z|LZSE!|%TgKvw|-5ED@OU$BE2n0|Z9O=M(v1mpqrFfl%gR9?uy{Oi{rkOq*RfB%6# z1PCA|pp${Bfd()#{N!P|4fj41<69Aq$E=K>;Tryc4F(7xpbZRv|AD;q^*8I6-->Vz zfB*5n`o{nM2Peo6f5F}W+3*h_fS7<7;^vxc*?8?`g7w0kCk@cHxaHDByoH$n$~K0fYN5BQR!wAqjL2Kmai^lz(OTgcO%Q oK%!9YKcr{|u^AZn7ytqc0H_5zuk@Q*SpWb407*qoM6N<$f;OS^T>t<8 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/py.png b/lib/ts3phpframework/images/flags/py.png new file mode 100644 index 0000000000000000000000000000000000000000..cb8723c06408828ce68a932ff472daabecc64139 GIT binary patch literal 473 zcmV;~0Ve*5P)@|4`Xj5kLT%`al?B=W5I`&pU%&omV`KRD53KJ$2txq_ zg7fbmh|k3I@68*40Al&@kBy(1nSq%Zss!k?M~{APS@-YvZ#JJG201x|0gQ}qez5@r z5EI0wa6>`h%cteq@9Mue|Ns2{KQdro`19u4 z9*F+^0|+3-w;#WAv9bOIX#?r_2Xj8qX@CF21pWab0}C_Lljko10*H}8@M3d^)Z>@G zf!6*0!vH4#gGo5!|DS*V{{HzdC&9L6!fAj26p=v+03Zy*=I{f8|9_}7o838uP_pBd z=z9{Sra^iHOE|+IC{ z-`~G~{{ZRVfB*dX11A6d`CYbrEkFRVK#Yg0UeQ$d`q2ZhA|Uz&)bIy{egR1#X&Imb zfB<4bR{ihazwp{dkXn!mkfPsU#_wMs1hfID1|Wb~;Esc6TU=EJGWFLlunkbN!Q9_} zL0)432p}e)sSwo=4bd$f5a~a^fBpd(3|0LbOac`E1P}{Q!+)sq|NsB}pI=e{a>g%& z)4?|U{s;0mNCQ9sG5v!Z57O~Jp}QY!5N0Be0)lgG%);U`26kn-@hOg zU%!6+4+cOs(0HIde9xZz`}Onxub&LUB0x(30+2WcIRJn#2ut|?gWYu1Cf+!-K%B8# zdf?1WA}#uZ8oj7u>$I1i0Al&`=O0k%-@icgAIJnM0xA6maSq6BK-ECw|NZ*S`0Lj% z1_pot6puj;05Ax`F!=umqj7^frO?t|3^&I1kxUq9yECc+jQpY84SWH_0#pxl$?v~F z@*hy-KN0|X07U)z`4{NpU%#2aHUI<=%a31wK(7Du52Oc(|3O^?R1IN+RRjI-n*kVB z3=9AP#PZ|EACPLGJ%9cJNh|>9B%spYzZw7h1%?tp0I_@ndg9MNE>313@6R75NcceF zkr51-#U+7;F#`Sf7i0rK0I_`g_NQ&Zji7_t0d_@@ozArL?Su`s;*Q{AKpRQ~TD44PX+*q00taD0I@Lq1k(S2nt`tR{g)9*1H=ZfhCe_Je;NM%1^NLXfPkU&2dGq1gcanC zzsyWb(hLmW7#V^70~+_6f#nZ_(jSmgFakOWAb=Pd7`8Gn`~pjZT=;x%FbfOAzdzu3 l1(F{a{)I#17yR-T)##NTdjqb^wzQ(`1@?t)Ix4MUXz556teM9A7Ic zq_@itH|pv>q+zrjZJ^Hx5bj=fD{5McI3ol<@^-l_@~tZGV7p>1CU&qG~{YccyC-q z$8~P)6sG{nMmQy85K$E6L33rja$x-b9$ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ru.png b/lib/ts3phpframework/images/flags/ru.png new file mode 100644 index 0000000000000000000000000000000000000000..47da4214fd9edb383687c1d4f84fe8b42a51ceb2 GIT binary patch literal 420 zcmV;V0bBlwP)X|NRSO0LlM<{-BURBqYRGSojej zfLOL~|EH_V_~;P>Nc10*D0|Jxss< zFi1)Q<$$6LU}rIc*dU*QNFV}+9T))>0|XG`SD?F)5CbX~O$rDA0t^5@iDe$xIAIn5 O0000N_~0!B1ZtR02zJmAl3i>|Nr}+@!x-t zvcLa8?7#n*fB$3p|L;$z$Rx)9zm`4EX`B2HAb?mPX21FhQ~^@@2dMNf!=L~E|NQ+A zWdHd6KUK1c=hsc9f8Rnyezh^=0R#{e(Ek5GMIci_s{j0D`2GL?@4p}ozrlKzzsUIh zhw0@n(cNzYfer%*AeP_1{{8vS`0Fp&84w#lmi+$#)ARI~X!Y&+KyeWsW`<9n00M~d z^G^m==F0#79{mS71ZX;t`~_q%fY`tPf!Mzw;*3o4FJA8j2p|?NkT1UdXZrG=;TOYy zh@M|qNhT&CJ~jq`0Ac}pl#vl6#K7 zFA)9u_3syi0VMzY2QmHtx&Qtu@&7&0&HxaAAvs6^7=(cU7?W7&6Z-$piRe`dyDK`^ zN$WO$zWL=wEu!PO?Vu9@iVSM&8cWvf2p~p=WCn&G3_$lI&>tl77dYY}Tp)vm0U*Et X__=7oxWDB`00000NkvXXu0mjfV`BRN literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/sa.png b/lib/ts3phpframework/images/flags/sa.png new file mode 100644 index 0000000000000000000000000000000000000000..b4641c7e8b0dd79aafaa73babdb525d3d2dc6a8e GIT binary patch literal 551 zcmV+?0@(eDP)4!1u&@QJ10&`rk^HbMk8Ee|uIe&H zS+;4$DbWCt7$DsBz5oJg0IGiW@flDZ69W^F_wDDmUw?kFvatRB^XvPsAO9KtfBpI8 z%a6}8Lb5=`00G1TbjJVx|9}4ZVWnuJEUfzZ$EVZx<$*voWzTGcp~$d(=+V{_?X+ z3y#kPS_RYq5I`UeU<=TsD9Bk~@o?qi&=DPd(?v86a{(`&&3I*E&g8|Wo?Sn;v*0j086Wc~d66UYT>00&^E24DBZ$$%)DV9SRUYEY{Wn z85tspN%!Z^Q&_X+!-^Gee*b1;`1hkUW~HJ2(d(c7|`w2)m##KhU^#|s1eh2YzMVW-Z(;)C{QJl7?-Rp6DTaSP8U8UK5I_L2Ffe?nu66~hU^Xzg zV`^IR|NmbGhChsd{xGln!xZ`Z|DQkqe?!SHn>GOi5KsdU{eSZ0I}_6%bMu4$|G#2j z_zP0e_xu0e-~WI8{{Q>e|6jj=B8;-KKpOx8hy`TyKZd`5nT(9?%F2RO|M~TQ@}K{E zz)C@?e?v4d{QeEp01!YxcLP=bXJGj0<#nFn|DXSVfvSK1-v%}nhwkOu|GvKejg0<-0ptJwK&1cy1oE`H<`Z?5M+~$7{9gq& zn~(3mv&(;P@Bi|05T(EW{(k!9>4UEid{_Ab1OPDr&i@1e0Q5gX)c>UW0I2)_6d3&w~i*%}NQ0G05Jg0{{#gAwk6j%|K36Wcrx-Z zDBuV5-R-~I;@8;l*yr}=_xShx{`~y_{Td7!{r>#`1^@zxvDKIVAJ?B1c2ZXvZZlkB uU;qZ}2Vg*eW0eWa_yywvNgf7(00RJ0?>)A@UfIF`0000`tbb&P~tyW8d(D~GxMiUp8x`gh2bB=pZ|ZjUUI!&{yK1G;4d%$ zIuA;MS%3cg{`2Rrii*m`ix&X`h=t(`1IH^)u0vdsLXv;@{sQG7;18Jf2Mqqg05F6A i0*HaZmVqPy5MTgGElxU<64PS<0000a|fPuet^$h(7pHv_{jfB<3vn*8q{15ov!pR5qo|Ns4BVf^v!57RHO zo?l?}`yWs<(7C?=0*LYNK?X*pGKOD3v;Q#s|MUOfZxH$qCjb2cBQWFtZ$@_cPkRpn z1P}|u9|n-AAQdnT|9?TXAyk7H4FCQBl>h_~&`AvcLF)bhMS(&{8jwh^2qVzve;5D) zh>=0RIIAq+{+o}$pxEl(3%2T)`P!1-fBt~{@Pp~sA7J?W`}60ovKZgl)=2;X#KQ37 z&mWM7{{H^+4`$e}UqIEMAo>Rk|KALM|1kdh!vsX^3=B^I0*D0|DL|!?{M^6*VPKPH z00z{*-?BnHps)wJ`QKllIe-8D1EGJQNCgNWMh3}bctpOPvlbWzK!=K^+cJPc;};D5 g19O2S13-WQ0NlBGh$rR(5C8xG07*qoM6N<$f_QiWUjP6A literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/sg.png b/lib/ts3phpframework/images/flags/sg.png new file mode 100644 index 0000000000000000000000000000000000000000..dd34d6121073fffcb2fcb5b9402b3e6361cded35 GIT binary patch literal 468 zcmV;_0W1EAP)dtOYis{Le*Dj#JuDs`KNc)tl9OYUmifJZ zA4uJ=-~WIA`uFP>kj*SB3$z3vfIu4l|NYA>FaP8D^M5;ba1<8<4f|GC2UPm+H&ER# zkRGsxe?Y{*01!YRC;dKnkj2`X)zkCW_8s5b+Wvn23^5y|0jv~A0#!5q{{4%A0U&@_ zfL8pya^=_l{r?#l8F+XY{;)G~as30ko(ZTFY%s&0Ka9WsFiJ`S6$1niy`;Hnv5 zfRT}r0R;fL{~@sn5I`&p|Ni}W_wFAQ`~gBVzyJ~jk&qMs5MThRyiZo6SsHx+0000< KMNUMnLSTY6dB!#X literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/sh.png b/lib/ts3phpframework/images/flags/sh.png new file mode 100644 index 0000000000000000000000000000000000000000..4b1d2a29107be96413eb86e64a75ac7a3ba5793d GIT binary patch literal 645 zcmYL{Z%9*77>Cd9?woAXO+#E-F%m^1b0Xy*Qky9{B^@hJqB6}jOKKPc70u1CS|Ug( z7>SA^1`5-}4+VvY=3G*k2%(8O!OWTIn!4?td(P>GANugV4?p-l@2B^fCONIbD;IAODX_{rV|BCn_NC>%qlWoHrzH=l|0Y^Rhgkwr%>N3 z(d)FjlCqjgyY4&yRH!;rb)|Z-v~HjxIkvar`*JLyzxBc-B?Ix`3*qGz4q3JAd`#LY+Xw^k(ph!n`d2H7`aI`Eh(LrOLs%9g zj93;8ws%s88WHkIqXqnSf?YSjh=@dF-}4L7dS0HFB@iNj8OY*&4>%Dn8t&*i)aXz6 zSX_wQ?~e=9UcwhrAtAf8XLVoTbE5+<^|-KK=D&>)yX6u!zrPCrbEr|4Yi(XyIGTQI zFEDsraAY{)DhUd*DN;Q?!uSxvkoT|31dF#>2L0DGeRcNZNehm>xm~}-9q?gtV@Qz` zv-lB19|m}3LHcg92}TUOb+%v(0bnUhB(5rQI9?ZY)h~Hw=%2Au&~WB@t;^kVE@F0Y z%=8f1ZN}R1MniiNxkJ!a;3!XFerfimE2A;1XJChGXJ=)MAVRubE8WFo1T(1Cmhdfa ztzC{Qms6asjkstFkFp5L#maeek84Y+NtW^Wf=SRytjpC1=BCX4NH^VxnQ`+YXocAv zR?lKskkKZN7D>{S3>4;4+gPYYq0_5iq@jsB^}M0yMT0|p`lM;R_dwbVrBg^4RRbsq Y$WB%-43-yHbAJTXS^1gPjGK@C0`m$%7XSbN literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/si.png b/lib/ts3phpframework/images/flags/si.png new file mode 100644 index 0000000000000000000000000000000000000000..bb1476ff5fe8e0d3af4fc6bd11e513d95fd9cccd GIT binary patch literal 510 zcmVm9@#;PEzrU@Gx(rNA|FHo^Mn<3|00G3vEO>ZAhtlWIf1*N!=PZ8p<;&lH z{~7-L`S<7dzu$lU0~x=8==VP$LqeQ==EOq)0mSn6{g=m2e$<)X`O9Zpu5&$Np~P9Kn=gZ0kf<1I-4a zUtk1c`~{K>fBu3(0U&^Y8d(1RW7^4V^6LZG89>!QBmXe{`pfY9FT=0Dj8O6)!|#7g zK*az7#L~(TO=a{vExMP2%`MCSoB^FIcLe_%lf;|~%E5I`(IQNh}3Ao>6Q|DFUXMn*>AqQd`w z|1kXd^B;tM|Njjl{{hM0zwd6?1Q0+hV1xeud-4=Wy?p-%sO`^#2S61Jzk!N?s)6X& zzhA%p|N6}=D+{y%Ab`Lc{sL9~1=0UN4*CD*7s%9KAf+JHKs~=eB-8KTKvw|-5R1&; zzd&a|ob(5%^Z$Q=wHy9p13+aOpFRNu5F>N&`Tk_-7w>=n{RejQzkfh&Kn{rf10?_b z{tFTZibx5v&dxav5I~H7|Ney-|DWN1$%1FyagzUW0464;_wU~W1Q5$TW@eGxtUvee z3vAf*8|igK9~@*rr66bh|NrkNM8z+V zAV?>O@ek;bKfu6d002F*|Nr~{=Pyu%qcHbB24G}l{PX7zKmY(S0M7peLS*D^UKK~y z+6(XH|9gD^r>6bY*OZ>+`2GX^{Qmv@{`~#_{QUm>{r(dW1b1xK0st`p&i@3&%J4-( z6Giy=|7vRh4-WrbUHx`??d0*<@CX3>{QLa=`~Cm?`~Ld;{u~zu0R89!i0SVi2B1U! z{r&fU#+3gbKmA|6l!@v8=U+^J{{8>`mjUR&KfnL~{sUtC1qTg400I5L26X!4i0my} zo;{uZo#CfPX{7DT1DwDAf(-fnkMZ|^ra%9I0mS$d6bftr0mQhUfkCOfiE zM%HqlKU{maemr>kAILSo!3gZ8-+$N@WcKe}3J^dnRtyZx@9+FOdynDWy$P0%j62V; zZC)?>`}hA}zy1SB2;@n0006p>>h67sJD!pI8@uK6+#GPW%ce@CEFZM znTD)%K!F4J_qpc@AQp(ZPk%rA2T{TB=kLG2fBygZ4O9eD_xsnsUl6u5yEM=SfB<5# z6|+@gR}o|u1R4pn+XF1YrQf2oylS zAbS3(sBkhcTmc9mV6grHN=r(zLW3F{=D&YK84x>x_WT70A}|U80*H};VI?9O(a}F7 e1_Utx1Q-B;QgQb4eH!Wj0000PEol7!5I{@}e;EG$`~Mq){{FvoV+so& z6Zek;JYV*``O13q$!wq?2!V|I&+z9TKmf4-@!$W9Km&lr1Ie!(IeUNcyZo#F`{&0p z?&{CqnEw9%50U)~((nf$fIu4l{AK07*qoM6N<$g2hARp#T5? literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/sn.png b/lib/ts3phpframework/images/flags/sn.png new file mode 100644 index 0000000000000000000000000000000000000000..eabb71db4e8275a5bfb7b1b8f3a8374d50da95db GIT binary patch literal 532 zcmV+v0_**WP)6{xSen|M~Ox&!2yPfTn)={d@hr-#`EU4p90fz>J~+=oo+i0%`d3@9*!wzk#ZO z2WQUEDXOGe*gO;$t?-?1~^z5 zels!xNuY7SApXOk^arFAjDSu82p~p=WCoxfknArIdOZOgs+`19}IpFa%0{xSUi1L7diFBr+f01$veIX40Tgn<}np4MoMn3N`o$god9 zrkzn;+j{#q5F}xeh49xZuI$05IKi0v3Lq917Le*!UxBJYN`Vr8|1$jj50VD5fvSOw z-(ZF~46zx($8#~-kUKOh9u19TNY0I~dG z`1hBA5y<}g_y4cI3}({*g*gAq^Z#dL_#dnH|JmpNJMaAe2etv|3x;0|00G4G>kr86 zzYquf`+xk=e@^EAQat}9xc>97{@;He<|?3qzo3u+2q2a}APrzwf*tnl_y6c6m z5?7266~G|=0}2mdsDK;{bP_-SF*1~XW%vY(NDyK`u#uw~6h{mUJPZH<1^~BhckWKq{Jk|L6Yw|Nrm*vcLXk{sL#*k)f0!)jRF?KX6FHLLC_7zyJOQ#RJf}AnqSv4E_E0 z_d>^cfB<4-C}v>z1dGUDNRj#r9OZBkNc8hC00bBS^Nnc?6(4BA00000NkvXXu0mjf DM=x{`~^7feaw| z@9%GBrr%pd7ytr@3Fu&;D>$G0;d%9&ckUlP_FsJLznOpkI<);49~%fU|NOOg<1c>p zU%YI;<@gu?0tjRS1JJ?0e*b^_hvDg;|Nnpg`SWY#%HJzjff!!CzZWj}wP?w&U%!5b zMg95nhXEjfKpOu4{R2W=zZm2}8km@V1qJfIu3a z^ZvQT`ny}__wU`%Fad@Sg!~P3A(#OSI%WR9r+OFw0*K`e1H&7RKZ`vj#ee<&!}=E_ z2M*0Ye}N7Llfc;c2aF@2)L(`#3;+SdxR!w-xR`AxVx?>``QKoX4p z{r>$=Qj`^FFF*h>?mzuiS(feBuYdplGyMPm{~rVab^il$89+1;fd$yvm=2wM1rR`t zfB*hvWMl+7=|4yZ16bGpcem@l{$rO?srZisfXY67`UDU_ED+y9gdmKw*T0{<`x@v9 zlkZkl}Tegi}1FC>)y z`~`;fe+5al?K4jS1P}|@5C%z67NEr6KmWj?{{J^f1u*QH|Ne!BH7L;kfnp0FfEXE= rk23safJY>Z`~zeELt=n*00bBSq*!cC{}>3t00000NkvXXu0mjfg_GrH literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/sy.png b/lib/ts3phpframework/images/flags/sy.png new file mode 100644 index 0000000000000000000000000000000000000000..f5ce30dcb79b443ebc1615fe4889cc26e2d762b1 GIT binary patch literal 422 zcmV;X0a^ZuP)@|4`Xj5kLT%`al?B=W5I`)SK7Qoo3V-qKd5l^# zgv$taFhBq?zJ2?apPwJU>mjm?jEr~g+yMw6Mj$IKE$!aDd%wZp7Z^YoAQH^_^XK=U zKYvwJR4!h;2oOM^VEz63kEEm|Pzoptl!JgjVAdZn_zMF-#Q*_BG6D!N0MDLEDh$KY Qwg3PC07*qoM6N<$f z|Nj36M1Mf!|33`>e*gaqWCO|HKwFtO#a&sZ0t5gt0M7pel4t!zdFAutyc%F2ByLSib7d z-@iY8{9s{WsjaVha_-{UlG^LGvEO)A^**!$6$1ni3ljqaFhGRYPy6$qf&JG{kKey{ z|N8ae_ixs}{}``bdDhkT?#VOp#VvoBm>H#|fo1~)5aY*A1~#Xrf1jTKgW~`H-wePI z{Ri~aZw6pM{r$!84-_k)u=@Ltg1)Cf2{xiF#iKZ95W~`fDE7~ z|1pDN1{?_>vH!pR*Zxoe2p~qo;+=0k{eVOy5dHe~@Bg1a48OqWACLsaJOfY!7)!uZ dz{3I%U;rfUVNTmRI(Yy9002ovPDHLkV1m_xKPvzL literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tc.png b/lib/ts3phpframework/images/flags/tc.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc1156bec3389e54d3c5bb8339901773a881e68 GIT binary patch literal 624 zcmV-$0+0QPP)Mt z2Fbqp@bUfi_XiXZ_x%As_WHU2H1hfc9TM^w3-JC0{^0Bg`~MgK0Qv%mWs{6kqN+Cc z+b3Uc-enXLWmi^s_2~6APR{Qy8GilxQ)j#p({t5y7`^LQ4&lw9Z0Ra5^`SSP!?%<&3?fU!$2L%N9{QCa@0Qv%mW$D2m zA^~iFet$Uihac>@UpF6ozx?O#ufL4HfBpRUndtzp5U;>@#TUPR{|1KG4}bt-0)`aO z`@jDF|MM3p^6S^{zdwKf{q+mXVfa4fgq7?27ZxvH%|34Sm*EEk!#{ukVgYLS^A{)$ z3^fq>3urc&4I=;kz4MwgW4X`2zkh%J`Om=vauPrQ0X6*k{pY`=2p7%A_P)?P zUwuVdAkKe=-@h3}_C6p$AOxEwQo+UIeHT5%mg3lYGL;@HP(LjqG0$?6F}(Ht8A z0K*^*BsuTDFa|;zm9Mc;PRcq|KMMBO%8|{GkrU*a2x&r-3HS3`xnqW1|R~39RolBu`vAj19S<{HjoWK z4L}tj8-Qm20eTwfuYbROLyY(bwgDi3SQvf*HG+%>>H*pSauQI_zkmNAP67ugNCPlf z{{97O00g6G8FFbZ~XHln?_bqJeH^V)*lb;YTJ&0)m(r7ytqc06HRil3NF`RsaA107*qo IM6N<$g5!7R+W-In literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tf.png b/lib/ts3phpframework/images/flags/tf.png new file mode 100644 index 0000000000000000000000000000000000000000..80529a4361941e01d1def5d581bf2847cf99fef6 GIT binary patch literal 527 zcmV+q0`UEbP)KfiwinSYl|od6I(APxT+{=azr6>87_{|pZw{QvzM zh#vfW|M&NwU%!FG-`~IfN=xzq?EwhD!Wi2C0Dv&)|Bp?Zs+hPi0R-LQn75%cY-O8s zAPsw;Q!9X27=Hcx_y6DDzyCn0!4P6RP{Xg^fBpb%_yyDhbQs7f8-Qku33L4cS@IvK z8OZ(n2kOgTzyGb@ek0IRW9ouaN6$P0x*s5bfH4)06qlIj4>;1{Yyp4(0|4dQTo>gF RMrQy3002ovPDHLkV1fdR=hFZH literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tg.png b/lib/ts3phpframework/images/flags/tg.png new file mode 100644 index 0000000000000000000000000000000000000000..3aa00ad4dface0a9c23744ab451cec0443f187bf GIT binary patch literal 562 zcmV-20?qx2P)@|6mN}b_RwI3=Dr@=ogG+VE_mq7La^(HIVqv%*^=nCj$%1|9}7Uoj(8m`RC6+ zAo~61@1NiQe*Xrt|Nh=Ea}z)Sfi-Y&{FjhmFg0aZxaj}$=L~=U)(L)O`TP6-zu*7= z1Ib@N^zYwqCU)6YF9v`BVgi}{>lcH)J%gU!|J)p)>i@rg^Kt&>WCWsLoQyxgk29dnvBpN5vQb0*LYVQ3gi&tp7jnGyMMp z@*mKbe}4Uk_!#8f-w;8De}5R+mEP<>2M|CkH{LKvi2wQh{WmZi827ytqc0C8?ZF&p#S!~g&Q07*qoM6N<$g49|K A6951J literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/th.png b/lib/ts3phpframework/images/flags/th.png new file mode 100644 index 0000000000000000000000000000000000000000..dd8ba91719ba641502bc7ffda16c25dc71b2066c GIT binary patch literal 452 zcmV;#0XzPQP)@|4`Xj5kLTn#2^O%5QJf({+!S7KyMr1NSdb0?wsyYS6cNVdko7wub89?$EL;)x)00Ic8;m6; z*ldXL$Yz5{poPB}7`^}m5DPc2nu@9r4=+1782$pC1a=$HjDKJ@i17#P6G=(#Q>V-T z0*LYF&!5c9%>VxV!xcCX0F(uW(%ZLh0Ro8SA3Hl!NeKhQjeq|#{05SL7=Ql;MhF4{ ujRQ&nZP~jQAb=RZqDCa1IEDa#00RK>mSUL#9?hx%0000(5Af|u+{y|g&!S9Ft%(8imf-XR*|Ns8{XO&`< ztN44b9;zJZ^?&~W0*LYB$B#TbJpUo){|D*(`>^x>w|gKB?9z<#wQ%E^n3(S0zYh>V zEO6uD3`W^f29Wmu3`mZF`w$?27#UO<(rVJ~y}I}N&+p%VfB*XP3y6U1Uw^?QNbuL6 zzrX+d{i`Cbal7Zfib@b53s27mx! mU=U|u;Adc9XCM*)1Q-C-v^a{iiydVE0000+lNu{Qmp=|FW6D0*D1<1Ovm9m%kYQ|7p*Yo3ikU z7~kiESLFZv{{Q#iA0zqwf4<*Xu-*CD8$MYncA&8U0mQ`ckKy0n|9}7f|MB&2MbDkQ z1j{KKBL!IA2(Z7F;(zkuBe#mo9TT;SzkoLU{s(jwKmai@{QdX$=kGt?etVi7(p9*e z(|o73K&5wiaQBkXEB9so{`tH8jIplb^d$ZPi5h|uLD4*o5RnOS|{Qd{@Cr~j!0D)}y0}8T#$FHit{lE#dc*g~k_utsL zxj&Wpto`+k?f2h5mv;mM4gK>M6!-uE1hnA~P_?8O+wWh(`!4wX{Cn2j=z=is`~Uxb zzx=@K_DcEUJ7IZAUZAu8{sn~+Kmai^Fsw$4O9qB73=GeJhA}V*Ffg!yW&gnt6F`6g Y0QOrYft$MNbN~PV07*qoM6N<$f;FEW z@b~ZkKS1)&|37~i{`~p>`}cn&^2?@800G1T)Nta$zvt>t<757@uK4@^{4bCmpb7~3 z1tfp{|Md&VW|Wov$G`v(Kp+kO{{26+>+jbuf7A2+upj>Wf7$Q%RxoV1$N0e}Fas1Q1BW)@@rKJa|xATBWA`D>(WW1JFHv zzy7cP0k+{MTm!^O009K@!>LoJmn~avX#S_9;5YZ-KmTX``u`YaHjD&?*dJiPF#rS* zNW;&cKP?=87yABYZ~OCq-%n(9P{tpi22h9t6$1niNQ1e-pD1xDrvBdy@BT2zfI@>2 zY$OAO1fsuxAqeOsfB<52Wnfst00RHO2+qJn5C9Nh04p0wT74^6IRF3v07*qoM6N<$ Ef~oTCkN^Mx literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tm.png b/lib/ts3phpframework/images/flags/tm.png new file mode 100644 index 0000000000000000000000000000000000000000..828020ecd0f6fc73348373c9e7a235fdced09de7 GIT binary patch literal 593 zcmV-X0VKiqu&g@GZ_kb&V31Gky(*8`UselaloV1SZ886W^4001!n&i@1e004Y* zk0R&!|NaLC5DIk$@CyeC@AU9ebDYiJ)zswL{Qdm<{`>p<{rmm%TMiuO{1voVt6d+$)WVk%^O){nwx0zkdJv^%rQ#Z=eQ%0Ac}Z`19}Y zkI%oqe*ekA`2WvehFzDBOo*xZ{rB(cQ`_&qdGhP`Z=jyvAOx}jAb?nae){+CFDvWc zZ(siLa{c@D=g;LQw^HX+sY@u`d-M3!$G1SGAWQ!Ifj9{ufLMTG0<_`#=U?|Ye)D|< z8pH7G?=PVA*@stt1I-4xpA0e#40jk9 f${7d%fB*vk3(!F(w2U8u00000NkvXXu0mjf+94t7 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tn.png b/lib/ts3phpframework/images/flags/tn.png new file mode 100644 index 0000000000000000000000000000000000000000..183cdd3dc98c6957bde83f375a431e543a3ce9e4 GIT binary patch literal 495 zcmV@|4`Xj5kLT%gT|L_r@28Q3iff@h;hzaCwplYy<4FCT9KXmB7 zhQ|Nt)Bg(#gN*m^{Qvt8$Of<_K(zn?!~}BDpT8hi0uj&>pxItt|NHv>A2{&e(C|Mm zFGw7s;m;qS=>P!)((wQ9AF!W(0sZ}7M&|#9_5TYB{zpfHZ20g2t^wjCfB*tH2_nSs z8?4R7=Kq6S{f9wP5^T(0hI#XVIvK%6GC)WrB}fdh6SO{nUiXv3iAlW}aH!uJM5J&?vILH{_iZHeP0c!XQ3PuJ7 zfB*t%VEX-sK~fTI%wL8-Af1e0BN-qh5dHlNK|sX-0mS$fH6j^sMDRaYQUC}r0RA0L U-~OCsp8x;=07*qoM6N<$f)6&Rz5oCK literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tr.png b/lib/ts3phpframework/images/flags/tr.png new file mode 100644 index 0000000000000000000000000000000000000000..be32f77e9910c0896c1ee8e7ed4f0edf815a517e GIT binary patch literal 492 zcmV@|4`Xj5kLT%yy% zCxE~j7#aURc>+`lQtjr(@bBOM9XtMi`UFz<>-YcPzyAOF1!ObI$^vZw2q2J#|6qeb zs-vSBOie-Rl$HN?bb!_U0_g#3VEFwTr~x2=m_Y6Zss`GqsL0^t^nd#F|4B*zH*EO- z_3QuT%m4H6g6)A?0#pkSKujPf{rCwIb#eU5`( z{s2t}2q2J#|9}60{Pg58FvR|AYlEDjruP5RC6KEgJ^H_IA4CJhNdN)F0>q49OBog~ z{?Etvzo-c07$As@{Qu?4|3!FoS~(Xu$ujuK&Ki|5;f$7pYF;Nfxh(xqFs zZUMDjzjMbfEbRY>5C0Dx`v3aP|JSeozj_5kj66I)?%V+g05Jg0{{w&k0RJ5w0P^!X zSy|WJ-R9=z(aMV97z{4myy=pj4$^bx3|zy{hYtY)h`Ao9!QA5iyLbPuTmkB_PfP?Vee~$j z!-o&gUAyj)n+wu&>J&%=KmU)%j{yP*q=CWA45Z=rFNTYkfO?!$Q_o+&{s0WlT)E;? zRQUhj|NkdWF!J+%fBF<4fS7B68jOwqzkLe|6QI@S&;S4b-#s()-1Y1C!Qjl5tA6F> zpisYc>&x@!00G2Y!@$5~Xb92(47=Z8{{bD%z~Ggi4>bPny?b}>+&OjWQc!g@0~6Et z-Maw-h`Ac5K~JB7o9jOp7XvpB0}lu>-nqlT$mpA&|LpDCZ{NOs`t<42?c44-IbZwx z0Rjj;B7x{N1H(%OhUW|nPgz+XA3Vr_1a9x%4G>@ep<^{rq?UTJ00000NkvXXu0mjf Doa;3l literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/tv.png b/lib/ts3phpframework/images/flags/tv.png new file mode 100644 index 0000000000000000000000000000000000000000..28274c5fb40e5d3bacd7c05d9a1b8017eeaffa6c GIT binary patch literal 536 zcmV+z0_XjSP)mzpv3Mmw05Sgm{~xFtLc$sUu>nR##!sI<0R#{WMEdpj-yJ7j ze)|0V&%b{_!~X#7`1>EofS_N0|E)GoUH&SsFy}2m05O4F@}ffYb$qvjbfSUipMQUU zgY^9U_4m&&5D8KZ)HC5u^uad<48Q&W1P~L@hOIAb7eDp_T4W)^*Xg48{r4ZBJ-_}k z{QCPJq~Q-pPnz693xQJ%zkUG(5aZwHtqd&u|9`*u&+TyOvq{_Ghd=&6oB?#w{~Yv(xYPgr|NRH*haKioowt*& z{dC`3v>70P7=Ql!$;`|Q@(GT>fdHT^FqGcDeG3pkjNS|k>E&sEfzAeo{ckY&=g(gd z`CCrwmxA8!XLtTy-uM6CzrVnM%iVPzAb=R%ks=j>et^j`MFs;Wh8c4i)_^1XKP)u> a1Q-A&>r+tW$-Nl>0000<~s!0-E7R}a|Ns2?4|9|}gs%Dgx1=;`*KrBFO{{8#+``2Fv2B6@-@8AFY{`(K80jdYk13&NH4;SYnOGgJvc0I@K9VW@Vk1}Xmk4~YK$|NH0P z-@pI<0D+Rop8&DnA}oIvEc_iD{O9-Y-=|4+U?1uFgf@9*z_zyJRJ z%fS3QR^zuR^Y0tyerNXndi(YlP_?gzto2+5fB<3vIsj-gQ0X6_*?&Q*d^>boB3^zoi8Kq^tbl{qlR!{9jwQ|Nij< z=(=A?F~8j-MSe1{Hl8Yc@zaXo%Ljk}VgUvb(2HJbe?0kqy?_3vsPXrUmq2rWE6M!I zE@4*I;Mw}vw&7&iFGdC>zW)rr{s9CK3xfnhrsi*1wqMhx{@J=6sPgxpU%z6)f4fBr zzxpd#a3tyYE8}0lKnA+|-+zWbe*glA<)R0}-=}{Pnt#7~1q_Mb;=;f3i~oshNgsS> zIr~z~(_bRL|NZ|1LO_>;A_pLVSoBpHQd9nLa7ao@{lMMx#__lBLe9Cjx?EiU z71{s)`Oon8FXP|;z>s7BItd_v7-5mj(Adb}701wVj$!S*>kNN{fN~5FV)t0eYh-DxnQ(+0I@JIfmA>F`X8wD?>`2h#NWRRzyE`zA&Pzh5tt#% z!^rUCA3y+sZ1@LM1hf`pHc0ia|NsB|1uKPX0CRu;1-SztfLMSo`wOxir1}rsY$VlC z667idh7Sw?0mQ-}a!Q0#;n&}vAb0=!_Zy@WNd5!6=O2ju7s{65Vq=gx0uVrqe;+b1 z$mIY3|KLBvF9x7j{{!v#g_UGtQhI;<6hHv607H@yECdWlR7EJN!LomVp$!l~APvkQ z4p0cL2#YobhQA>6hXEjfKpL3-19eKmL_s!=;22W8vpXP)6RGKQPvx1q)UK1Q6rb{|u}Q zRsa7#{m<~1f#L7}|GzNK{&HxZVEKJ4>jAefr9{u_M=MTegAo-i2ynN}MJ5kZm z42KRel$J95{>|{~*Z<$Y8RdVo9Ap3pAeIsa2H!t_KmPs=bot-kKnMR0GBy2s>sC=w zQFmwO;@`jj{`v(%zkl=p`BTQg01!Yd{Xp;j{qx|@pI^U!0X_En_iwRd$6{h)tX8f( zaOhC?&!0e%U%!3phy1#1%V=eK#YHXK~n$!{#(ib5I~GyfqpU(k2*|8J(R-+sudaynhucHbwAMTnor{mwqO^w7JHzaBsT z{O^B8RYf5+LvDs&KmRKVd78=o{`1#HTiEo_OolaGleS)G+IQ#sUI`b*pv<`1zCJ=H0jd{{2S>p`ri%{LsXJ%FbMS z$#S`6f|?OG!^Jxczkf6Q`UNF{l0Sd`ad7zm>({^EzyAS6{{CgrkluOb3l1A>ZU2~A zK+FZ=zkmP!`TOVhpFbzBzFaPmD2$N3;+$pK?>zdet`f0002ovPDHLkV1gy;I?Vt8 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/um.png b/lib/ts3phpframework/images/flags/um.png new file mode 100644 index 0000000000000000000000000000000000000000..c1dd9654b0705371876d3e3d06f950be02de2a73 GIT binary patch literal 571 zcmV-B0>u4^P)1J@ZrOUii!$=0Ad7ci1(2A^!3kwFaT->5i7Ttz54h6XL$JkA3s3o z=g)t?e>01U3h&zo5I`(I-#Hr#t~u}$WXZu-Kyv1Rm&rE5wcB1YFI@@=0EYkn8G$4- z^Xb{M0Ro8S57@^*QJ}#<$Im$M^4iKw z00a=@`Sa(sw6y;I{R{T)e~6KA2AmBHF`#D(ii!XNh(#nN}rF)(}plRy9vU;su%e@J`J?dSji002ov JPDHLkV1n4Y8}|SJ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/us.png b/lib/ts3phpframework/images/flags/us.png new file mode 100644 index 0000000000000000000000000000000000000000..10f451fe85c41c6c9a06d543a57114ae2f87ecc1 GIT binary patch literal 609 zcmV-n0-pVeP){qW|y?pud`Sa)3|NY&vWd%S0u>b>P!2!lUe;6EF*#G_c zFVXVt@6Q{uX@40W{p0iY2Aa+A^Cu7i8KT+YH}2j52q4BskM2rJ$^k9;2Xxc_|Np=M z&VaLlA*IO5FlECMfB<5VUNC{tBZO(|zW*;@GJN;|bTJ71`0*d;`d`2P!x=ymOA`2> z+y@9C##^^8%gd{MW@Y91_2d742B2~OQNf=-zkmD?Vqkdk_wPTUNeuu2#KPTG{_;O4 v7C%8E5*DLB7#Kb?Fnj}}-(W6879hX?8lYRg`Y`<~00000NkvXXu0mjfD6Jtx literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/uy.png b/lib/ts3phpframework/images/flags/uy.png new file mode 100644 index 0000000000000000000000000000000000000000..31d948a067fe02d067a8c2e69f28cca446bc7c57 GIT binary patch literal 532 zcmV+v0_**WP)_vmzq~N}&z08z z0*LYY{pZr+B0$d}2MCnI@DC;m3N;oM#uMkR0R#{ugY)L9Y<*xj0QCR^`!^)W!R$Za z5CobHbl5+T3;%B|S`QFFjQ1Zt|MTw;G#Vi+hCg5i(ELAtfD|ak8UBG;ObiSF0R{lf Wla#5zB1?M!0000JMe1P}`Y1HZufM;3|NZsr4^XwNEI-g5fB<4iWtjhPj`qjL zFGT_P{{Q>;-(R370Ro7T!5$Ws$Po%5A+Zb!3j_cNFaSC{Z(fWD@s$7o002ovPDHLk FV1jsy^u+)G literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/va.png b/lib/ts3phpframework/images/flags/va.png new file mode 100644 index 0000000000000000000000000000000000000000..b31eaf225d6fd770e0557c2baf8747c91ce88983 GIT binary patch literal 553 zcmV+^0@nSBP)|05Jg_ z#sD@3Z1&&(|Ni{{_Zx)%v%Gr!NnLzkb;C z@#QWhJ{xI9$+*)bNX$S?E90e{K^EMkYok78VXhR-hjM0*K`g z$j^Tmm_>oEVgxdn{xJOg$-v0L$jHRN$OsH@P9Y&+^ne@=1^@xXcy~X;zaPM$WdOPj ri2i^{AeYDB@IM9-1r;P))4B5XW#s0WPsWLbObPT z00IbN14#96puqp%TKvBf)PA*{`~CO-uM~~n%6z|m{re4Z55!3T0R+GV5`UO(`=kK4te?c|?1P~}#f$jmC10=!0@{8gBFK@Zu z-~WJALqveC|MTzfA7GIDVE_mqkOrW8BqZ2?K4D^#WMJTBVE7%b47BbKBg1c?EHl#| zW&XdQF#ikE01!Zo3=CVrargu%_x5NZPzVyc@Ms2-@*Gr#1lu5DU{kh6%sopXB}uDxAo`@Bt_a*7F+) z{Q{%kjEu5C8vp``Wse8Lf5q7h438n|z~nEOsZga5l7ZnjPy;{!05Jg0{{a91!FF~d z;^O)I`~dv>{`>s@o}L{E2?GEB|Mm6!N=n`P`~Ld+{`>p?A0G$7!M*~B3FM_eAU;q6 zNCH*w+4I@c^Ny?Q?QPpWZQiU1R1HBuWkA0H1P}`l|NYDG=gP=Ihyj(0CItil0R{kn^jdV2 S*Eqib00003_-&c2+@O*Bba;fM%rBo$$qwJnuekcf#0k*RG7MM`2+5Y7dK4k?R% z*@ue~6f;u_A~T0evP6qS=VxXnGS_mt>CSmS&gmG`kLSL)e_Vf_=c&!fKB7`4C;*C(7s2O*P zOs1$U0urzb3<=a`d9ABG4Q$eK<6~5Cg~V%B8`UqrfRSN8f&?QTVICs^VT=&p*?Eut zt4Ur&-BL_ON(Z|Xfgo93lf|gRkT$Mz-7^OQoD^XMF@}g>R?uoU0094KbXv)l?aF0E zh@GXQVr04m@05R(Rjxq*p|CCIRfv;QJmH1kf!hsqX*sha?_l;f%e5i5I~4YG#H4;6sPG2&U~bv( zNr`eMXGIX9?wP^zPQP(6+&$05Jg0{{;N}{t6CR|M}we`P%>f+X)5&j)Fc04NUj< zI`HZ2Woe@k6UF22`V|Qe2MTi(6bb?WF#yj00*Hv<{QLmnFd+P z9@fS%;@9^E|H1b5`Vk8^2@Yw;#}(}C_yUN9iGkt%y>Cxm>T2lO13mifZBc!n`jhHL z!MK>$6Z_d%|GfHMapIVnqod%T-`{|q1_&S~IR%b4Z+;63vi@iI?>jBz=eJjkjQ{>J z{QUQa@gMhBW_GSe5B=V~`@+h~@c%z3lmG&V@$K8U{QUg?|NjRX|KX9x@@rf{MxIY^ z?fG>3gQ&3ruaE-Yw>fW_PfAH~0~Or8dmA8tSRg?T(ZDLQOiOw%6RWM9%$i>kkAMHu z-oH0qLBYb*loP0ck&zJ?LI42-(g0BnBsn<*tTlsx%zxbLe*S!R<%*E3>|Z7(9Y#iG zpdch>0Ro8e`Sa&!arp;m4%k`<1H=Xjf<=G;Aix0Vb{`Xo7A9K&0000@|4`Xj5kLT%B_22*h{r-RE_y1qN|Ns8=|JN^|IHRmA&<212VgZ}|A4N4# z+WpUe-rxU^{Q|504bi~x`!`SnKmf4-F(Xhl(8fQ1fG+vp^85d{-~aW0|NsB{f9tRR zg1`PJA~XO^2M8drlm7gLxRT-bpa1p0|1bLmQVm3Azy5pw{{IQ;B%q-{(*XjA3FM@| zf53hMJK)W)|CK-uK=ku}!>|93eu7kk5yVLV0R(o^Z;+)RSAk6exg_KF|98LtFaPy_ z#c#OrKtum9FaQJ)NCPuC$bbfbRI>n`@$3IHusZAC|2cmD|MKhqAD{+Mr~sV<5I`Ue zOuzpyNJ@f@0s0=KlTj9AB*>SXe;D}wK*A7+fQkVEi190GMB<5K2mlB$03I1qT8uIj Q5C8xG07*qoM6N<$f}XY6qW}N^ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/vu.png b/lib/ts3phpframework/images/flags/vu.png new file mode 100644 index 0000000000000000000000000000000000000000..b3397bc63d718b344e604266259134e653925c9d GIT binary patch literal 604 zcmV-i0;BzjP)7(YpZ_rO%ce~L0mQ<@*ghnz= zZvXM=_iyVze?clB=ogUu_5ar|Ae&KE_8$WSKmaiTHT?Vg|MaE5OE>+VAUAUr;?j4FCZIvLVX%uQ>mocOU*NTK^}%njy93_oP3+fBpUa=kKpye|`ZO zzyE?r`Mc+5Xcyo_v6>E-#|Nl{r(LkAS94bRQz)6 z1Oq?-fi&zp{`>yDUnV+0)eJF{zWfaN_0t9DjNiZhfxY|}ME?2z=O_CghC>Vh0R+`!~0W%8&aG{{H+4 zRPp!M??1nPG5q=k)Wabw3seITKp+kO{xLwc{r>&$?_UrCGJgGL;^k4Qujka#`tk7L zjnovNAkb9+0R(gvNCCu>f4~1QF);xde}DY=^Xr#I=|`5hhp|NZyp>({@(egUPqbabS$v+tIbNu;L>xVSv%=>ggR z5I`*V_U`A@(fj`3!N1?XfHr^}17rYw{OcFcC69Z0BvMmfELgy8V88%&5l9HrLN(5b9xs8l&q^Gm-@&XkD1Q1BW|346ee?wJ+H2_`p`^l3V z+1Wtu|3AMO{`_T8Q2{Cj2q2bUARGR$NlJo(=nv2bFrDDw2U!bb{QV0-KNuJQ0*G-b s1H(6@xcrC2{sj~H2V-zBFaQJ?0G2^Lae{Q+uK)l507*qoM6N<$f;6K8u>b%7 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/ws.png b/lib/ts3phpframework/images/flags/ws.png new file mode 100644 index 0000000000000000000000000000000000000000..c16950802ea95b40a4e024be6cce870b1991f40e GIT binary patch literal 476 zcmV<20VDp2P)>E69JA<-5ee*C|6>(%ey48MQ<`3*E1sOaY}WDP*a00a=wNxxYB{9yb2 zOU?HFZD2h;3gJl2w`NO~f5I`Ue%-|qnfGfh(_6MlpFT>xzKtBKk z5J&^l?>`KZl3-*0GW-GQWCR<@03m_s?_UT4Dh3E3#;>Rm$$$~H$WlN65MTgr(_Ikn S3@&c~00004%P)h=Z!r1m*T3JtfNUmdS)dI70mLHE z+xy?%p5eHez0mS(A>sL-rPOy{yGk_s3 zz{tq>=+Ps90Al&~?;k`pNCN|I|RMxbJV00L=%3E(v!XamT}{{RArWx|9$7Z{pWi2U{IKN}lRCnF;x&=P&!(V3Rv&T;|GP6K500a;d#1A^U zy8js%fB*jT=jRXRU*A|5K#2L*r(ZvQ{P_O!=da%umKG4l0|XG%NkAKznV4BvSbj4y zawHV~fBoj~uiyXJSQwZYIoR0QIXM3O{tc7`Dgy{0# literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/za.png b/lib/ts3phpframework/images/flags/za.png new file mode 100644 index 0000000000000000000000000000000000000000..57c58e2119f402072640ca758657798b621f3fb1 GIT binary patch literal 642 zcmV-|0)737P)Cfuwe;F7c=r;qyPX>nX3=Cfx7(O#Fd}LsF&%p4OfdL?Z7#j~W z{9>KL@b8bfkaUokt?1q(EJu$q{Qdp^&mV?AfBydlG2rBvO`8A$h{fmzgIW0J+B07s zK74uV#pQB`MAdI!SdN|k|KJ`--LK#OfB*Xb>lcvCC@cGqfdL=@05Jg0{{a910Lj6A zAt&mC*9zzO1pWQ@b1`Tn1`iV6=KuBe|Nj2}{{8>`{{Q~|0SOB6tE&PCq@k>=Oh@B| zgDu0v2b@p;u)h2C^Y4EK4rY$O|Ni{`3qc?^$?%A8m^2X}fLMSU{`~p7aqHLD@0rW| zzIWc|c>Ry-$DePXKfn0(_xG>AzksTL{re5n#{Tyw!wUw000L?F`}dEV6O*(3zu6Di z9{gne@#pKG|NpqSh1vf81DXBz&mW+Me}8zmL>PD&00Ic4!QY)xLzl7RCikV!EWdtz zoorLj&BXoYF88-DfB*gk`{Eza7yo#;S!C4G00M|*elvr*8B6X-zBgb0FtGkEHc8mM zoOScs_b*;Q0~!ksq<=swfJjA!^Ww!_00G4E&x@hvA~zQkvxU59n3VsHd7Nj?ec|R~ zkmuqAx#JHo0{#K*`TG}00$uYDAb=PdGJ(Ek5Vx?d6PS=4{E7kSFNS|$^b5xL14ayB ck_{lh0N_F{UmK66LjV8(07*qoM6N<$f>aVd=Kufz literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/flags/zm.png b/lib/ts3phpframework/images/flags/zm.png new file mode 100644 index 0000000000000000000000000000000000000000..c25b07beef894408ae11c3be294d6e0eeb28c0bb GIT binary patch literal 500 zcmVLz(k0+Rp#|Nr~{@4x^5fB>iy$Oe)U zA3pkg`SAPSuU~)vaR2(F*|!oPfLI`Azxw(Lr25~#KmY#x{rl(7|GyyW-|wp|{||Bf z=)Cuc_0O*fYWnY`7ytr@1!(pEfB(To0uBE22c#NA{{8#+_rKq)zyAFG`zKdY6euFW z2yzlY0I`6SGJqWbGV%`;{r&ytA5hcp-u`P`AAdOg`t|1D?}h2=^+y>10tjRS&?yMj zfBygt2HEouXg1LCzyJLD1w#LR1MLR}1V8|>0KE@nffPa916Bvu_V4#Epof105yNkw zzknJ50tl=DS->P)FK#X6%e&u3i|I74$@9uP0*YE!se={)r{r~?r68Z&3 zzZn^2KYiK`5I`&p3=IF7JHD>uaSofgtUAf*-w!6nU;qF7`S<(x|35&+uYZ4j|Njjl zS;T)|pU40ZKrEjoRWbQ1@P?oGFzcPCjLiCzcU8ZC{rmejNW<@6e}U*1kPT$~=Kk_~ z83O}A05QHjx|ikdd7h}_|NsB_wl9j+&d<}^`}fbEzkmP!1q4tEL@O#P9zA*#Ab?m{ zv`qe9Isj6Ah@Z*IdsTw}WYHgB8-D!*lmGvK*?)d>i2vThzyJ_HEdLn(@uk-N0|t-& z`$Yv&?#y3UfPVP*`ya#a|BS!>G5-DsFR)1c1&eHCHYDzO7ytqc0P0;>l>h9)WB>pF M07*qoM6N<$g71PE`~Uy| literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/icons/ts3client.ico b/lib/ts3phpframework/images/icons/ts3client.ico new file mode 100644 index 0000000000000000000000000000000000000000..3b3aef0ef109091c806213a6e332133396965e1d GIT binary patch literal 70333 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

lG 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 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/icons/ts3server.ico b/lib/ts3phpframework/images/icons/ts3server.ico new file mode 100644 index 0000000000000000000000000000000000000000..3d5c2776009e03e7951a6434464909293e5b0a21 GIT binary patch literal 69372 zcmdSB2{@Hs_cy+dnM_HB218VoIEc)Sq7acGDugmb<}u?jWQfdT#>$jgGGsi4P?0G^ zh9Z@S&?Hjl-A8?==lA@d=lWmQ`(D@k?!No1z4uz5wb#Axeebo`zHeUu4A21&4>O_^ z@Qw^14*-~%f7Jyr08!)sSS%nu4HPf}R8s(w|6Qj6Fxf_u|5dl91fbf56rh9w9yoOu zz@GOHJP#D?1!#Eu2OiZC?$R;<2gfh@Fj@e54Cc2!9l+iy@6Y1yQBo7ad6YhVuQ#+E1G_ITE&sKmC z+}|psqKD}}8)z*4sJpt;grYL_9|F`CG~dEdgX?brY73@sVW{C(SE$h)!lJ|QKy%`^ zz!?Zb=`VnXBdUJGL&$HVLf~6y7y|sEAjIGofEovG9!NpmQ5E&YZ^iw%P$Qc7__cI9Zf%qQinmkOYPn@9X@+8@d>fq`~<*rQ~ z=4l z#4ruRZ#JOO1(F21fp|O`Mh_Y^QLef;Xc#_>+JKrw(bVxU3?<21xl@Nl zBRw9TVbLUbC@gPk`PC(CkS;b09|SFVanP2tE&U{=TC#Zi9Wa(>wufFHe5B)2G{fFz{d0(IGP%OySV{) znd*R#nGRev*9RYSJ@CDB1{}@K!c~iN;A3q9hR3bovc3(NpS}v-Hs;`OcM*K-E`f*r zW$<>q41Ug60B>;y0xS(7(E1$UZA{>rtucgLF@Z39GYE3Dga~H~2z0iEFjpIha=#1_ z9(EAn=>XAQ4iM{Q4?!M|5ai_w!6;wzafb+BHwf~*ipri4;^zZl{@#$}?*cJ?ZV-X@ zg6IG*2oLaq=pY}s5$q2M0UnSN=mD{TUXT>z1qs345F6|ZH?LiVln`G?5A}hxaDPY- z^M#!2zQD&9iEtC*C6J47{o`0K~mIpxEUP|=?O7#>qa2liVB81F(FVKcMY;*!yzvz z8VZskp(r&P$`eDOD)l28KzRzn!>1r2VFuFD-at*w0`zpYK~L91=kK19WyAz&2X@kL@$1vI31uyy^!}GyDcriKzQ-j^`YPb(xKOclw6VKu8$%%4rCPZmAP zTmGBWKlo#rCur#Z(emFaW8M-}LjO(ZAN{WUJnvm46fpB(YfJOBRB<-+-GMF z@cmQpkJ=fmfEU1~G^vY<_Fs&A3D$paktvoHNsNpm1gVe3n%{u4ScX4T{(}~a3mii8 z?NQeRsgK3wh5BFY0&%w7e+d4rJdwi613X{sseZh$30S=Ui(`O2`0wOFY%EqBSu>2rz81+>ldfQqK;kHp?xg7Mz_`SO z_~aL{d$2U5*5g?0#Z7CLCy@Qs)lZT9axaM^MUs~Zj805Uh>N)%yi*KKd;kHg7(g3I z>tiHcUj3M)znjF7AYCu+TFO*PQe5Z`j-6s?;*-pAfUk2%=9l{NM;d!(#;0N@r;>uXq_GlB0IXbQx8@rK?HiEYB|?F?CXi`z!e(@y&Zd(k_NQ3~={P^$SSgj!A1t;K2FpmMkZeNMFE5a!gs~{@`kkIB{vnSg0Z8gM^_Q3Tkf#4JMo| zNvj&tua96@v3!71k~qR3@#RhN3brGaf0I9lphu$UU+q(*d}Wm+$9e>5 z`78PUl2<`QvIQDTH*J4}p5En;-$_!eM|}ShMx{oyY9js0Vq%hhD6U4f)IOd?(!Z3$ zHbpen|41sE@`~6aD3126?dq8zEhQhn{2&RrupwP)_{FneRz*Gj zyHsV2X3f7OH9_O5e`)$#wdMZbD&Qz5YgYC4?=AnPJkyZlWO7=|#G&v3x|07bX){>b zJlhD^+zt_^fNArrgNcaQq(tJB|0bWf!TpO8sejY9P0CH2BGC;h#c%$I{-mPbP$Zp| zP?7)SCeaWe{-*v+q?yN2@Wqi##afQ2uXm>bYZ*aQ?U~iV_Wv2n%|Hw3r8+R!{~_IRj9aJ&WQ@3s94>LGi05=;F?SsfH3< zI;8|QS~#%PQbBR%39vh@1hzWLo3W-niYqT0Xo15yEpXI5hO#m^=_!Njnd6%=ryGhn z-3(R1<-8Vn{Ejv4bacVtyf(O-pjeX>Hl&_khSQ5pQewPfu_2OA@yl8~tNh7#& z)&ce94PKYcz|R)tE9T&9Zvp;p4uH2b*o+|qZ7%@c))Yb=%pd^8kJp_pLb$UfM7ml* zpp!L(x?F|;H#@lI;Q-fNts%<87NR_DA@-`>W(*mA)fpmvT;RHoE8IY_WSox^#QM8J zQm8+qUh{&RC>~Uh#G@E61`dng0desMprFtM$7HSn4p$1tkGFw_#zR!@fs-fu!O)Nt z-*teM#bdCs8Q6^T?Crp1xOj)9-w3y2HFLsEDkq(=loN<{Exe3u!2 z1F{mMAu}-s?#6{cPFxt|CtQc@gd329V!XR3#w+|C<5ebyLw#m6suX zhRVVmcu;s79-tVo7R7(fD6XrjDBq0tS_^JMTVXo17iB_6=`DD4FApByzY9-)$9oS+ zv0i-*v^Ug17s?$?bs-)rknJu|!y_Aj0?lzv9H5AOeR# z8x#><2jMRwygR~&A$;mqBIJn@;r?kNv^o)?H!_oP~5>z2FTJ{?81hn&{-jIgkq?d@%O00z7O3`Q5t%`VXI!6Sdd2aR>@EfMI{ zyP>@u;dK$-xYEw*JS`m`iXHcy0AyGuVuw9wZ>t3mL;XwI^xQY4VMUj=n~x3pNxh@g z(B6VN+{2IJlp_|F=eDArb@uQfb!nSy)PD_$kIru)Q0xFdp9VPX;6wMX(0`Mk-`^P(pr35}cRN&>~M`B0ZYi~n=#%{4+Y!Lm; z7ltSjJF>LB9kHbaf3wlJY~uH!EhJ)RJZSIe1uK${|H1rE4ZpSv|F^t%p!rXx!lC^ZaQPGt9DnZ>T+n&6$9YZgFxCQ7t#jaH za0YxXXoJBiR|v8(g5WC`AOM{sU$ZlXFyz-a&w;%iAOiX9SRcpDv)(v#)|(RO4l(Gg zH!0{UB!>8*vs@pza5@++=tqOET?Cv`y9Mg%&2aHzADEkuK>Bq&L8JE$sO=JOiq9~jvo<=X0oFIi5 z3suAlct-3;duO!aM2QvQ_aHph5iLD202<{0XA$0N8LimFt#BRT(-FQD;Ry&ofbg&X zhTn(qIBgW#JEFI5G3fnnIf_6LzI7R(AK^z4{sqF%Ap9J{FCqNbzu`sD6R52O;CLP& z#ucDE3ZVBc+QYY@{qVq6AdZRxaq=_}UpoPDJ{E|}6+ryj|EK*TtO$=DHAgJMD->r{eR&%y)--W(!9t^A4FbS5_xGH^3rNR+7dUl95k)>|qHfuZ79&AuJ@Vj^4ll*DhM1CWk{pLPu3M-=tEo?WJHkwoga`iSr@-bf^vog(THZ!B$dt z+u8OC%F3M3_{Bv=q3wniYTr*{{*Saliutloo&{L*2Ka%be=wkU5$!P|LjWkzoTNl^ zGaS_ksJ(RyqDKIFULp*_hHIZX!9ceI%~)ViQ&rX>MTMJ@A;N)?bAcs#6t}A>E9rTT zEf~|9>J1Gwi938vU7%Iti7CI#Orb1LP+-VjZeKeSRj+@p;#Ji45`ogGsO#IeZ$Do$ zW;cDDJAD^xVhJiOx?V&6Ar>z_Qi#JjEQiKX;69|sf57hb&{y?z=CusfUl zv*O9Tr%iZrS3INkY(EC1aMbVoQy77Ok--t$(8JBxhgs*&BL`C+yRnAJ3YXQ)2IKmJ zpT6o1vj!iXj%>(Gy&G#SB*IK%Np;LjOKR83v$deqe(Qw(roiD{BbUV1O3s`VEJ?ML z^tO{cnfm<7*ZTA*ot`Tn9VQy?9A=AH-|hbWDSvd)v$^};F5WrGqjtmh`a`*2EiTMD zM z?k&@ToZDmPU%AUjT3O_=aZi+atlSFyeDM9ktQTG;lo4=S?mN4ko{utq!xugmnw#g@ z@ob2aE~rJDcP06nIIr*i$DA^)>{!=G@%nQo&SE(({lvvwJ7%_SuSfL0p9q9hIs5TT zlH&MCmCC-cZdI0SUsJD&8_i~-X?vNw47#0;+0V$_-gP6THp%drZIz*9%o+xM_oEQ#+nscE;_3FBGsT6UsGAoi586I-h~n0L{(R{ZEXw}S z!Vr$U{Gqq}BRZ{P|NaMB1qB5P0fwU6BRT2Rj+cF^96QHt_4S;&b6M|`d7Jmv&4wEo z#bl5=b|Ocr%+jR;z34C$Qs46F#GPrKrYy<8fV#M06TY5d#)zTJHbv8c>d}wOaosof zMn_rQnsK2x9f!fG++{JMjHztYrY_dm9W##+t-f)RN%GM$P*7v=nnDzeKyzh$O`#}e z509py(0T>yKF4t?`A}vxna>##jm~08cH^h37qWjgD*o)DrG{IweIh&H`mnnthbFsStV}|VD zrC~8!+pnz5j;~WCY`H`Q39Wbd8I?rs$wA672Hyh%6l!#Ac(M`P~S0Ia_;>p z_47NIN9+qqNA>lZT%J@vZOZ>~E8+7*WJ2Tw#yvluVBr2|wI)JLdk z&5mNls3|DvV#uuRJOI+Wr*rml(bu-~@Kf5S;n%ZiX(?E(mOpzLMdc2?7*jd%W4@0m zic9?d5KBMC=EAxP5IRpb$+XOsXI*H@pV!;bE}in6XCHO=ifw~JwabWowcR-5nRDmV zyDzl(m^jqt<=N=>U8sp{AGO$SF>bK<;`GEJPG5h4`c9{m{L@ zXXPpno^v|H7M9@j|*+MW5dXE@<)cLMl-J; zsJ=KWT`qyEf46{Eur^bAUBVQxZ%3TDN##pk24ZA0dX`z7ILm8M-Sf^*U{WPUHD>f7WQ15QoQJYwYuOCc}V5+GZih{vujRCxxWth7Q~osPYK*U_vG@2 zxjj=|*J+u>MI&&{)Po~y6%-tEKPbTOjQ*m9g~glV;)e7gJp-qj%)GN!K5JzoOhE9{ zDf2rY09jBvHs&>WXyA)v4=6Z|v)VPrEPm z0$vG!wXwBz>B|}MfiKdAec2fmui{4-0gp)-s%(}$RJ0nbU2cDSaxN8DFSZh?vK*(g zz1T>T`GZQnHha3dOm>yC^t?MchDuu^h2BCkRWnT0XKcp3$!}P&bC=_x9U7BlaHd31 zX8ygyh_7<@Qh+{ZrF@{1jgRA-wFg~HZ$s&rX@WgtPGRxBRYJIKZbnMv8D--JGj4wo z;*b;GMR&@1e{ROMpkz#dmYj&v%nCIfeQ~wd_Lf_Elm#>~bhV9yR((oFs;;8!tvlY7 zJ`8L2=;ivP8z--o?mI84lHuFi%sT%OmN$z0m$+#{&PeV4s_`wQW!c1hV~6tF{;?BT z@5yLh2P~NczY^mL+4(Axn{It=hw`iU7_hcd?^&?ueY(@r<}gd@6_?t5%6GO#U^!Df zm2a!)3Ll?VW5#7KP_D3GhQ*3GbuhiQ%+o{5JSiG^(npNFKQzmH_p6t6y9}cZRnI=w z`1>{K40gAPhWYS+^loh5FnP4``9YMWKh{4ehJD~9-!2Gb0cCo+xqNEKCcnFs-TyqN zRMTB5zVyzjE^~y6;W3A!jr&_V^eO6N7^SdMt~^uIk0%@pR;KpSpHt=@a2k%8$Ie9U z6!B`VT&k+qNv6U-?AEJ_Kg_29lG*xI0*$TIIDi8wy51& zB9_;OKFB2I1bUqhP=NrIx1uySfLaO~9+v)nS12UoXcn}YV(LK_vn@81=Gce9qRw=_ z6AV{>Aa~K0;afHtUMB=X-`*2*f8MWnZ529slg?k1{W~o*P=LbS)ulrj)#$s!m;ye? zZnT37B|ldrZ|2g8UR;^tXrCCwF$C{<7&*HI>m$s2S@LM5L9V5w{=n+#iMIFd&$S6N z?24Y1-rN(4@ zkYt+XF|2LNz>St7q4B)q&)rs@2i!AM-TR|4&hLT-?zDPP-C@@7gubtm5qXKV>D3H( zAC(R~^*eH0yR0cRPs&KmAl3_;m@OKuh`!7^G&S8N$w+ag$XH9~)mO3y49v_Glx*^) z(94!dUO*+Bd~toTxqdn5X;W|MDP-UWU)P`YKXHzGICh69T6_)~XJS!0`HT$=KR


0u)&jX?d$$&JqgkN0bwP5gA3Yf}Gl=x`K53eZbJ03C_$I6&$zjFZ;Nd`bg-D*~UQ5&v@HygHA{Nb!B9_ytj`#Q_kHA zvwW!k6Iak>U*j`Zel}u2XJT?sVd--Dg)B3%)cn*#NfnNKH*1x7Y`6vP;#4UZ1-GjX z;b(bAzj%_R=_?DonpSDuIxN2bqTgY*`k7UOj2{s(nUOidv{2Uc#i^{U`Mg2ka~3~e zz-#UJ!Mv6nHbw>_&1=0IZfWY*{=;Ni`s(z= z6nPJ`pS|bZ+4f&Gx~CJ>@lY*vviiQ&sket&_BX#s_P+YFcZ*e8ci!EtahI^B^7c%3 zDft;{C+lv-7)&X}MTSeHtZCLsG1q8e0>dTB1>a@u#0P!L{b`iLr9@!fH+PZ2ctnil zfk6&`y~6wJ)z>h`I=F22@?<}6=AvOfXZ1jvjzMjFD_W8uP zr>{JgEhjpi>+%-sU6al;LnZ9{v;5aep1Ypb-P#n1ncKsRmzjENn;b*s$<=tDsUZ2{ zz=I2AwASV;JlXi$q8ur&;Py|48_`9X78-gS2kwmvog{Br)(M?lFH0{@5Wc9z;V6J7 zNS_=Oy7;w&IfaOw!Mqagpt^1 zdofdQy)%uE?isIT?zws31$Uj|fY143=t-CppJjms;WH1#?Zoyl?8*HoAKDuGcw!-P z{PMEg%Q5ep-v@S9&N*qRsO+Iv%^%#0$Ma*72Or)zFEBV{xV<3X%hG|4f?;*2h}j!M zrrWmksBL8D4%%teLuXZ1zi%y3Jf-DU#)DgMt+?73Hq*-!lBL3azU$zw)Wh=Gsx#AV z?ws~Q7{Wdr?)5GKEc257+Z}pG3+w0VTrSKnc)u4S&78JH7aQy2pO+hc%3R*qLUBHq z7=-42%~MY?|MQnsD%6smRXZ}5KUT@DxcT7Rl(P)mV0PWc)4S|72cmyGxC$7_?agm^ zcya+BF!<(`y>PcqNv@};q6v#>G4J5Ps@4Vr9&0YvV6)hRjERy()dH;7eG<}N`zZ1a z4PHL3VxV&~X|Ue$o~+=aU(Wtd-b2Qrh6_$}j|NU<8;Hd5@SMDv`rsV%LvJJX@#L4{ zi}zlk*NIPW3sVD=!rFSdo$TXb0ya;_``SIg8TKtzItMLLeBk#@$i-Wj@zfH zKPV3d*WfD0ayoIWGF8uad98`nGrqks8FJde-E!>yd4CV3qTIR*WNy;%?MH@|iJIII zrcX>qpkt0GHg=G=iW2M_W@0xoEF>O|GG0@m=8iA!bcnC|I^+A)bO&tn&2;;jz|1GuaFxv!iTIMV-fXF4wR9(d`tKit#?6(`rNpB(ILRT89uvK|F{uZd_tK) z)?QvRRb|qL472yRb0Xn@(N!rc=eX~8FuvXy-g8%Hwu!ZhiwLD?2H78Y^{QprKKev5 zg|=MK$R`JC=CJ$_VHLr>DcjpRPttvvGL0hNVj&gdPOc~)T{Cy~P}fr8ic`Ibr}#4~ zf|(V+)$6CXddN(U8&k5JweyN)G2`I-?#Rk+a;0h~{5# z2CUoqU{QE~DD@1TACAaWJpeQ|Tfk^FG7==W!;Y)l+!D>9(v**;o) z`&8yTM$-f8AEmS!h*3o>W0aYG(^dIlkMgJXW;;jiq*5v%J9R`Py?-kYvlfFngT@X1 zASxan#k8GX-0hXKN$o5V`f0OHV;6YuX4l>+RXpM!Bg~4bKxz@uZUo^a-7xXHY26U)!Ci%^Y>xu=3 zz;RJ?bvD^xXuHcnCq2N&ktx5pZd0)rM^M+n8<&6LqV%sY5Lw?dziEn*o zV`pcLYOys2FQmiB@%zEP#giHD+K2Veo+$`F(lW7&<5>{Y@Tz9zQUsRgr7GSodEom= zO!kUo&-){;hBlUEYnGLgyOuN#t5iHHH}?+eGdJ5(AYxMZB3EUb6FCK4k?LdFRjrx} zXD0+jZe`p(i6Iy>J(aDl8>?Zx)3e0%ZtW-FhbcgiH0<(Ues0i_0ljA~8Bnl-K;X%k z{kIGyCWW(EbaPIWER6DnXv+D&Dr>f$^9iNrD$H>DV39St`@@V7g-WY2!OKWOGFcu@ZCq#fml9M?m{so24i zGo7EUh0Fu!I_r6z$Zuurq$`nO9-R;1d8%ryPt|ID|6@5XxyaAGg%rkgP}*$wwdZiX z!gY-9c>^Y}?s%^A^}WaNW5!dMso4wdp#tX}#R$?nkWO>o6%wfzI zOcsZ|Kc^hqZ{#@~ekhPIr2C=kUj0B)-x}4$?Ca{%F<|7Q_NqgP`j}(fpna~yz>N~0 zdOm%MA5CXARiZ> zNoH$w_?G5t>jL^?r^A%1l*L^x_nFm9Dqen7wq$qXR!zNjG8uk+$$#3|{9Aa}*P=-+ za-M>cIf9i|lXytLmOffFK|DFZq*Q5?Qs`c0My}FxTi1u2^0ysZ6WKF{_s~oz%!CKc z2gc^Mm~?=oc8+ULc(!p`(51IuD_4a$HUt6)tdR_j4!2#|>3GUd@>svyr`KVkC)dFm zb8}%$K=$Bb;Cqp*ZxI68W4SQp;h|8G=sGRO0low1W!DL0d=EHE1MVGOos0&(Jl+_+ zS6QmF<%*N^e1-M>1j|Mz5jpph5t0G*mI@xVBeTTx4AERLp%6y$i9i6Q#I5tEuVyn8Vnt z7bo)G6&Nk;4NIol0RbP^yU6%?E*T9h?N{)7$)H%Z5+{>UM-LloJ^o+U-;RHgBWG4q z{6dQ>VFI0JXFzK9dcC>jQ2Cx+Z&qMh_3$m$K-*cK>}O=9j42imZyKt3*$fnPen=a* zJ?cFmIIAVFOXh9XwBzWd={-RA6mwD;QAypGcLjsd)| zV1Y*mNBjKM&)pw-mJ<8z3zeqLnm?$}H^*4|xGKc89IFn&nQmp!wo@kyi(oH*+z_M6 z|7y3wC4#iU1?O|2^>R0Igvp_%+w%jxKlU3}^?T2uEIcEWF#NK7^>O{hn>rX8pY}Ko zz@@x-g@N%fe%7YTgJVJ8S7lxezce1Wy>-E(Mh+{HCvDkzd#eIZB0pZ{D*A%j_DN)v zEg45ne%;w~_fAIJTw%JNOt%AYeCGomiXVYX1T!P_mHu)-Q&q}{+ z`N7SgIK?=@{)3w|+)D=lkKG79N(KoB%O&{bD9)vy%MQPy=rwlt(Fc_~Aq-NVT3xg* z_s6)7H)=J%E@3djlrkqe1}ia}f<6cz;-jHY(Xcscc{9x-=jzyyUmrmsdnZ$m6ALE( zhVt6T$=&WxK7ZEhJ;;^ZXdG$(BXpOWpR-N$)x#!o6DN7@P$$~q@PXE@HkHFxY=vRk z5r-KEURw4%@PE8}@$1xBESe~XpF%L*R5BI8H@BLMux9W>H1G00`)|io_AtWqFgegv z7NRc&x?ZvRbeL7XRg{pQ#y@!5@jdD6V(A^`7YFDr(EV+_c6-svUfq`}iX$m=(rW8v@~eA#YMa!Z8r6C5 zI0|1{ecVHO9!pAmVtiuL^6PEKFNishwoE=g)@K3PC7k$`XWP;HWHrJbEC17ZkGm6W z`o(1}Uh%D-vJ(w>H}>*n;v4l3D~IIB3ql47Ts$`d7a!)BwikcqPs&K`j+2jamkt%C zRej5D5*&i#kx}SOX|qf{;a`8pqt~@9KAMrs&!~Fntxt4EXNngyv(hn!9L$@=So@=i zCdEg?CQr={pxE(L{YGGXy$Y@5ifcV^#$q_Mj41(E$ImkYl$DWN%g^4QE|YUv?MU+T zpBp;D9iN#&ye!2!fEAGb^vPaEcYYWRKkU&vfWZ;k(;2^u<+p`& zwb;jMJlhL|FnWULygJ~i=|?hvOgx#Y)(XFLq>R(BZzfFfg_m)}mo-2C@nbdE%0)BJ zy~P6pVL0Ak#+{d$RF!K(ujnv&S>}s{PJVOgu;RO!=E=yRn{IpT^Jo}{wMe=y1N?$r#Ba{8Y2wrTd$P-eiz=Q;9tZ_0(mBD%xg ziuW(6@!%k?|Cy2AE$OEh;V2Vj{-C?DUvFehqyjOfzxhGGy+_aV*B$tphj|9O3$-NJ zwq~7)$Ro#Vo{4?6YHGfV!gA8rIGHLy7z4*IL5HpZIWSX|ckuNEr!j-+utvkincjs2 z+US@5E)hRp@3gPm|0Fg2i!TpzcWDiE0ne@KdaBjrDfNXx@kvJ>>$q@hdx5PfId%m5 z$_7oAmZ3(MK?XNYMwyNFgT|T99=I}YnFwY}JDdSySeD0lFxIAn#hyy`@iePC1_|< z?xKSQGK%}Y0{blwx47Umy4vDy@Hb*^@#w>7?>Y&<}3M1g;lWKm%)_`}Y~fHL3|HQW({nY%?~pc>DU ztp@=Midg9_Gj8FZiH=X-V(w`twf<;h<}$fGev&CQa99W zmS-~k%~w^?&$SG>0qdgy-)b@SJ#_^8nvH4$_nT&=wQNcR^EWRr7&6O7SEGw4*p3;d zv?I%4gyR;Z8&3tyN9b-^+@jMwSG#tt@)g(RYNcL9rwb7#0gpyrIA8K||9MYU%KV}nxSF4piV73(F=o8EopSI+9z<2{?MFvyRKGtH+NUwL*lEfBBiMYAJ_ z@M13pkF&Y=y*Bjcw{c1IqD-O?njek>-QrM*M3)IfZ2waV?*LC#gW zFl{qc@}t||?Fuq=2`+j!DV|Y8dE7+znnOEqLWrw4|ZazGpj!p?0$XoY8PB0(bUV?Pb~N*K&-nq@b4fM2N40{j>GS6whp1>Y zo8B@#l5S)gQy$O>*(Hvb5^*3LHPx~kbj4T zx;Nf+i+i3@xpeD~H?tPyCeQDNaM!4@n)!y98CIs`@18i)iJh4$=hJHLv2p7@TCP1a zfdPk$%-R)X2e>KxpQxVn`X1J{MMUcT{TW#ak8E8Gp$n~nHV47Tu}5BUQ1*be1AtkK zjhCJLtsPUtm%BB-jTc4pcS|&{eb}GWZ{*7Q*tCtRZGg3-H2XZrAf}u;%*`lZvFhxjingC}t1E0)VMTcpqB4gL`d`cNF3x&ufgl(N6fbcTRbk;r=WII5d?~ThZd6C?$9#$CNap7fBF7E z@3MJF0Pd#}-{hRUm&o1_?zKb9+BD>^$&!GLa0OPfzY>A=;a%9H+Y<9mDggs*G~%Wp_Od?qH@?49WQCrL-9*)D%}aSxWq1WXT~eo zTYH!#Mh7oOcRf$6@L~EgvtbTDjz`<3=4?m9@;AFQ*^duA#tN`9BnNj8rW7fGj(Mtg z`1Xq??uH*a5juoP@vOP+=3ldpwZ!PNZgYDlO=gTt2*$qhYZPaYxAf(3I>tOT6k>Q~ zzE5c_#FLuphSao=mU_q0SS9Q5gNH2N6b6XV(Y0lt_1Jn~2nViL?37G~x2{e)8HYZg zmSup*rx2O{ffh8LYM|XT?acP!{=R3?FRuzu;~L)wuxB!c+&(f@wMUEambv6%aCF`K zlbCFGO3bkK_9=V0ZE1&HwF7tsAM!;hd{;96IbKn+lXB-(s`f7_uKM_$)M_?Za*B6e zaq&hDir<$H&ALDP^dVtuKNo3U*xwy7HC?--c-zT6&|1ZE`*iCr zig`XtB~SLG<0|H^z4#vI?@zQe{R{Xq-SPqFP4eKYl(5e#ub?>gh81tt34WL=y_qfBuko41CsJiPZjn*0wPEjK zonW6IVSIs!aju*PyxZYANj8SvE9kZVc(Fe_m;1))N0;T3j^Ej1m92DlrS==dsBd9+ zBD;+jlDp_?ym#(yW?QD8_04G&?KFi5#=Ngxq#I2Suhv^WZ{;NH$pydp(VT}%3TfIG z8ZWOe_@A@;5HCv3%cJT^zN;arao|`zxtZf0!>%qVjFPClFi)Q1#d9b2Y&p)Y@Q#J= zDb{BpwI=qN#FlZ(#)D3S-ypu5vj3pU+4(0ArFRdjh{!vQTweSUDVsjVhvv2)C-Cs2 zkIsCukKZX$@b&*tpxm6bvfQBiLupWP`TW5p=l6XV$?76RwYOTfO^1HA6b^OT8pN|= zltZo{7Wht(BfVylYqeYwk}h^C99~SKUq;^NgOq!^wRdw|!e*Mr6Qs zZObd)vR{O}Wqc;?D|Bk$)lzbZ=3-aHMaJ*dC9@pvujd-CxUcSzvCp$n%rgo)O4m0( zNU-QpwP0l}#I&V+nthYU=)a(~sH;=lhRJS3cYBT<13ubxZ>c$!CAx#A`^HbQ_ndsU z@22$ZjWk;yw`g+>7eO2ue#pk>#H}bEerDb)qpCV_XBu}9R$qMz$$sfEDtdzJg{3zE zf2=bzHgo8^yu1S8Kw6}=WMg{D9?dh?@4eeQqo<@-ZEm{s?9NjCcgr?$YS~>dc91mO z=o`=L$N9e4;DrnCIUZib(4wTxrgu`~z4XM$bKQv^0ST{$$s*#Jp?Z0o~(CEvz*~edm>QI`p>b!?pAL<8=u^N0GoQBYK?4H+LBl3)g^sO9@7qW+_u<2OwQU&F$Gcr@ z4r_iQOm%+o4$8hx&0^zqeu!{XY)+Y5bzrxZ$i3JEb6 zGJJ}k#Mj+l)1D3~#H(7d2_JPaV!e}B@z9}J?!7W!mQ^Sjp*a5qN42pf46luMq7z!Q zv+!J$Ke;FNomBTXC0dBJMw15ratn7DM~u1lM~NSIzNeKhv?Xt2qvsVg%~xbbXL&qn z6!;ItRQQ;~!LNDw)Ot2de_B&SZ0w4Vm;caZ)-3*N*PSR?qG0?ZIm#>f*TW9aZiiG& zr^MTzkKc{lD}5;5#M>{=Y%)g1m14rDSKQz>9UaFL1A|TKlLy{o_^<%#aN zDa4*XfBf;cH!RVEhpk(-RMM+)%xiKwoYXS(q3!6c6xwH}O8Twmskl?!z>5Kb>x9! zC%~=6$TwLHH<{s7hU*{;%a4w0;h&~U!km^Rj$nsf`2`yj$kBJUx7G(^YUf{aH>8xC z_hnf&ukhV9x@Nh38v%n$sQgfh{ye?FLDE6ZlJ$}4!HhLNvy}!>34Q&=RJoNS&3+HV zKJAiOKO!P4e>Yw<&$CQvFYWRr-V#oI{Lm3xER_;jKx=dJPs22Zz}+9ezT6mZej3(s zdnD07PYd5)cKcq);;r1mj?No+C2|HlPt@2n4>jJgt9OJ)6`l~65`)3<)!8GCFy+Pb z^5WZDX>t$9MGAscta7Q|0nYaF1C#27Hbw4?cdJZtYr`cJ4RZDL(iq8HDo1j?Raypn z4S_;YXVs<_3_7EdqFHt`yi1m==pZof{pR(S4LkYHsngrUZ!>QE(e!U=Xx@NY=nYrP>T0){#~40iQ$Tg`Vx_N9N?;=>&b2$ zhQQ!awm4v(G6gc&n7xy{jf_w`SbFxTi0>{cjK8X-==Won#6L~goeb?n8}R&AA8ls| z{LjU)Cf=odiIX)|pT|#R`c*o(*?IEqZfhTcA~C9A_QH3imt6PqVyaJPthMp06*@65 zk7yEm!cTDO5aM(2w#J$FAB+y`4W5peml)NS(2W$4QGNLNasL??&u`uia?kU^9=c!s zbdp$U`T6POcCA7EDCI?Q{vU)_Od;%*{#Du-)$Mo&g7teD|L~1Bl+vG$B?K|5FnM-Z zMVM&NKwGrj7gi7FVKN;)od--W_$~c4B?Gk?cx6~95(3mQFx0(#An5?b@WTB zR)d-iu3Y;X)ktTq#|2rSxxHc*^qpfY(Qy99Q5GDJp+GwZ{WXX4YUyrY5vkQ@k~MpJ zyPl4$GbuPHKDxvuY(raki8rE{hgF2KJuyPpjc+AY<>mSN%i-eV&bvdJ)+B6?cn-7K zdBrfwK715^L__Z3$cac3&2{vr2j>2onkA*go$}v5b>=F+byR)#bTs_UolooFT;fvA*Ey)k@($+5igGu6dFEH6C{7PazH2|GcSh7S z+Et$0k{y3-E?_O@{e|C9kpS_Tq1v!|XjpF;yY@NlYBy?t6;4 z5FI7ZdYmTGH)5zxi_^~6juh4Z@<+7*`|0Zddb_2*(4T7vpi$JOkKl$i=ki*qG zg7_N=pNF)kWH2@eek1YtxF}8{s3A)85$lo#`Of>*X7GA@LjW%FeC%+iJjt{DmymY^ zTMP`*a=5KAeX=Z`|Ivt@EN*%H@DQP~TlGEIdiCY2;AO7yzv>C@!w|QidQm-~O;AJNWgrtMQv406(rYd6s7vWpr z1}v?vzO`rpS-r5|EIP8AH41U30VU(y%0~lLWe483Ff24CF&UDm)>| zs3crE{=d`zTXpw9VtabOc8+9y6N-gGPq3XFiWK}>S7VNG@EJcD9n0S2jY}R0{Wj7VD)JfY|Aff<{ z=RmE7PZL&P{UtBYRz)UOJKy6ybJ`#3sIXPj$JL7mT}7|Xwf|DH`<{i%9ou%`L-?bE z!M?XzgLCd@cX4|P8vz9}6rJBkYQg2Bwyy>ha#g8fA)7PrRd)gEv#+B2)fhXOADw%s z9|FT^{XcsAwUGdiAMR^ zj5q_PW<(LmXlU+7eMcTPcsU>i0_gX&xEFrjl{s-|f79G|#EE&ceQ0%rJEp~qh-{Tc zT)yc~9LAn01ij_vrmie!HNIb`dN7`H?_BlSs}xU$@w{A*?o-m8L=DIVSo++nMn#;K zC)EY`?%!3sQWLa-Qn`RZD5zy@NPO`(zupG382cUH_CfIH7*~n$z&MZ1M`++4Ig)KH zC^>*nIwnkf8Ee#HZ4osvTD;wkSeFW2ws

iaZ#JzG}=BM-Tqo7w6Y24jx8K*3eOr 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 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/channel_flag_default.png b/lib/ts3phpframework/images/viewer/channel_flag_default.png new file mode 100644 index 0000000000000000000000000000000000000000..4f90c9e99ee54af244711dc91721baa5cc1adf94 GIT binary patch literal 696 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 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/channel_open.png b/lib/ts3phpframework/images/viewer/channel_open.png new file mode 100644 index 0000000000000000000000000000000000000000..bed4b522c62f8a6251bd3af67b62d796e7367d0c GIT binary patch literal 847 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 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/client_cc_talk.png b/lib/ts3phpframework/images/viewer/client_cc_talk.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f7f8e391572b0df4344f4ba2aff3dffe1e7de9 GIT binary patch literal 868 zcmV-q1DpJbP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igV> z3KS!eur!MR00QJmL_t(I%XO1YXk1kk$A9;}J8zyZnVFbOC(&dYjI;@9Dl}vvRnXKH zp}1(F6s$!QSAvS-!c7(~TDmA|mtC}7Rl(3!(WWpUXse+%2DCA$B~r~ylVB&w%w#6X z`Pa z_mvVtB$4*f!~4g~{_X9wq)p0|=}kr(p_Iwar@GmbpW@nB*C4Z%=M*&U=FyJ+p6o2i zrYhd*O~NqRY&ir{BB+u{!Qs8v)bOVr`xLBX1_pjirJKO>YAi2aN1FVvswJ>(=*$37 z^q7KBHY$W|f|Rhh1j<vYQk^Pwuj5PTn8 z7b-4z0T4GGg~7!28w2@+&yF`+7PPfNV*?mQ!}<|!$j9?M7MGw@hPy=|a(`Ajtlko} z#oTm(U47kh$;uC>#x zopM&s+-}rgUQ+t?w=6A0>)G{(1LHnmXlUq8sW> zapgPB2d7_}8^2un==h0uKlELvRwxuu_W_+-x3YiG|9yZX{W#~|%iB>x2<4od#X0Z$KKFf|=e>%(ma=Ln`xKek54nZq#3&X?(+6^YJ@xgQ zah0nj#j$Vfi5YwLvpHst9w!y*L8KYVNlZ?q*5L4w57`=|^q)OzkcEeY z%1@4nRmb)&P;Pqr4j-jhpX70S>rG)rJJ)rX`e266TAO@xFpG&!J0jXi+}a7MO|*#L z-q;bb(>=|xEF~RDtol9-IwDT*j3YeW#VIwYOwbRZwcZ~kzHJS|nB1*HX##o=4>;tR z_-rfO3aM3KJJ8qypp<%pLnbsv&ADYnba~3fBioAvM3bdhUO{-eIJS)~NBwV7zPs}b zlV)g>VnPEZ7W3@Tk@zCoUscKWxN9AuQXQjQ@^BpqseB~fZa1kE$BAJO1Vq78(eJ60 zruRgHWC`}*~7&td#o zy}fcq+<8yFVoU#_jP3m_>i>MGdEl?P&(HGp{kx;O7Xl0bX{J-KN`)Pt00000NkvXX Hu0mjf;GdO) literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/client_mic_disabled.png b/lib/ts3phpframework/images/viewer/client_mic_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..107b72536ee89d9dc7b13e462da0bc6752c087ab GIT binary patch literal 977 zcmV;?11|iDP)z@;o?%Qu_W%F@AY({UO#lFTB>(_`g8%^e{{R4h=l}px z2mk>USO5SzmjD14Z`WEMkN^M!M@d9MRCwB?lihDqWdO#X_ne-V)_$*LZeg3u;#f9e zNJ=)N7tY#6U?Pcx=><+RF1(s7F+#bKgqbXH23;1EsBxHV3m15?VZ1OSLdpg*8(9L8 zk9OVJV1s_r(~on``<~Ye&G4S*5BTNxJjp{i=Xjc!0N_nR0&lmsPq%b-ZYGi>2$Y(% zN(EpHb^w`dM!lMp!KjXxYip60Wo>+R_O zd%Fi6W-F?^9#l$YEG@}UOJ%5{097e4v)BD(L`o_AlGHE_94VhYYkTq0qwmt&cS4h8 zSS?lz4u64j6BGDq_)AJk#hgrb+n=}CEoGbW0Kvz{by*`stD5z)7d@EnN{QF3arz)y3)_WSKlr`>Ka!wJsG{c<_4$K$g@ zUhn=_o15#_(rHBF@!}?n#r)3lviV;FLd5-nfWX&Q=kBIcy?_0EXV=wOY^$8jHo-7{ zW%UiAk;TOWzr|v*nRD!}t9wS^Tv|8yxW4}Kv2gg6i>VaG63OyfrTAEaAh42G&Q zGv}0xqS;_fFKC*SRMqJPNz@;o?%Qu_W%F@AY({UO#lFTB>(_`g8%^e{{R4h=l}px z2mk>USO5SzmjD14Z`WEMkN^M!07*naRCwB?kX=ZVdjQ6t_tST7>c(CoG94DqzD~@( zr+q9(pXYEyhSq_)=;nAa>$XtL#2~tevhE@Z@pP14@a@RFs`j&X1VJ z-qyQb_y2bhE&90Yxqp8Ao`<27;&;LWKvPo_7={7MvfwxlU!jyzgu~&rX0tV1W-pVc zr>8Ize|xi{!dWi}0$N&Hzyg5LXoSgRf+R^ur_&Gw0SvDpbID7US zQmGU){S)RB2_%*h&dJHipA85hc=)jAf3NpU@o?l79McmxxpfQdM-IRr8h9BS8_z$# z-d?i$)Txn2Lqlu6GoX|r7z{=KId-DB=gDIfhlAL?V+U*-H{zb#y?n00d;6lmmaK9( z@SwiFY1aM7C|+X?xPwi@LgFs6KKSa%m|HRxAGUc=+Mk+V(t$10RNl zU?Btxic**b#N% zBeI;I{U8(yol#YlpPQSBIGwwq9LL?NtEtKN`+Ng4$B*xmw{OSt(h@SN3R_VT?o3UI znx+8&7)mJ`8ylf0%3F@(EQAo&Vzo|qYHBu>cXl!`o3WfsLVy1rnN$j&78XEkHr=;( zuh`bs_GN-3Nf1TRt!Y}Z%jJ@6MMaxecXTl6++67I-eKy+3oto3P@_@MWD>6f0m0PO zrDi{%lww)etLNtD7w+EeAdh{|KE7GE4$l<@qmc+!cs$YE_V)Fr@^bu_NFc_taCp6) z*$J(!tssPe=Xo#;gYNF`)0s@>-X*u&pJOmsdo^ukVPPSvs;c&nT)5z~?Adc`SXJwr go14D?ekY#?08wX9Feo3M2LJ#707*qoM6N<$f<5A(Y5)KL literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/client_priority.png b/lib/ts3phpframework/images/viewer/client_priority.png new file mode 100644 index 0000000000000000000000000000000000000000..b54b7b096807087a8319307bc40011aaf7646456 GIT binary patch literal 1215 zcmV;w1VHKLZ*U+=)p!fv7f#TG` zAxLl%!EgG`&*5<32cu%worY0{L9A7~=}6b}76Isk~1IN~P)K3@?4 z&zpALY4A7Z!>t0&I7qECf*j`WHIYAjW_h^ivJu4lvb8y9VL`DD`rG0ZKvcZ$L@8 zo)6*!Fng}&gE0q~LGCMnsiR8`P)pL0I_sTkS+y)n+TA3GZ*h1dBmd6 zPPMfiz;QhsdzIzoWuA*aAIRqye>DL_1Y~WSi2PgEdpTYm3RO-`O;c56VcRxyb8{35 ze^V2xX*BuY5PaV|nM}TZKC&~y(9jQr!`tx1=i%~0Ow&XVF*Y3)!M5#pPn_sIAAKfH zCUcM7yBo=7r}=AX39U8a`7Gu0Bbx%)w*7Hm-M<2|L+<@JFw1J`wj5{Hi*x;lE5 z_QM_Y_MRkt_YNH$iJ{5K$#%=KL@D)%Tw{&4y`!e)@k32bO zJkO)EtBdZVN3m^t?e&TXd-l8_fyV+F%d&onMx$5Wdi$M1u~-N%%rE4wUmMM6(@e)= zF?`=gDMdv^1?zH@i17Kqh0ek6zq?pvS@_}`rBVr{M2dx#KwVv(?5vCA;_-M(PcoU= zcmpLOH1BU^;lVt?;1*h2+SvQzUY>4fpt5ocf84l1Ds}to-^a#|{5&#Jxe>qw&{|^{ z#-i35t<7`VG{?0z(OPez@;o?%Qu_W%F@AY({UO#lFTB>(_`g8%^e{{R4h=l}px z2mk>USO5SzmjD14Z`WEMkN^Mz`$y-Xp2L90UdR98&fuL8tuwhNY%}) zO`0uB+PJ1Ur^$KAdGR8Q`~iFZfG?j1o<{^hz>Q28U}R*3wzf6^#>dCi!DpVgI@Yg4 zuiKbTgSzYC1Yr3A4Fo1(I#x;?pExMO0Et9`Y&MIks+gwP*V)s}&gX{68zxIGWL7MS zR)ebJbEVcGU$fYj{<$SE41;7cNj{$k2<8eU&MuT#Sgn(}w#pULVcry!|Nawo@if-J zC~e`USOKtY8(r6l$KwF*a=k&`@i?|r=g6YT>0*tmmkR9I@(pAAHzH=gMBco%C77Bz zI_7z<8~A<@i$+DUTIJL8MK0?$s4B<~ZYh7v?%^HyuH@aJ6vy7L0lS-l;kQQjJlOvb z7fKaQoH@tm-+oO+Hvs_+4>G^;_Nx(WN5KmmdXwAv&L0KFnt_>fv;3!M%;d}bk(x$x zOf+W&Rc)tc7~Io0Npize*q)1Qm;7+}1btiIYprlquXFTdntv}ZvuRz5c(hDz;tL{f zeIb^}m@jK8fu z+If_}HcwY>tzcOeLI`}{Z{2|qf@Cs@A5t`TQT^wa19MN;?Nv;>#^gJhA0zqOGO|Mn zYnq0XlCG{UOw()$G)==W4CZsW3;hrGzjS1Bsyse@_9sP%z+4FL+j?i?loC=DDJ4P( zf*`nlbj?7aP{8v%(&_X^1CI>6dHWqbC*!ex3_~Y!Z{+dZ!aNr*UaIQ4ju3+DY5;Mg b@BIG&6kuZVdV(VA00000NkvXXu0mjfAT6MN literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/client_snd_disabled.png b/lib/ts3phpframework/images/viewer/client_snd_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d516d267a017d35f0c444870b13d769bb2e05802 GIT binary patch literal 929 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 AaR2}S literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/client_talker_request.png b/lib/ts3phpframework/images/viewer/client_talker_request.png new file mode 100644 index 0000000000000000000000000000000000000000..5c09ada17cbabdfb929d5b159a713c4086140c3a GIT binary patch literal 1065 zcmV+^1lIeBP)KLZ*U+=)p!fv7f#TG` zAxLl%!EgG`&*5<32cu%worY0{L9A7~=}6b}76Isk~1IN~P)K3@?4 z&zpALY4A7Z!>t0&I7qECf*j`WHIYAjW_h^ivJu4lvb8y9VL`DD`rG0ZKvcZ$L@8 zo)6*!Fng}&gE0q~LGCMnsiR8`P)pL0I_sTkS+y)n+TA3G-YOx1wr6YglyY(BuP4l zKp>#7|5P9lPy~+S+yLPB`-=g9BuU^n&Mh#;SivY(j4>t@g0%#aB>k1D&`?-wtd~S7 zg)GYuMG-!qZ&l782?_=B`8=AMn&5J|KqjE1H2da zpS{HFjMC04it;A_h z4y3XQoeyqIeg5$FBnLo^#rlqf!{N%SS1v!-4FfHATSgy+9@i&QmXSzNIKFzczV1Tj z$UH3XA?Q{PnVdZhU~c98>pahQG&kQ_E-5V;1Mu}*qW!VvGy|n zD8F`Xs;bJ48{8QD{ j)lmR5@p%07O8?gY8gymP0A9XN00000NkvXXu0mjfoX+6K literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_channel.png b/lib/ts3phpframework/images/viewer/group_channel.png new file mode 100644 index 0000000000000000000000000000000000000000..6696f0791ef24dbd9090da86de83ed5020e58b17 GIT binary patch literal 1309 zcmV+&1>*XNP)KLZ*U+=)p!fv7f#TG` zAxLl%!EgG`&*5<32cu%worY0{L9A7~=}6b}76Isk~1IN~P)K3@?4 z&zpALY4A7Z!>t0&I7qECf*j`WHIYAjW_h^ivJu4lvb8y9VL`DD`rG0ZKvcZ$L@8 zo)6*!Fng}&gE0q~LGCMnsiR8`P)pL0I_sTkS+y)n+TA3Gg9hIJy@)-zWoD5r(+P3X{Dl z_{R2y3ahL_U>Qf_3ROuat9?klw3jrw%m40j|MP`V9NX*9%kP(&ot@q8=;#PomgTCd z3Q-gpP1EdKw=#3OUZ0zpnfWsu4*LlqPkAQu?M=U5y2{MY&+9`&Ly{_tyaUx z$OtwzHl2xy@%e>?g|TQf8uWU-Px;o?4lZ06y~Cx`>5mG9f*?r}1_uYBC<-Ex2$IP& ztYz7W&dyFjQ4}T=3LzekBcIPB5{Y1VcnB=Z9p_!wg(OK>US0;raqxJ(5Cj1L(B0hy zhG7^70cJ(M*~6Ny9ularI6l)|=c*p>~~bzzz&48uSok-*f{)H6;9ad}E9 zve_&ul?n(Ua2yB3G(m_7+qOZei)=OvP16qDvhDxevcXe_!Y~Y!%VoslGB}QfVHj{6 z3QAp2>cB7>P*oMO{4#i+N3mE$Z|_TR0gliAmIupnFbo5xX@OyYrrmT`R#vKtqKK`n zL6K)UFdUE7)m4y21A&$Rip9q?mwQm>gTWw@$s`CNc=BWee!m1w)0{7E-1uy-Tz;HN zzjOPww?_R(W3OO+V;2Vxf5zOW*Kq&-igo?^=U?#y0|Ss{8KNknTCJj1t09$on?Dgd zF?;pnPktHw@S51x(}&_e2QaxRu6%G29*-Ai&kT#PSS-b*)9EXPLP7X{=V&yFldmR( z1M!9aJ;P>qnm(-my^B*)1#g}{33a!GyWjtSP+Kr?_`zXkj|Y;^2dn8|uTF429>CXY zzeAz~6YswcwX}_QGxy+aHZUIR4nJ za0`Jjy;nEJ`iUFUl@fa1J(M+jMyWP~9$17sn8b-nEl`xta|`TK20qdA|?j zSLAxcxo7%s8P^XIa!2N_eyOz8u_x>{AIqdOvn!qJe1!HZ++A~K-A>PK<_5yymw!Yt zd*pT3HkIE}i8A0x5OWJWVB_xnn2q7wPrq9J%Pg_ty55U-`wBnnKc2|XprRNca$4!? zR+dS{(dV-Lc$RsdD%L!`r0~vX{pQEU5rNYJLff}w?wzz-{}PXiwcm!g)Z6oZ|50yD pnNb_?=BT2hLSFr{wFkv3n55<|e|Ob~cLmTF44$rjF6*2UngHbWe;WV* literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_icon_100.png b/lib/ts3phpframework/images/viewer/group_icon_100.png new file mode 100644 index 0000000000000000000000000000000000000000..7c948d4845cde7bb80a9e4c60cf0da3cc975d6ea GIT binary patch literal 809 zcmV+^1J?YBP)z@;o?%Qu_W%F@AY({UO#lFTB>(_`g8%^e{{R4h=l}px z2mk>USO5SzmjD14Z`WEMkN^MzpGibPRCwB?lfO?BaTvy*>s?!V2R%p)bbzx0u}l&Q zMMw)K*hxhpF&GE@6Oif&(8w@m>UP7&|}%!}#ARDF|$Z}TJbxo>Ns_5+WfDnSk z#bt2|{0|55 z{KZrF2mA46dK$W3fv#yFgw%R_dk;+jNs^FCr4B)XUq3ex{t$-13-H|YAQD+XIQ$Xa z-CmrYog8*_Najye6a|`Ao+*}!QCg=sI!a>h{TwRB6Mxw4h@6~L1A)M*AP8oFVHjXp7Mq)!6P~*gthNRm zCzCimNy6@M!0#VGVk83-Ha9V~>pI--HcCk8 nT`)MZX0zG;Ya?^pI{zC00DU<`2^Fi$00000NkvXXu0mjfgd$G7 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_icon_200.png b/lib/ts3phpframework/images/viewer/group_icon_200.png new file mode 100644 index 0000000000000000000000000000000000000000..4acadb5e25e656331049d350fde82d0a708b19f1 GIT binary patch literal 781 zcmV+o1M>WdP)PK^VrL?I&zY7gIx6d<%}T`P%%6nr5666E7+4bjWfR zsg#JKdI6Kkgp8bl%Tb^eh$@K7ivm)Yf{isRC>ex&4`T2L3Ys9Yvm%zQgzU^||Zx1qAIU)!mE-s2t zRTc4*6S!R-6pJbhhF?Y#0piga&qs6wsgy8zdKyPsyCX5t-_?asD1?oTO|-YQY3o05 zmOO*eNMUbpXHrMtYxRX=v6$-du*P68h)0hf;auwiq3ZJA!p!sxva(FPQCnJC!a4$$ zWubWxhDQ74^BTrqjKfCTP=CE1qt8b%HT4ROjZHZIb`;jK0v!QKmn>(7gp?306-zic zh+yQ|2y#jRMv4OCsD|A^BfTpPk-a(snx@g+-LrUju%E89X&bdRRyi0j%oQkwb2u0V z9M7fwe*YrLhfaV*)@U?heSN*Z{u&Fjr2>bM2#${;uxgR+=xE2*MyQ{3BA?IeuRw~) z*W25(e9E0XX>M+qV5+MqRcou;?J7x9>ZwM!Y_VAW{u=*v{1jjSH=;nUG5D7B00000 LNkvXXu0mjft88R@ literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_icon_300.png b/lib/ts3phpframework/images/viewer/group_icon_300.png new file mode 100644 index 0000000000000000000000000000000000000000..d048e3ef49b75531b02a3a8bbefa000b422057f9 GIT binary patch literal 820 zcmV-41Izr0P)z@;o?%Qu_W%F@AY({UO#lFTB>(_`g8%^e{{R4h=l}px z2mk>USO5SzmjD14Z`WEMkN^Mzs!2paRCwB?lg&@l02s!f(yrY)&5R>k30X1G;cmnM z2?-kqgcylL{D{iYa2Dp33jspBXcQ8S7o!nOjD$ZxE<{2i@}+^m#xTo1*au}R-Dn4N z>*_@gq(|PQoF44ua!2Oig`xNKvLtl;pl098GKrfCQUL#VBF;a-mu$>eU;($cd1 zw6E$^h+Q~LPjVS6<$?K zsHx$Q&lm9S?R!*_6dv5a3!BY`jrC1991aAxHU)!&#>S@2L?QuA)3CC#1iSq*`UB74 z^ZW31dIrB1m!NAE2*Mujba!tW0J5S)a{D=yN@d9M30}T>gH$So=4LO(Mu*_>c#zoL z1!JNi2>THO;NT!Pk>1N7o85=SLc?rkF#T-?1N{LkE-s_9vmKIj0D=UzLt7IDfcJ(s zoJ{U&f{=sTeGPqmPf=ZMhttWy?RFz02>(x-*BTlc!Uh1(^N7b|VG~8*)5l4KLP7NQ zK0)AlKeD+ThKGj1^H&gyMZ+!^Z~R126rm_`pdicNOQjOVMqi+!S5PXcP?RE|6L2`H z5s8EXqA0@PfMr?ueE!+LJKF^-djXnO0Y%avNg7&3hm+%w*-IB%TYa-utJMflRTU&j zVs&-3hp(%JiDnRs$B;`8l0000%VDkn literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_icon_400.png b/lib/ts3phpframework/images/viewer/group_icon_400.png new file mode 100644 index 0000000000000000000000000000000000000000..8a44f0e2e943ee7ee64cb84953133679b369f97b GIT binary patch literal 1160 zcmV;31b6$1P)KLZ*U+=)p!fv7f#TG` zAxLl%!EgG`&*5<32cu%worY0{L9A7~=}6b}76Isk~1IN~P)K3@?4 z&zpALY4A7Z!>t0&I7qECf*j`WHIYAjW_h^ivJu4lvb8y9VL`DD`rG0ZKvcZ$L@8 zo)6*!Fng}&gE0q~LGCMnsiR8`P)pL0I_sTkS+y)n+TA3GuzwhmB zzGa-Xev)(e9 zOha0&w!5{pwQ_NB!FPCgWD5p2r>CZ-c6N4lFgZC103c2bj7H;fL&KGt#>U19olalW z+1WuH{C+=3 zl0-6@gkGnI+1#t(IQC*Ry5B5Yfo0jWAjC^No)xexi{abD5YuT;6a|$^g*C4iKkasC zsx@F3M&5xS2#KSkgOa_yU5t*7!sT+o<5>kw)5v5pxc}fD{zdn(`ezL!NuB~u6T>hZ z%knT73?K*s4#y&znwp`l)q)^M_3MB{}%K^2uS~th>g{`e^l$V#GzM&rT^LBJ~T|-O|P_Nfxdpisv zCSd-tNDLLomSilU%at1&n@z}sxLtIaKj&&D=mb2gJDlqxD03jO`k)6;!NK`PE15^>&l a{yzZpPdrNp87Hv-0000Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS& z3ONX$GQ}GJ00N^)L_t(I%cYY~NEBfh#-DFTXZDY>ZvI7d6G==2$wOTP4WqzL3V}o` zCMe=1y$P}?Y?nQB$qsqx;6d09VPKcmPzW@FQgktK-4tm>8{OGlW}QFZeDih5gU!+R z^j_W{&%^sXgmaFoOu@x6F)@Lvsw%8ZPCoJw^4RNgJ?swz+%|yMnM}UO<$8B++iz}7 zO?_4r1(8Su06;DjjFy$j^`cnT>hX}q{(cm7c0vdQz$pbiJcJsLV{2gnS;p)vW0~P} zy5f?8Qb8ykUR)$ZlE6+*V97FURRw1ZYCH~iOAA`->!ELMx_&Gzl{zdOP->c~l$&!7 zy1x&9Z!e7PZ4gO9ML3LKeSIJv4|qP0lxdm|{@uAQ06T^uvuqaD@iC<4X3(P}d>ItJK#$vcG zNobTL6t%a**xd!?9EX&$!qAhX;|sxH5Z^T|d0Ud2K1Cw9#~7+-W>9TeF!%Nl35RjV z>%~`1OV-u~ok>&_1*euZW}TjX)NLCt!eQ6|blXOTF>uatLlm)+&ySTT3LF-^UN0(! zhvRFqoC^p7DCZ~uP)G>5Vy+Q>-U=kqxMrfEVD1iU>v>uxG9hldcXYZ}%y z4Mi>&dIJHx&*i!~=g@WCX+aPK2!ep%z|-aQ!iQ(~i;G{@h$1m<8#mo<{+rU#nt_33 hQ4}w=kzBRTe*v&CLC-*FK^Oo4002ovPDHLkV1fZuQknn& literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_icon_600.png b/lib/ts3phpframework/images/viewer/group_icon_600.png new file mode 100644 index 0000000000000000000000000000000000000000..d625f2ae9a51c479565b1ae9b69e9356bf8b016b GIT binary patch literal 757 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igJ* z6eTpB4_40r00MPML_t(I%dL@1PZMzz#m{tRifsc8_5p1|tBDl}#D!ABLN-8%5iyXs z@l(h*A+R^7_*|5gnn*%~rUaCf2oY?XQmMnEowhTbVIG}-X~$I!QeEj;-FtG+FDJRc zKZ-hUgu}NUFbw<9YGv;Z3=CW_O%u`RirUiB5+Me8ytcj;{Ucy_c!+noyzZ{905vi) zgsa`%u-j}Pgg};LB$7$QVlk+iTGw^G^y2xv7XX+Bz;1WCW@e@-hG8L!WmE}4g#u1b zPf;uu(9z+=wr`nTO3z6o&iM*j}S zagb#hnx?_$>%>tm2SF4tICvXJhes%vCFJ?Md2f7trwO2FYNk{YAP6G1wzlDRyOGUi zp`0k_>+QwMSFb?`0mHCVfiGm50HRo$Jw85!Ae4~FWMLQv{60T|!65WX1)K3rNU{Wm zu~-s`-PuO&*B4e}tErxAJvF~SK^=;`07U@|&7#%mu*eB%>TKq>2C>-c;$Au_+Z=Xing)x722C@l)l9UuwIR>v<>lq& z#lHdoARdoTTnYw2Q8e=TJPHLKG{Ybq?#I{dFB9kQ>GMV|m)q~V-Y0lG7w^~|4$9;4 nA{4q}R;$LNNF?$G|64x+lSwgoDU!|z00000NkvXXu0mjfQ+r5d literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/group_server.png b/lib/ts3phpframework/images/viewer/group_server.png new file mode 100644 index 0000000000000000000000000000000000000000..ee155d63900fa803096cbef1b676fcfdb1e2ba48 GIT binary patch literal 1301 zcmV+w1?u{VP)KLZ*U+=)p!fv7f#TG` zAxLl%!EgG`&*5<32cu%worY0{L9A7~=}6b}76Isk~1IN~P)K3@?4 z&zpALY4A7Z!>t0&I7qECf*j`WHIYAjW_h^ivJu4lvb8y9VL`DD`rG0ZKvcZ$L@8 zo)6*!Fng}&gE0q~LGCMnsiR8`P)pL0I_sTkS+y)n+TA3Gs z;m-QHx4OD^)pgx>Pn>wN?0FuZ=W!V0!o`dF)zhab6bb}EKomv9aZIz>WO{mvih&IsnH91~5&5X_`z- zJS}EtXZv0E!Q|%V=Eu2Qj!Y(l_0-rHXXfT`_x5OpA&HbkQN%$IkftedED^$_T&|GI z<*;pA00i#v1s)YxVc{Gc`4Z z@B0KnK(SaP3`2q-KuSrHD3nszc7{04AY}}IQi?QHSh18e8V#z|DpE?a*({FZVB0pa zlt?Kt3@G3r? zot@NbH8DRwzqP%+eRpML|Y;0^&^{c2Pq2^a`TY_q1pN;!28#_ft(lXCK zJC2M(-g)nR^~I%2)0Rq8>h(I~;}hg^?ezBb63Li~U&E~)aAsq@mES07-_74!I$-v+sXie3xA%sb-6F^V0QIk>ZQjU;YZ z!mWdJZYFEKSQFg7@+rC9&~ z1As|HK~y-6b(4Ke6L%cOzxTWA>(T4AR-nLSOcSvoTQ;ID{y~7unkCCjoMvn+ zZko(x0XFu>$if0~$%dPW3F-xErkP7K5ovT?l(NzxeM6{@LM*hj^uTfN z_Wb!I&nMq+@;n%9(AVD?Dki7xO){zIkVqsV!;wgy2{yk_N=y(&EYN0`*$zYKHu;@R>-o?RA{UsTwvbV_x3VLmwr-KQTf}|!JZ2M0C*Ab z!k)dKhr|}M7EENn*S8b6sz%MVP_~3uc0Wa^}}O_mFZaEqbbLOFL& zgVj*@)Pmi33n4@y)8hDno8n>c$lVZqF$8fwxGQb(Su$*DOhIV`xfD~@*H!^S7!(Qu zuq+2WU)XU-2VzpLm_`#r6U;Q@0fq27)T0By1!AsQu*Ni+O-6wzX)|2Dc1bQlDViby zCooWyNP*+n{FPbYX|*bd7Oj8S;@{?4Z|jF|-0QwGB&;M_0RYN2tAZ3o(fy~-Vg!y| zL4*XhXw^W|N(cpmE&$;99J`eO0El`W#;6yJ8ykjjygJ~U|LgWBQwW4pO)@b+-S%o& z3Hageq~#ky5Rm@J7Y)Rd)hsHNN;j@26Fyu@*H9u{Q(3M8mSth=y0Q1nnbTus+Zwu} z(Z~w=ukf8I?-njwXdy7%r{1L8r7cx~XKprWU*U7dYIDjS7_}oiFjA6uKpZ++s?6m#g2TA}SHyVN< zl=KZ4dfxk>!&9SsO<(jMkE!n`rgYi2(kKy$<8`_^-{E)mMa@(9RexJ;b%{h`Pdpai lG4EaQn2hK903ck<{R?$-n^aiy?%@Cc002ovPDHLkV1ljZ^wR(U literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/server_full.png b/lib/ts3phpframework/images/viewer/server_full.png new file mode 100644 index 0000000000000000000000000000000000000000..bfec600d6c90a3f5939c1d78cc42818b99606065 GIT binary patch literal 774 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 Uqmv#V11e?kboFyt=akR{0M6YbivR!s literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/tree_end.gif b/lib/ts3phpframework/images/viewer/tree_end.gif new file mode 100644 index 0000000000000000000000000000000000000000..b89d56e122324da9072a7061dcf21fe2370e4abf GIT binary patch literal 61 zcmZ?wbhEHb6krfwXkcWR(o_Ba|9{1wER0+Xj0`#qKmd|qU=r!+U&;1*ztkN6OWm{h Oq<{0bJ$fO+U=08Ygc7g- literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/tree_line.gif b/lib/ts3phpframework/images/viewer/tree_line.gif new file mode 100644 index 0000000000000000000000000000000000000000..324204a9843c6101c09e88fa568aebb2b9826113 GIT binary patch literal 63 zcmZ?wbhEHb6krfwXkcWR(o_Ba|9{1wER0+Xj0`#qKmd|qU=r);U&;1*ztkN6OWiuh QsxH5COnA}b$jD#~06MP}1poj5 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/images/viewer/tree_mid.gif b/lib/ts3phpframework/images/viewer/tree_mid.gif new file mode 100644 index 0000000000000000000000000000000000000000..2c8de45ad09863a82478ff340b0d97e2e43cf18a GIT binary patch literal 64 zcmZ?wbhEHb6krfwXkcWR(o_Ba|9{1wER0+Xj0`#qKmd|qU=r`?U&;1*ztkN6OWm{h Rq^)1aCRp^QJQZQE1^__P6G{L8 literal 0 HcmV?d00001 diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Abstract.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Abstract.php new file mode 100644 index 0000000..3edb227 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Abstract.php @@ -0,0 +1,160 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_Abstract + * @brief Provides low-level methods for concrete adapters to communicate with a TeamSpeak 3 Server. + */ +abstract class TeamSpeak3_Adapter_Abstract +{ + /** + * Stores user-provided options. + * + * @var array + */ + protected $options = null; + + /** + * Stores an TeamSpeak3_Transport_Abstract object. + * + * @var TeamSpeak3_Transport_Abstract + */ + protected $transport = null; + + /** + * The TeamSpeak3_Adapter_Abstract constructor. + * + * @param array $options + * @return TeamSpeak3_Adapter_Abstract + */ + public function __construct(array $options) + { + $this->options = $options; + + if($this->transport === null) + { + $this->syn(); + } + } + + /** + * The TeamSpeak3_Adapter_Abstract destructor. + * + * @return void + */ + abstract public function __destruct(); + + /** + * Connects the TeamSpeak3_Transport_Abstract object and performs initial actions on the remote + * server. + * + * @throws TeamSpeak3_Adapter_Exception + * @return void + */ + abstract protected function syn(); + + /** + * Commit pending data. + * + * @return array + */ + public function __sleep() + { + return array("options"); + } + + /** + * Reconnects to the remote server. + * + * @return void + */ + public function __wakeup() + { + $this->syn(); + } + + /** + * Returns the profiler timer used for this connection adapter. + * + * @return TeamSpeak3_Helper_Profiler_Timer + */ + public function getProfiler() + { + return TeamSpeak3_Helper_Profiler::get(spl_object_hash($this)); + } + + /** + * Returns the transport object used for this connection adapter. + * + * @return TeamSpeak3_Transport_Abstract + */ + public function getTransport() + { + return $this->transport; + } + + /** + * Loads the transport object object used for the connection adapter and passes a given set + * of options. + * + * @param array $options + * @param string $transport + * @throws TeamSpeak3_Adapter_Exception + * @return void + */ + protected function initTransport($options, $transport = "TeamSpeak3_Transport_TCP") + { + if(!is_array($options)) + { + throw new TeamSpeak3_Adapter_Exception("transport parameters must provided in an array"); + } + + $this->transport = new $transport($options); + } + + /** + * Returns the hostname or IPv4 address the underlying TeamSpeak3_Transport_Abstract object + * is connected to. + * + * @return string + */ + public function getTransportHost() + { + return $this->getTransport()->getConfig("host", "0.0.0.0"); + } + + /** + * Returns the port number of the server the underlying TeamSpeak3_Transport_Abstract object + * is connected to. + * + * @return string + */ + public function getTransportPort() + { + return $this->getTransport()->getConfig("port", "0"); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist.php new file mode 100644 index 0000000..c303ae4 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist.php @@ -0,0 +1,119 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_Blacklist + * @brief Provides methods to check if an IP address is currently blacklisted. + */ +class TeamSpeak3_Adapter_Blacklist extends TeamSpeak3_Adapter_Abstract +{ + /** + * The IPv4 address or FQDN of the TeamSpeak Systems update server. + * + * @var string + */ + protected $default_host = "blacklist.teamspeak.com"; + + /** + * The UDP port number of the TeamSpeak Systems update server. + * + * @var integer + */ + protected $default_port = 17385; + + /** + * Stores an array containing the latest build numbers. + * + * @var array + */ + protected $build_numbers = null; + + /** + * Connects the TeamSpeak3_Transport_Abstract object and performs initial actions on the remote + * server. + * + * @return void + */ + public function syn() + { + if(!isset($this->options["host"]) || empty($this->options["host"])) $this->options["host"] = $this->default_host; + if(!isset($this->options["port"]) || empty($this->options["port"])) $this->options["port"] = $this->default_port; + + $this->initTransport($this->options, "TeamSpeak3_Transport_UDP"); + $this->transport->setAdapter($this); + + TeamSpeak3_Helper_Profiler::init(spl_object_hash($this)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("blacklistConnected", $this); + } + + /** + * The TeamSpeak3_Adapter_Blacklist destructor. + * + * @return void + */ + public function __destruct() + { + if($this->getTransport() instanceof TeamSpeak3_Transport_Abstract && $this->getTransport()->isConnected()) + { + $this->getTransport()->disconnect(); + } + } + + /** + * Returns TRUE if a specified $host IP address is currently blacklisted. + * + * @param string $host + * @throws TeamSpeak3_Adapter_Blacklist_Exception + * @return boolean + */ + public function isBlacklisted($host) + { + if(ip2long($host) === FALSE) + { + $addr = gethostbyname($host); + + if($addr == $host) + { + throw new TeamSpeak3_Adapter_Blacklist_Exception("unable to resolve IPv4 address (" . $host . ")"); + } + + $host = $addr; + } + + $this->getTransport()->send("ip4:" . $host); + $repl = $this->getTransport()->read(1); + $this->getTransport()->disconnect(); + + if(!count($repl)) + { + return FALSE; + } + + return ($repl->toInt()) ? FALSE : TRUE; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist/Exception.php new file mode 100644 index 0000000..a9e52a7 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Blacklist/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_Blacklist_Exception + * @brief Enhanced exception class for TeamSpeak3_Adapter_Blacklist objects. + */ +class TeamSpeak3_Adapter_Blacklist_Exception extends TeamSpeak3_Adapter_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Exception.php new file mode 100644 index 0000000..342ef40 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_Exception + * @brief Enhanced exception class for TeamSpeak3_Adapter_Abstract objects. + */ +class TeamSpeak3_Adapter_Exception extends TeamSpeak3_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer.php new file mode 100644 index 0000000..0a78b96 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer.php @@ -0,0 +1,190 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_FileTransfer + * @brief Provides low-level methods for file transfer communication with a TeamSpeak 3 Server. + */ +class TeamSpeak3_Adapter_FileTransfer extends TeamSpeak3_Adapter_Abstract +{ + /** + * Connects the TeamSpeak3_Transport_Abstract object and performs initial actions on the remote + * server. + * + * @throws TeamSpeak3_Adapter_Exception + * @return void + */ + public function syn() + { + $this->initTransport($this->options); + $this->transport->setAdapter($this); + + TeamSpeak3_Helper_Profiler::init(spl_object_hash($this)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferConnected", $this); + } + + /** + * The TeamSpeak3_Adapter_FileTransfer destructor. + * + * @return void + */ + public function __destruct() + { + if($this->getTransport() instanceof TeamSpeak3_Transport_Abstract && $this->getTransport()->isConnected()) + { + $this->getTransport()->disconnect(); + } + } + + /** + * Sends a valid file transfer key to the server to initialize the file transfer. + * + * @param string $ftkey + * @throws TeamSpeak3_Adapter_FileTransfer_Exception + * @return void + */ + protected function init($ftkey) + { + if(strlen($ftkey) != 32) + { + throw new TeamSpeak3_Adapter_FileTransfer_Exception("invalid file transfer key format"); + } + + $this->getProfiler()->start(); + $this->getTransport()->send($ftkey); + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferHandshake", $this); + } + + /** + * Sends the content of a file to the server. + * + * @param string $ftkey + * @param integer $seek + * @param string $data + * @throws TeamSpeak3_Adapter_FileTransfer_Exception + * @return void + */ + public function upload($ftkey, $seek, $data) + { + $this->init($ftkey); + + $size = strlen($data); + $seek = intval($seek); + $pack = 4096; + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferUploadStarted", $ftkey, $seek, $size); + + for(;$seek < $size;) + { + $rest = $size-$seek; + $pack = $rest < $pack ? $rest : $pack; + $buff = substr($data, $seek, $pack); + $seek = $seek+$pack; + + $this->getTransport()->send($buff); + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferUploadProgress", $ftkey, $seek, $size); + } + + $this->getProfiler()->stop(); + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferUploadFinished", $ftkey, $seek, $size); + + if($seek < $size) + { + throw new TeamSpeak3_Adapter_FileTransfer_Exception("incomplete file upload (" . $seek . " of " . $size . " bytes)"); + } + } + + /** + * Returns the content of a downloaded file as a TeamSpeak3_Helper_String object. + * + * @param string $ftkey + * @param integer $size + * @param boolean $passthru + * @throws TeamSpeak3_Adapter_FileTransfer_Exception + * @return TeamSpeak3_Helper_String + */ + public function download($ftkey, $size, $passthru = FALSE) + { + $this->init($ftkey); + + if($passthru) + { + return $this->passthru($size); + } + + $buff = new TeamSpeak3_Helper_String(""); + $size = intval($size); + $pack = 4096; + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferDownloadStarted", $ftkey, count($buff), $size); + + for($seek = 0;$seek < $size;) + { + $rest = $size-$seek; + $pack = $rest < $pack ? $rest : $pack; + $data = $this->getTransport()->read($rest < $pack ? $rest : $pack); + $seek = $seek+$pack; + + $buff->append($data); + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferDownloadProgress", $ftkey, count($buff), $size); + } + + $this->getProfiler()->stop(); + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferDownloadFinished", $ftkey, count($buff), $size); + + if(strlen($buff) != $size) + { + throw new TeamSpeak3_Adapter_FileTransfer_Exception("incomplete file download (" . count($buff) . " of " . $size . " bytes)"); + } + + return $buff; + } + + /** + * Outputs all remaining data on a TeamSpeak 3 file transfer stream using PHP's fpassthru() + * function. + * + * @param integer $size + * @throws TeamSpeak3_Adapter_FileTransfer_Exception + * @return void + */ + protected function passthru($size) + { + $buff_size = fpassthru($this->getTransport()->getStream()); + + if($buff_size != $size) + { + throw new TeamSpeak3_Adapter_FileTransfer_Exception("incomplete file download (" . intval($buff_size) . " of " . $size . " bytes)"); + } + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer/Exception.php new file mode 100644 index 0000000..add2be5 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/FileTransfer/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_FileTransfer_Exception + * @brief Enhanced exception class for TeamSpeak3_Adapter_FileTransfer objects. + */ +class TeamSpeak3_Adapter_FileTransfer_Exception extends TeamSpeak3_Adapter_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery.php new file mode 100644 index 0000000..38f7db8 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery.php @@ -0,0 +1,261 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_ServerQuery + * @brief Provides low-level methods for ServerQuery communication with a TeamSpeak 3 Server. + */ +class TeamSpeak3_Adapter_ServerQuery extends TeamSpeak3_Adapter_Abstract +{ + /** + * Stores a singleton instance of the active TeamSpeak3_Node_Host object. + * + * @var TeamSpeak3_Node_Host + */ + protected $host = null; + + /** + * Stores the timestamp of the last command. + * + * @var integer + */ + protected $timer = null; + + /** + * Number of queries executed on the server. + * + * @var integer + */ + protected $count = 0; + + /** + * Stores an array with unsupported commands. + * + * @var array + */ + protected $block = array("help"); + + /** + * Connects the TeamSpeak3_Transport_Abstract object and performs initial actions on the remote + * server. + * + * @throws TeamSpeak3_Adapter_Exception + * @return void + */ + protected function syn() + { + $this->initTransport($this->options); + $this->transport->setAdapter($this); + + TeamSpeak3_Helper_Profiler::init(spl_object_hash($this)); + + if(!$this->getTransport()->readLine()->startsWith(TeamSpeak3::READY)) + { + throw new TeamSpeak3_Adapter_Exception("invalid reply from the server"); + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("serverqueryConnected", $this); + } + + /** + * The TeamSpeak3_Adapter_ServerQuery destructor. + * + * @return void + */ + public function __destruct() + { + if($this->getTransport() instanceof TeamSpeak3_Transport_Abstract && $this->transport->isConnected()) + { + try + { + $this->request("quit"); + } + catch(Exception $e) + { + return; + } + } + } + + /** + * Sends a prepared command to the server and returns the result. + * + * @param string $cmd + * @param boolean $throw + * @throws TeamSpeak3_Adapter_Exception + * @return TeamSpeak3_Adapter_ServerQuery_Reply + */ + public function request($cmd, $throw = TRUE) + { + $query = TeamSpeak3_Helper_String::factory($cmd)->section(TeamSpeak3::SEPARATOR_CELL); + + if(strstr($cmd, "\r") || strstr($cmd, "\n")) + { + throw new TeamSpeak3_Adapter_Exception("illegal characters in command '" . $query . "'"); + } + elseif(in_array($query, $this->block)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("command not found", 0x100); + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("serverqueryCommandStarted", $cmd); + + $this->getProfiler()->start(); + $this->getTransport()->sendLine($cmd); + $this->timer = time(); + $this->count++; + + $rpl = array(); + + do { + $str = $this->getTransport()->readLine(); + $rpl[] = $str; + } while($str instanceof TeamSpeak3_Helper_String && $str->section(TeamSpeak3::SEPARATOR_CELL) != TeamSpeak3::ERROR); + + $this->getProfiler()->stop(); + + $reply = new TeamSpeak3_Adapter_ServerQuery_Reply($rpl, $cmd, $this->getHost(), $throw); + + TeamSpeak3_Helper_Signal::getInstance()->emit("serverqueryCommandFinished", $cmd, $reply); + + return $reply; + } + + /** + * Waits for the server to send a notification message and returns the result. + * + * @throws TeamSpeak3_Adapter_Exception + * @return TeamSpeak3_Adapter_ServerQuery_Event + */ + public function wait() + { + if($this->getTransport()->getConfig("blocking")) + { + throw new TeamSpeak3_Adapter_Exception("only available in non-blocking mode"); + } + + do { + $evt = $this->getTransport()->readLine(); + } while($evt instanceof TeamSpeak3_Helper_String && !$evt->section(TeamSpeak3::SEPARATOR_CELL)->startsWith(TeamSpeak3::EVENT)); + + return new TeamSpeak3_Adapter_ServerQuery_Event($evt, $this->getHost()); + } + + /** + * Uses given parameters and returns a prepared ServerQuery command. + * + * @param string $cmd + * @param array $params + * @return string + */ + public function prepare($cmd, array $params = array()) + { + $args = array(); + $cells = array(); + + foreach($params as $ident => $value) + { + $ident = is_numeric($ident) ? "" : strtolower($ident) . TeamSpeak3::SEPARATOR_PAIR; + + if(is_array($value)) + { + $value = array_values($value); + + for($i = 0; $i < count($value); $i++) + { + if($value[$i] === null) continue; + elseif($value[$i] === FALSE) $value[$i] = 0x00; + elseif($value[$i] === TRUE) $value[$i] = 0x01; + elseif($value[$i] instanceof TeamSpeak3_Node_Abstract) $value[$i] = $value[$i]->getId(); + + $cells[$i][] = $ident . TeamSpeak3_Helper_String::factory($value[$i])->escape()->toUtf8(); + } + } + else + { + if($value === null) continue; + elseif($value === FALSE) $value = 0x00; + elseif($value === TRUE) $value = 0x01; + elseif($value instanceof TeamSpeak3_Node_Abstract) $value = $value->getId(); + + $args[] = $ident . TeamSpeak3_Helper_String::factory($value)->escape()->toUtf8(); + } + } + + foreach(array_keys($cells) as $ident) $cells[$ident] = implode(TeamSpeak3::SEPARATOR_CELL, $cells[$ident]); + + if(count($args)) $cmd .= " " . implode(TeamSpeak3::SEPARATOR_CELL, $args); + if(count($cells)) $cmd .= " " . implode(TeamSpeak3::SEPARATOR_LIST, $cells); + + return trim($cmd); + } + + /** + * Returns the timestamp of the last command. + * + * @return integer + */ + public function getQueryLastTimestamp() + { + return $this->timer; + } + + /** + * Returns the number of queries executed on the server. + * + * @return integer + */ + public function getQueryCount() + { + return $this->count; + } + + /** + * Returns the total runtime of all queries. + * + * @return mixed + */ + public function getQueryRuntime() + { + return $this->getProfiler()->getRuntime(); + } + + /** + * Returns the TeamSpeak3_Node_Host object of the current connection. + * + * @return TeamSpeak3_Node_Host + */ + public function getHost() + { + if($this->host === null) + { + $this->host = new TeamSpeak3_Node_Host($this); + } + + return $this->host; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Event.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Event.php new file mode 100644 index 0000000..5c3c8a7 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Event.php @@ -0,0 +1,170 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_ServerQuery_Event + * @brief Provides methods to analyze and format a ServerQuery event. + */ +class TeamSpeak3_Adapter_ServerQuery_Event implements ArrayAccess +{ + /** + * Stores the event type. + * + * @var TeamSpeak3_Helper_String + */ + protected $type = null; + + /** + * Stores the event data. + * + * @var array + */ + protected $data = null; + + /** + * Stores the event data as an unparsed string. + * + * @var TeamSpeak3_Helper_String + */ + protected $mesg = null; + + /** + * Creates a new TeamSpeak3_Adapter_ServerQuery_Event object. + * + * @param TeamSpeak3_Helper_String $evt + * @param TeamSpeak3_Node_Host $con + * @throws TeamSpeak3_Adapter_Exception + * @return TeamSpeak3_Adapter_ServerQuery_Event + */ + public function __construct(TeamSpeak3_Helper_String $evt, TeamSpeak3_Node_Host $con = null) + { + if(!$evt->startsWith(TeamSpeak3::EVENT)) + { + throw new TeamSpeak3_Adapter_Exception("invalid notification event format"); + } + + list($type, $data) = $evt->split(TeamSpeak3::SEPARATOR_CELL, 2); + + if(empty($data)) + { + throw new TeamSpeak3_Adapter_Exception("invalid notification event data"); + } + + $fake = new TeamSpeak3_Helper_String(TeamSpeak3::ERROR . TeamSpeak3::SEPARATOR_CELL . "id" . TeamSpeak3::SEPARATOR_PAIR . 0 . TeamSpeak3::SEPARATOR_CELL . "msg" . TeamSpeak3::SEPARATOR_PAIR . "ok"); + $repl = new TeamSpeak3_Adapter_ServerQuery_Reply(array($data, $fake), $type); + + $this->type = $type->substr(strlen(TeamSpeak3::EVENT)); + $this->data = $repl->toList(); + $this->mesg = $data; + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyEvent", $this, $con); + TeamSpeak3_Helper_Signal::getInstance()->emit("notify" . ucfirst($this->type), $this, $con); + } + + /** + * Returns the event type string. + * + * @return TeamSpeak3_Helper_String + */ + public function getType() + { + return $this->type; + } + + /** + * Returns the event data array. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Returns the event data as an unparsed string. + * + * @return TeamSpeak3_Helper_String + */ + public function getMessage() + { + return $this->mesg; + } + + /** + * @ignore + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->data) ? TRUE : FALSE; + } + + /** + * @ignore + */ + public function offsetGet($offset) + { + if(!$this->offsetExists($offset)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid parameter", 0x602); + } + + return $this->data[$offset]; + } + + /** + * @ignore + */ + public function offsetSet($offset, $value) + { + throw new TeamSpeak3_Node_Exception("event '" . $this->getType() . "' is read only"); + } + + /** + * @ignore + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + /** + * @ignore + */ + public function __get($offset) + { + return $this->offsetGet($offset); + } + + /** + * @ignore + */ + public function __set($offset, $value) + { + $this->offsetSet($offset, $value); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Exception.php new file mode 100644 index 0000000..01129a3 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_ServerQuery_Exception + * @brief Enhanced exception class for TeamSpeak3_Adapter_ServerQuery objects. + */ +class TeamSpeak3_Adapter_ServerQuery_Exception extends TeamSpeak3_Adapter_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Reply.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Reply.php new file mode 100644 index 0000000..5bf6532 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/ServerQuery/Reply.php @@ -0,0 +1,346 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_ServerQuery_Reply + * @brief Provides methods to analyze and format a ServerQuery reply. + */ +class TeamSpeak3_Adapter_ServerQuery_Reply +{ + /** + * Stores the command used to get this reply. + * + * @var TeamSpeak3_Helper_String + */ + protected $cmd = null; + + /** + * Stores the servers reply (if available). + * + * @var TeamSpeak3_Helper_String + */ + protected $rpl = null; + + /** + * Stores connected TeamSpeak3_Node_Host object. + * + * @var TeamSpeak3_Node_Host + */ + protected $con = null; + + /** + * Stores an assoc array containing the error info for this reply. + * + * @var array + */ + protected $err = array(); + + /** + * Sotres an array of events that occured before or during this reply. + * + * @var array + */ + protected $evt = array(); + + /** + * Indicates whether exceptions should be thrown or not. + * + * @var boolean + */ + protected $exp = TRUE; + + /** + * Creates a new TeamSpeak3_Adapter_ServerQuery_Reply object. + * + * @param array $rpl + * @param string $cmd + * @param boolean $exp + * @param TeamSpeak3_Node_Host $con + * @return TeamSpeak3_Adapter_ServerQuery_Reply + */ + public function __construct(array $rpl, $cmd = null, TeamSpeak3_Node_Host $con = null, $exp = TRUE) + { + $this->cmd = new TeamSpeak3_Helper_String($cmd); + $this->con = $con; + $this->exp = (bool) $exp; + + $this->fetchError(array_pop($rpl)); + $this->fetchReply($rpl); + } + + /** + * Returns the reply as an TeamSpeak3_Helper_String object. + * + * @return TeamSpeak3_Helper_String + */ + public function toString() + { + return (!func_num_args()) ? $this->rpl->unescape() : $this->rpl; + } + + /** + * Returns the reply as a standard PHP array where each element represents one item. + * + * @return array + */ + public function toLines() + { + if(!count($this->rpl)) return array(); + + $list = $this->toString(0)->split(TeamSpeak3::SEPARATOR_LIST); + + if(!func_num_args()) + { + for($i = 0; $i < count($list); $i++) $list[$i]->unescape(); + } + + return $list; + } + + /** + * Returns the reply as a standard PHP array where each element represents one item in table format. + * + * @return array + */ + public function toTable() + { + $table = array(); + + foreach($this->toLines(0) as $cells) + { + $pairs = $cells->split(TeamSpeak3::SEPARATOR_CELL); + + if(!func_num_args()) + { + for($i = 0; $i < count($pairs); $i++) $pairs[$i]->unescape(); + } + + $table[] = $pairs; + } + + return $table; + } + + /** + * Returns a multi-dimensional array containing the reply splitted in multiple rows and columns. + * + * @return array + */ + public function toArray() + { + $array = array(); + $table = $this->toTable(1); + + for($i = 0; $i < count($table); $i++) + { + foreach($table[$i] as $pair) + { + if(!count($pair)) + { + continue; + } + + if(!$pair->contains(TeamSpeak3::SEPARATOR_PAIR)) + { + $array[$i][$pair->toString()] = null; + } + else + { + list($ident, $value) = $pair->split(TeamSpeak3::SEPARATOR_PAIR, 2); + + $array[$i][$ident->toString()] = $value->isInt() ? $value->toInt() : (!func_num_args() ? $value->unescape() : $value); + } + } + } + + return $array; + } + + /** + * Returns a multi-dimensional assoc array containing the reply splitted in multiple rows and columns. + * The identifier specified by key will be used while indexing the array. + * + * @param $key + * @return array + */ + public function toAssocArray($ident) + { + $nodes = (func_num_args() > 1) ? $this->toArray(1) : $this->toArray(); + $array = array(); + + foreach($nodes as $node) + { + if(isset($node[$ident])) + { + $array[(is_object($node[$ident])) ? $node[$ident]->toString() : $node[$ident]] = $node; + } + else + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid parameter", 0x602); + } + } + + return $array; + } + + /** + * Returns an array containing the reply splitted in multiple rows and columns. + * + * @return array + */ + public function toList() + { + $array = func_num_args() ? $this->toArray(1) : $this->toArray(); + + if(count($array) == 1) + { + return array_shift($array); + } + + return $array; + } + + /** + * Returns an array containing stdClass objects. + * + * @return ArrayObject + */ + public function toObjectArray() + { + $array = (func_num_args() > 1) ? $this->toArray(1) : $this->toArray(); + + for($i = 0; $i < count($array); $i++) + { + $array[$i] = (object) $array[$i]; + } + + return $array; + } + + /** + * Returns the command used to get this reply. + * + * @return TeamSpeak3_Helper_String + */ + public function getCommandString() + { + return new TeamSpeak3_Helper_String($this->cmd); + } + + /** + * Returns an array of events that occured before or during this reply. + * + * @return array + */ + public function getNotifyEvents() + { + return $this->evt; + } + + /** + * Returns the value for a specified error property. + * + * @param string $ident + * @param mixed $default + * @return mixed + */ + public function getErrorProperty($ident, $default = null) + { + return (array_key_exists($ident, $this->err)) ? $this->err[$ident] : $default; + } + + /** + * Parses a ServerQuery error and throws a TeamSpeak3_Adapter_ServerQuery_Exception object if + * there's an error. + * + * @param string $err + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return void + */ + protected function fetchError($err) + { + $cells = $err->section(TeamSpeak3::SEPARATOR_CELL, 1, 3); + + foreach($cells->split(TeamSpeak3::SEPARATOR_CELL) as $pair) + { + list($ident, $value) = $pair->split(TeamSpeak3::SEPARATOR_PAIR); + + $this->err[$ident->toString()] = $value->isInt() ? $value->toInt() : $value->unescape(); + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyError", $this); + + if($this->getErrorProperty("id", 0x00) != 0x00 && $this->exp) + { + if($permid = $this->getErrorProperty("failed_permid")) + { + if($permsid = key($this->con->request("permget permid=" . $permid, FALSE)->toAssocArray("permsid"))) + { + $suffix = " (failed on " . $permsid . ")"; + } + else + { + $suffix = " (failed on " . $this->cmd->section(TeamSpeak3::SEPARATOR_CELL) . " " . $permid . "/0x" . strtoupper(dechex($permid)) . ")"; + } + } + elseif($details = $this->getErrorProperty("extra_msg")) + { + $suffix = " (" . trim($details) . ")"; + } + else + { + $suffix = ""; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception($this->getErrorProperty("msg") . $suffix, $this->getErrorProperty("id")); + } + } + + /** + * Parses a ServerQuery reply and creates a TeamSpeak3_Helper_String object. + * + * @param string $rpl + * @return void + */ + protected function fetchReply($rpl) + { + foreach($rpl as $key => $val) + { + if($val->startsWith(TeamSpeak3::GREET)) + { + unset($rpl[$key]); + } + elseif($val->startsWith(TeamSpeak3::EVENT)) + { + $this->evt[] = new TeamSpeak3_Adapter_ServerQuery_Event($rpl[$key], $this->con); + unset($rpl[$key]); + } + } + + $this->rpl = new TeamSpeak3_Helper_String(implode(TeamSpeak3::SEPARATOR_LIST, $rpl)); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS.php new file mode 100644 index 0000000..aba617f --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS.php @@ -0,0 +1,95 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_TSDNS + * @brief Provides methods to query a TSDNS server. + */ +class TeamSpeak3_Adapter_TSDNS extends TeamSpeak3_Adapter_Abstract +{ + /** + * The TCP port number used by any TSDNS server. + * + * @var integer + */ + protected $default_port = 41144; + + /** + * Connects the TeamSpeak3_Transport_Abstract object and performs initial actions on the remote + * server. + * + * @throws TeamSpeak3_Adapter_Exception + * @return void + */ + public function syn() + { + if(!isset($this->options["port"]) || empty($this->options["port"])) $this->options["port"] = $this->default_port; + + $this->initTransport($this->options); + $this->transport->setAdapter($this); + + TeamSpeak3_Helper_Profiler::init(spl_object_hash($this)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("tsdnsConnected", $this); + } + + /** + * The TeamSpeak3_Adapter_FileTransfer destructor. + * + * @return void + */ + public function __destruct() + { + if($this->getTransport() instanceof TeamSpeak3_Transport_Abstract && $this->getTransport()->isConnected()) + { + $this->getTransport()->disconnect(); + } + } + + /** + * Queries the TSDNS server for a specified virtual hostname and returns the result. + * + * @param string $tsdns + * @throws TeamSpeak3_Adapter_TSDNS_Exception + * @return TeamSpeak3_Helper_String + */ + public function resolve($tsdns) + { + $this->getTransport()->sendLine($tsdns); + $repl = $this->getTransport()->readLine(); + $this->getTransport()->disconnect(); + + if($repl->section(":", 0)->toInt() == 404) + { + throw new TeamSpeak3_Adapter_TSDNS_Exception("unable to resolve TSDNS hostname (" . $tsdns . ")"); + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("tsdnsResolved", $tsdns, $repl); + + return $repl; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS/Exception.php new file mode 100644 index 0000000..1701479 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/TSDNS/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_TSDNS_Exception + * @brief Enhanced exception class for TeamSpeak3_Adapter_TSDNS objects. + */ +class TeamSpeak3_Adapter_TSDNS_Exception extends TeamSpeak3_Adapter_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update.php new file mode 100644 index 0000000..247ee12 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update.php @@ -0,0 +1,217 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_Update + * @brief Provides methods to query the latest TeamSpeak 3 build numbers from the master server. + */ +class TeamSpeak3_Adapter_Update extends TeamSpeak3_Adapter_Abstract +{ + /** + * The IPv4 address or FQDN of the TeamSpeak Systems update server. + * + * @var string + */ + protected $default_host = "update.teamspeak.com"; + + /** + * The UDP port number of the TeamSpeak Systems update server. + * + * @var integer + */ + protected $default_port = 17384; + + /** + * Stores an array containing the latest build numbers (integer timestamps). + * + * @var array + */ + protected $build_datetimes = null; + + /** + * Stores an array containing the latest version strings. + * + * @var array + */ + protected $version_strings = null; + + /** + * Connects the TeamSpeak3_Transport_Abstract object and performs initial actions on the remote + * server. + * + * @throws TeamSpeak3_Adapter_Update_Exception + * @return void + */ + public function syn() + { + if(!isset($this->options["host"]) || empty($this->options["host"])) $this->options["host"] = $this->default_host; + if(!isset($this->options["port"]) || empty($this->options["port"])) $this->options["port"] = $this->default_port; + + $this->initTransport($this->options, "TeamSpeak3_Transport_UDP"); + $this->transport->setAdapter($this); + + TeamSpeak3_Helper_Profiler::init(spl_object_hash($this)); + + $this->getTransport()->send(TeamSpeak3_Helper_String::fromHex(33)); + + if(!preg_match_all("/,?(\d+)#([0-9a-zA-Z\._-]+),?/", $this->getTransport()->read(96), $matches) || !isset($matches[1]) || !isset($matches[2])) + { + throw new TeamSpeak3_Adapter_Update_Exception("invalid reply from the server"); + } + + $this->build_datetimes = $matches[1]; + $this->version_strings = $matches[2]; + + TeamSpeak3_Helper_Signal::getInstance()->emit("updateConnected", $this); + } + + /** + * The TeamSpeak3_Adapter_Update destructor. + * + * @return void + */ + public function __destruct() + { + if($this->getTransport() instanceof TeamSpeak3_Transport_Abstract && $this->getTransport()->isConnected()) + { + $this->getTransport()->disconnect(); + } + } + + /** + * Returns the current build number for a specified update channel. Note that since version + * 3.0.0, the build number represents an integer timestamp. $channel must be set to one of + * the following values: + * + * - stable + * - beta + * - alpha + * - server + * + * @param string $channel + * @throws TeamSpeak3_Adapter_Update_Exception + * @return integer + */ + public function getRev($channel = "stable") + { + switch($channel) + { + case "stable": + return isset($this->build_datetimes[0]) ? $this->build_datetimes[0] : null; + + case "beta": + return isset($this->build_datetimes[1]) ? $this->build_datetimes[1] : null; + + case "alpha": + return isset($this->build_datetimes[2]) ? $this->build_datetimes[2] : null; + + case "server": + return isset($this->build_datetimes[3]) ? $this->build_datetimes[3] : null; + + default: + throw new TeamSpeak3_Adapter_Update_Exception("invalid update channel identifier (" . $channel . ")"); + } + } + + /** + * Returns the current version string for a specified update channel. $channel must be set to + * one of the following values: + * + * - stable + * - beta + * - alpha + * - server + * + * @param string $channel + * @throws TeamSpeak3_Adapter_Update_Exception + * @return integer + */ + public function getVersion($channel = "stable") + { + switch($channel) + { + case "stable": + return isset($this->version_strings[0]) ? $this->version_strings[0] : null; + + case "beta": + return isset($this->version_strings[1]) ? $this->version_strings[1] : null; + + case "alpha": + return isset($this->version_strings[2]) ? $this->version_strings[2] : null; + + case "server": + return isset($this->version_strings[3]) ? $this->version_strings[3] : null; + + default: + throw new TeamSpeak3_Adapter_Update_Exception("invalid update channel identifier (" . $channel . ")"); + } + } + + /** + * Alias for getRev() using the 'stable' update channel. + * + * @param string $channel + * @return integer + */ + public function getClientRev() + { + return $this->getRev("stable"); + } + + /** + * Alias for getRev() using the 'server' update channel. + * + * @param string $channel + * @return integer + */ + public function getServerRev() + { + return $this->getRev("server"); + } + + /** + * Alias for getVersion() using the 'stable' update channel. + * + * @param string $channel + * @return integer + */ + public function getClientVersion() + { + return $this->getVersion("stable"); + } + + /** + * Alias for getVersion() using the 'server' update channel. + * + * @param string $channel + * @return integer + */ + public function getServerVersion() + { + return $this->getVersion("server"); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update/Exception.php new file mode 100644 index 0000000..a68a447 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Adapter/Update/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Adapter_Update_Exception + * @brief Enhanced exception class for TeamSpeak3_Adapter_Update objects. + */ +class TeamSpeak3_Adapter_Update_Exception extends TeamSpeak3_Adapter_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Exception.php new file mode 100644 index 0000000..8f4c5d3 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Exception.php @@ -0,0 +1,129 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Exception + * @brief Enhanced exception class for TeamSpeak3 objects. + */ +class TeamSpeak3_Exception extends Exception +{ + /** + * Stores custom error messages. + * + * @var array + */ + protected static $messages = array(); + + /** + * The TeamSpeak3_Exception constructor. + * + * @param string $mesg + * @param integer $code + * @return TeamSpeak3_Exception + */ + public function __construct($mesg, $code = 0x00) + { + parent::__construct($mesg, $code); + + if(array_key_exists((int) $code, self::$messages)) + { + $this->message = $this->prepareCustomMessage(self::$messages[intval($code)]); + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("errorException", $this); + } + + /** + * Prepares a custom error message by replacing pre-defined signs with given values. + * + * @param TeamSpeak3_Helper_String $mesg + * @return TeamSpeak3_Helper_String + */ + protected function prepareCustomMessage(TeamSpeak3_Helper_String $mesg) + { + $args = array( + "code" => $this->getCode(), + "mesg" => $this->getMessage(), + "line" => $this->getLine(), + "file" => $this->getFile(), + ); + + return $mesg->arg($args)->toString(); + } + + /** + * Registers a custom error message to $code. + * + * @param integer $code + * @param string $mesg + * @throws TeamSpeak3_Exception + * @return void + */ + public static function registerCustomMessage($code, $mesg) + { + if(array_key_exists((int) $code, self::$messages)) + { + throw new self("custom message for code 0x" . strtoupper(dechex($code)) . " is already registered"); + } + + if(!is_string($mesg)) + { + throw new self("custom message for code 0x" . strtoupper(dechex($code)) . " must be a string"); + } + + self::$messages[(int) $code] = new TeamSpeak3_Helper_String($mesg); + } + + /** + * Unregisters a custom error message from $code. + * + * @param integer $code + * @throws TeamSpeak3_Exception + * @return void + */ + public static function unregisterCustomMessage($code) + { + if(!array_key_exists((int) $code, self::$messages)) + { + throw new self("custom message for code 0x" . strtoupper(dechex($code)) . " is not registered"); + } + + unset(self::$messages[(int) $code]); + } + + /** + * Returns the class from which the exception was thrown. + * + * @return string + */ + public function getSender() + { + $trace = $this->getTrace(); + + return (isset($trace[0]["class"])) ? $trace[0]["class"] : "{main}"; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Char.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Char.php new file mode 100644 index 0000000..cf9e853 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Char.php @@ -0,0 +1,269 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Char + * @brief Helper class for char handling. + */ +class TeamSpeak3_Helper_Char +{ + /** + * Stores the original character. + * + * @var string + */ + protected $char = null; + + /** + * The TeamSpeak3_Helper_Char constructor. + * + * @param string $var + * @throws TeamSpeak3_Helper_Exception + * @return TeamSpeak3_Helper_Char + */ + public function __construct($char) + { + if(strlen($char) != 1) + { + throw new TeamSpeak3_Helper_Exception("char parameter may not contain more or less than one character"); + } + + $this->char = strval($char); + } + + /** + * Returns true if the character is a letter. + * + * @return boolean + */ + public function isLetter() + { + return ctype_alpha($this->char); + } + + /** + * Returns true if the character is a decimal digit. + * + * @return boolean + */ + public function isDigit() + { + return ctype_digit($this->char); + } + + /** + * Returns true if the character is a space. + * + * @return boolean + */ + public function isSpace() + { + return ctype_space($this->char); + } + + /** + * Returns true if the character is a mark. + * + * @return boolean + */ + public function isMark() + { + return ctype_punct($this->char); + } + + /** + * Returns true if the character is a control character (i.e. "\t"). + * + * @return boolean + */ + public function isControl() + { + return ctype_cntrl($this->char); + } + + /** + * Returns true if the character is a printable character. + * + * @return boolean + */ + public function isPrintable() + { + return ctype_print($this->char); + } + + /** + * Returns true if the character is the Unicode character 0x0000 ("\0"). + * + * @return boolean + */ + public function isNull() + { + return ($this->char === "\0") ? TRUE : FALSE; + } + + /** + * Returns true if the character is an uppercase letter. + * + * @return boolean + */ + public function isUpper() + { + return ($this->char === strtoupper($this->char)) ? TRUE : FALSE; + } + + /** + * Returns true if the character is a lowercase letter. + * + * @return boolean + */ + public function isLower() + { + return ($this->char === strtolower($this->char)) ? TRUE : FALSE; + } + + /** + * Returns the uppercase equivalent if the character is lowercase. + * + * @return TeamSpeak3_Helper_Char + */ + public function toUpper() + { + return ($this->isUpper()) ? $this : new self(strtoupper($this)); + } + + /** + * Returns the lowercase equivalent if the character is uppercase. + * + * @return TeamSpeak3_Helper_Char + */ + public function toLower() + { + return ($this->isLower()) ? $this : new self(strtolower($this)); + } + + /** + * Returns the ascii value of the character. + * + * @return integer + */ + public function toAscii() + { + return ord($this->char); + } + + /** + * Returns the Unicode value of the character. + * + * @return integer + */ + public function toUnicode() + { + $h = ord($this->char{0}); + + if($h <= 0x7F) + { + return $h; + } + else if($h < 0xC2) + { + return FALSE; + } + else if($h <= 0xDF) + { + return ($h & 0x1F) << 6 | (ord($this->char{1}) & 0x3F); + } + else if($h <= 0xEF) + { + return ($h & 0x0F) << 12 | (ord($this->char{1}) & 0x3F) << 6 | (ord($this->char{2}) & 0x3F); + } + else if($h <= 0xF4) + { + return ($h & 0x0F) << 18 | (ord($this->char{1}) & 0x3F) << 12 | (ord($this->char{2}) & 0x3F) << 6 | (ord($this->char{3}) & 0x3F); + } + else + { + return FALSE; + } + } + + /** + * Returns the hexadecimal value of the char. + * + * @return string + */ + public function toHex() + { + return strtoupper(dechex($this->toAscii())); + } + + /** + * Returns the TeamSpeak3_Helper_Char based on a given hex value. + * + * @param string $hex + * @throws TeamSpeak3_Helper_Exception + * @return TeamSpeak3_Helper_Char + */ + public static function fromHex($hex) + { + if(strlen($hex) != 2) + { + throw new TeamSpeak3_Helper_Exception("given parameter '" . $hex . "' is not a valid hexadecimal number"); + } + + return new self(chr(hexdec($hex))); + } + + /** + * Returns the character as a standard string. + * + * @return string + */ + public function toString() + { + return $this->char; + } + + /** + * Returns the integer value of the character. + * + * @return integer + */ + public function toInt() + { + return intval($this->char); + } + + /** + * Returns the character as a standard string. + * + * @return string + */ + public function __toString() + { + return $this->char; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Convert.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Convert.php new file mode 100644 index 0000000..6b8979f --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Convert.php @@ -0,0 +1,349 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Convert + * @brief Helper class for data conversion. + */ +class TeamSpeak3_Helper_Convert +{ + /** + * Converts bytes to a human readable value. + * + * @param integer $bytes + * @return string + */ + public static function bytes($bytes) + { + $kbytes = sprintf("%.02f", $bytes/1024); + $mbytes = sprintf("%.02f", $kbytes/1024); + $gbytes = sprintf("%.02f", $mbytes/1024); + $tbytes = sprintf("%.02f", $gbytes/1024); + + if($tbytes >= 1) + return $tbytes . " TB"; + if($gbytes >= 1) + return $gbytes . " GB"; + if($mbytes >= 1) + return $mbytes . " MB"; + if($kbytes >= 1) + return $kbytes . " KB"; + + return $bytes . " B"; + } + + /** + * Converts seconds/milliseconds to a human readable value. + * + * @param integer $seconds + * @param boolean $is_ms + * @param string $format + * @return string + */ + public static function seconds($seconds, $is_ms = FALSE, $format = "%dD %02d:%02d:%02d") + { + if($is_ms) $seconds = $seconds/1000; + + return sprintf($format, $seconds/60/60/24, ($seconds/60/60)%24, ($seconds/60)%60, $seconds%60); + } + + /** + * Converts a given codec ID to a human readable name. + * + * @param integer $codec + * @return string + */ + public static function codec($codec) + { + if($codec == TeamSpeak3::CODEC_SPEEX_NARROWBAND) + return "Speex Narrowband"; + if($codec == TeamSpeak3::CODEC_SPEEX_WIDEBAND) + return "Speex Wideband"; + if($codec == TeamSpeak3::CODEC_SPEEX_ULTRAWIDEBAND) + return "Speex Ultra-Wideband"; + if($codec == TeamSpeak3::CODEC_CELT_MONO) + return "CELT Mono"; + if($codec == TeamSpeak3::CODEC_OPUS_VOICE) + return "Opus Voice"; + if($codec == TeamSpeak3::CODEC_OPUS_MUSIC) + return "Opus Music"; + + return "Unknown"; + } + + /** + * Converts a given group type ID to a human readable name. + * + * @param integer $type + * @return string + */ + public static function groupType($type) + { + if($type == TeamSpeak3::GROUP_DBTYPE_TEMPLATE) + return "Template"; + if($type == TeamSpeak3::GROUP_DBTYPE_REGULAR) + return "Regular"; + if($type == TeamSpeak3::GROUP_DBTYPE_SERVERQUERY) + return "ServerQuery"; + + return "Unknown"; + } + + /** + * Converts a given permission type ID to a human readable name. + * + * @param integer $type + * @return string + */ + public static function permissionType($type) + { + if($type == TeamSpeak3::PERM_TYPE_SERVERGROUP) + return "Server Group"; + if($type == TeamSpeak3::PERM_TYPE_CLIENT) + return "Client"; + if($type == TeamSpeak3::PERM_TYPE_CHANNEL) + return "Channel"; + if($type == TeamSpeak3::PERM_TYPE_CHANNELGROUP) + return "Channel Group"; + if($type == TeamSpeak3::PERM_TYPE_CHANNELCLIENT) + return "Channel Client"; + + return "Unknown"; + } + + /** + * Converts a given permission category value to a human readable name. + * + * @param integer $pcat + * @return string + */ + public static function permissionCategory($pcat) + { + if($pcat == TeamSpeak3::PERM_CAT_GLOBAL) + return "Global"; + if($pcat == TeamSpeak3::PERM_CAT_GLOBAL_INFORMATION) + return "Global / Information"; + if($pcat == TeamSpeak3::PERM_CAT_GLOBAL_SERVER_MGMT) + return "Global / Virtual Server Management"; + if($pcat == TeamSpeak3::PERM_CAT_GLOBAL_ADM_ACTIONS) + return "Global / Administration"; + if($pcat == TeamSpeak3::PERM_CAT_GLOBAL_SETTINGS) + return "Global / Settings"; + if($pcat == TeamSpeak3::PERM_CAT_SERVER) + return "Virtual Server"; + if($pcat == TeamSpeak3::PERM_CAT_SERVER_INFORMATION) + return "Virtual Server / Information"; + if($pcat == TeamSpeak3::PERM_CAT_SERVER_ADM_ACTIONS) + return "Virtual Server / Administration"; + if($pcat == TeamSpeak3::PERM_CAT_SERVER_SETTINGS) + return "Virtual Server / Settings"; + if($pcat == TeamSpeak3::PERM_CAT_CHANNEL) + return "Channel"; + if($pcat == TeamSpeak3::PERM_CAT_CHANNEL_INFORMATION) + return "Channel / Information"; + if($pcat == TeamSpeak3::PERM_CAT_CHANNEL_CREATE) + return "Channel / Create"; + if($pcat == TeamSpeak3::PERM_CAT_CHANNEL_MODIFY) + return "Channel / Modify"; + if($pcat == TeamSpeak3::PERM_CAT_CHANNEL_DELETE) + return "Channel / Delete"; + if($pcat == TeamSpeak3::PERM_CAT_CHANNEL_ACCESS) + return "Channel / Access"; + if($pcat == TeamSpeak3::PERM_CAT_GROUP) + return "Group"; + if($pcat == TeamSpeak3::PERM_CAT_GROUP_INFORMATION) + return "Group / Information"; + if($pcat == TeamSpeak3::PERM_CAT_GROUP_CREATE) + return "Group / Create"; + if($pcat == TeamSpeak3::PERM_CAT_GROUP_MODIFY) + return "Group / Modify"; + if($pcat == TeamSpeak3::PERM_CAT_GROUP_DELETE) + return "Group / Delete"; + if($pcat == TeamSpeak3::PERM_CAT_CLIENT) + return "Client"; + if($pcat == TeamSpeak3::PERM_CAT_CLIENT_INFORMATION) + return "Client / Information"; + if($pcat == TeamSpeak3::PERM_CAT_CLIENT_ADM_ACTIONS) + return "Client / Admin"; + if($pcat == TeamSpeak3::PERM_CAT_CLIENT_BASICS) + return "Client / Basics"; + if($pcat == TeamSpeak3::PERM_CAT_CLIENT_MODIFY) + return "Client / Modify"; + if($pcat == TeamSpeak3::PERM_CAT_FILETRANSFER) + return "File Transfer"; + if($pcat == TeamSpeak3::PERM_CAT_NEEDED_MODIFY_POWER) + return "Grant"; + + return "Unknown"; + } + + /** + * Converts a given log level ID to a human readable name and vice versa. + * + * @param mixed $level + * @return string + */ + public static function logLevel($level) + { + if(is_numeric($level)) + { + if($level == TeamSpeak3::LOGLEVEL_CRITICAL) + return "CRITICAL"; + if($level == TeamSpeak3::LOGLEVEL_ERROR) + return "ERROR"; + if($level == TeamSpeak3::LOGLEVEL_DEBUG) + return "DEBUG"; + if($level == TeamSpeak3::LOGLEVEL_WARNING) + return "WARNING"; + if($level == TeamSpeak3::LOGLEVEL_INFO) + return "INFO"; + + return "DEVELOP"; + } + else + { + if(strtoupper($level) == "CRITICAL") + return TeamSpeak3::LOGLEVEL_CRITICAL; + if(strtoupper($level) == "ERROR") + return TeamSpeak3::LOGLEVEL_ERROR; + if(strtoupper($level) == "DEBUG") + return TeamSpeak3::LOGLEVEL_DEBUG; + if(strtoupper($level) == "WARNING") + return TeamSpeak3::LOGLEVEL_WARNING; + if(strtoupper($level) == "INFO") + return TeamSpeak3::LOGLEVEL_INFO; + + return TeamSpeak3::LOGLEVEL_DEVEL; + } + } + + /** + * Converts a specified log entry string into an array containing the data. + * + * @param string $entry + * @return array + */ + public static function logEntry($entry) + { + $parts = explode("|", $entry, 5); + $array = array(); + + if(count($parts) != 5) + { + $array["timestamp"] = 0; + $array["level"] = TeamSpeak3::LOGLEVEL_ERROR; + $array["channel"] = "ParamParser"; + $array["server_id"] = ""; + $array["msg"] = TeamSpeak3_Helper_String::factory("convert error (" . trim($entry) . ")"); + $array["msg_plain"] = $entry; + $array["malformed"] = TRUE; + } + else + { + $array["timestamp"] = strtotime(trim($parts[0])); + $array["level"] = self::logLevel(trim($parts[1])); + $array["channel"] = trim($parts[2]); + $array["server_id"] = trim($parts[3]); + $array["msg"] = TeamSpeak3_Helper_String::factory(trim($parts[4])); + $array["msg_plain"] = $entry; + $array["malformed"] = FALSE; + } + + return $array; + } + + /** + * Converts a given string to a ServerQuery password hash. + * + * @param string $plain + * @return string + */ + public static function password($plain) + { + return base64_encode(sha1($plain, TRUE)); + } + + /** + * Returns a client-like formatted version of the TeamSpeak 3 version string. + * + * @param string $version + * @param string $format + * @return string + */ + public static function version($version, $format = "Y-m-d h:i:s") + { + if(!$version instanceof TeamSpeak3_Helper_String) + { + $version = new TeamSpeak3_Helper_String($version); + } + + $buildno = $version->section("[", 1)->filterDigits()->toInt(); + + return ($buildno <= 15001) ? $version : $version->section("[")->append("(" . date($format, $buildno) . ")"); + } + + /** + * Returns a client-like short-formatted version of the TeamSpeak 3 version string. + * + * @param string $version + * @return string + */ + public static function versionShort($version) + { + if(!$version instanceof TeamSpeak3_Helper_String) + { + $version = new TeamSpeak3_Helper_String($version); + } + + return $version->section(" ", 0); + } + + /** + * Tries to detect the type of an image by a given string and returns it. + * + * @param string $binary + * @return string + */ + public static function imageMimeType($binary) + { + if(!preg_match('/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/', $binary, $matches)) + { + return "application/octet-stream"; + } + + $type = array( + 1 => "image/jpeg", + 2 => "image/gif", + 3 => "image/png", + 4 => "image/x-windows-bmp", + 5 => "image/tiff", + 6 => "image/x-ilbm", + ); + + return $type[count($matches)-1]; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Crypt.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Crypt.php new file mode 100644 index 0000000..dff571c --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Crypt.php @@ -0,0 +1,482 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Crypt + * @brief Helper class for data encryption. + */ +class TeamSpeak3_Helper_Crypt +{ + /** + * Stores the secret passphrase to encrypt or decrypt a given string. + * + * @var string + */ + protected $passphrase = null; + + /** + * Stores an array containing 18 32-bit entries. + * + * @var array + */ + protected $p = array(); + + /** + * Stores an array containing 4 sub-arrays with 256 32-bit entries. + * + * @var array + */ + protected $s = array(); + + /** + * The TeamSpeak3_Helper_Crypt constructor. + * + * @param string $secret + * @return TeamSpeak3_Helper_Crypt + */ + public function __construct($secret) + { + $this->setDefaultKeys(); + $this->setSecretKey($secret); + } + + /** + * Encrypts a given string. + * + * @param string $string + * @return string + */ + public function encrypt($string) + { + $string = trim($string); + $encryp = ""; + $length = strlen($string); + $string .= str_repeat(chr(0), (8-($length%8))%8); + + for($i = 0; $i < $length; $i += 8) + { + list(,$xl,$xr) = unpack("N2", substr($string, $i, 8)); + $this->encipher($xl, $xr); + $encryp .= pack("N2", $xl, $xr); + } + + return base64_encode($encryp); + } + + /** + * Decrypts a given string. + * + * @param string $string + * @return string + */ + public function decrypt($string) + { + $string = base64_decode($string); + $decryp = ""; + $length = strlen($string); + $string .= str_repeat(chr(0), (8-($length%8))%8); + + for($i = 0; $i < $length; $i += 8) + { + list(,$xl,$xr) = unpack("N2", substr($string, $i, 8)); + $this->decipher($xl, $xr); + $decryp .= pack("N2", $xl, $xr); + } + + return trim($decryp); + } + + /** + * Enciphers a single 64-bit block. + * + * @param integer $xl + * @param integer $xr + */ + protected function encipher(&$xl, &$xr) + { + for($i = 0; $i < 16; $i++) + { + $temp = $xl ^ $this->p[$i]; + $xl = ((($this->s[0][($temp>>24) & 255] + $this->s[1][($temp>>16) & 255]) ^ $this->s[2][($temp>>8) & 255]) + $this->s[3][$temp & 255]) ^ $xr; + $xr = $temp; + } + + $xr = $xl ^ $this->p[16]; + $xl = $temp ^ $this->p[17]; + } + + /** + * Deciphers a single 64-bit block + * + * @param integer $xl + * @param integer $xr + * @return void + */ + protected function decipher(&$xl, &$xr) + { + for($i = 17; $i > 1; $i--) + { + $temp = $xl ^ $this->p[$i]; + $xl = ((($this->s[0][($temp>>24) & 255] + $this->s[1][($temp>>16) & 255]) ^ $this->s[2][($temp>>8) & 255]) + $this->s[3][$temp & 255]) ^ $xr; + $xr = $temp; + } + + $xr = $xl ^ $this->p[1]; + $xl = $temp ^ $this->p[0]; + } + + /** + * Sets the secret key using the specified pasphrase. + * + * @param string $passphrase + * @throws Habitat_Exception + * @return void + */ + protected function setSecretKey($passphrase) + { + $length = strlen($passphrase); + + if(strlen($passphrase) < 1 || strlen($passphrase) > 56) + { + throw new TeamSpeak3_Helper_Exception("secret passphrase must contain at least one but less than 56 characters"); + } + + $k = 0; + $data = 0; + $datal = 0; + $datar = 0; + + for($i = 0; $i < 18; $i++) + { + $data = 0; + for($j = 4; $j > 0; $j--) + { + $data = $data << 8 | ord($passphrase{$k}); + $k = ($k+1) % $length; + } + $this->p[$i] ^= $data; + } + + + for($i = 0; $i <= 16; $i += 2) + { + $this->encipher($datal, $datar); + $this->p[$i] = $datal; + $this->p[$i+1] = $datar; + } + + foreach($this->s as $key => $val) + { + for ($i = 0; $i < 256; $i += 2) + { + $this->encipher($datal, $datar); + $this->s[$key][$i] = $datal; + $this->s[$key][$i+1] = $datar; + } + } + } + + /** + * Sets the defult p and s keys. + * + * @return void + */ + protected function setDefaultKeys() + { + $this->p = array( + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, + ); + $this->s = array( + array( + 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, + 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, + 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, + 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, + 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, + 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, + 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, + 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, + 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, + 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, + 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, + 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, + 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677, + 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, + 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, + 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, + 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, + 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, + 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, + 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, + 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88, + 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, + 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, + 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, + 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, + 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, + 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, + 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, + 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09, + 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, + 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, + 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, + 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, + 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, + 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, + 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, + 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, + 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, + 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, + 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, + 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, + 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, + 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, + 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, + 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, + 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, + 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, + 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, + 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, + 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, + 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, + 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, + 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41, + 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, + 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, + 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, + 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A + ), + array( + 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, + 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, + 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, + 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6, + 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, + 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, + 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, + 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, + 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, + 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7, + 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, + 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, + 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, + 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, + 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, + 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16, + 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, + 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, + 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, + 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, + 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, + 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960, + 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, + 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, + 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, + 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, + 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, + 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50, + 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, + 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, + 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, + 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, + 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, + 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0, + 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, + 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, + 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, + 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, + 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, + 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735, + 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, + 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, + 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 + ), + array( + 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, + 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, + 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, + 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45, + 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, + 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, + 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, + 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, + 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, + 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB, + 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, + 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, + 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, + 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, + 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, + 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B, + 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, + 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, + 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, + 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, + 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, + 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B, + 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, + 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, + 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, + 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, + 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, + 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D, + 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, + 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, + 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, + 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, + 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, + 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633, + 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, + 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, + 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, + 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, + 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, + 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24, + 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, + 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, + 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 + ), + array( + 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, + 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, + 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, + 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8, + 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, + 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, + 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, + 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, + 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, + 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51, + 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, + 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, + 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, + 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, + 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, + 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB, + 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, + 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, + 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, + 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, + 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, + 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47, + 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, + 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, + 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, + 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, + 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, + 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38, + 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, + 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, + 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, + 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, + 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, + 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D, + 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, + 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, + 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, + 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, + 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, + 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0, + 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, + 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, + 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 + ) + ); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Exception.php new file mode 100644 index 0000000..23796cd --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Exception + * @brief Enhanced exception class for TeamSpeak3_Helper_* objects. + */ +class TeamSpeak3_Helper_Exception extends TeamSpeak3_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler.php new file mode 100644 index 0000000..bf9d7f7 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler.php @@ -0,0 +1,101 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Profiler + * @brief Helper class for profiler handling. + */ +class TeamSpeak3_Helper_Profiler +{ + /** + * Stores various timers for code profiling. + * + * @var array + */ + protected static $timers = array(); + + /** + * Inits a timer. + * + * @param string $name + * @return void + */ + public static function init($name = "default") + { + self::$timers[$name] = new TeamSpeak3_Helper_Profiler_Timer($name); + } + + /** + * Starts a timer. + * + * @param string $name + * @return void + */ + public static function start($name = "default") + { + if(array_key_exists($name, self::$timers)) + { + self::$timers[$name]->start(); + } + else + { + self::$timers[$name] = new TeamSpeak3_Helper_Profiler_Timer($name); + } + } + + /** + * Stops a timer. + * + * @param string $name + * @return void + */ + public static function stop($name = "default") + { + if(!array_key_exists($name, self::$timers)) + { + self::init($name); + } + + self::$timers[$name]->stop(); + } + + /** + * Returns a timer. + * + * @param string $name + * @return TeamSpeak3_Helper_Profiler_Timer + */ + public static function get($name = "default") + { + if(!array_key_exists($name, self::$timers)) + { + self::init($name); + } + + return self::$timers[$name]; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Exception.php new file mode 100644 index 0000000..ba770bc --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Profiler_Exception + * @brief Enhanced exception class for TeamSpeak3_Helper_Profiler objects. + */ +class TeamSpeak3_Helper_Profiler_Exception extends TeamSpeak3_Helper_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Timer.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Timer.php new file mode 100644 index 0000000..3ee87cf --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Profiler/Timer.php @@ -0,0 +1,154 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Profiler_Timer + * @brief Helper class providing profiler timers. + */ +class TeamSpeak3_Helper_Profiler_Timer +{ + /** + * Indicates wether the timer is running or not. + * + * @var boolean + */ + protected $running = FALSE; + + /** + * Stores the timestamp when the timer was last started. + * + * @var integer + */ + protected $started = 0; + + /** + * Stores the timer name. + * + * @var string + */ + protected $name = null; + + /** + * Stores various information about the server environment. + * + * @var array + */ + protected $data = array(); + + /** + * The TeamSpeak3_Helper_Profiler_Timer constructor. + * + * @param string $name + * @return TeamSpeak3_Helper_Profiler_Timer + */ + public function __construct($name) + { + $this->name = (string) $name; + + $this->data["runtime"] = 0; + $this->data["realmem"] = 0; + $this->data["emalloc"] = 0; + + $this->start(); + } + + /** + * Starts the timer. + * + * @return void + */ + public function start() + { + if($this->isRunning()) return; + + $this->data["realmem_start"] = memory_get_usage(TRUE); + $this->data["emalloc_start"] = memory_get_usage(); + + $this->started = microtime(TRUE); + $this->running = TRUE; + } + + /** + * Stops the timer. + * + * @return void + */ + public function stop() + { + if(!$this->isRunning()) return; + + $this->data["runtime"] += microtime(TRUE) - $this->started; + $this->data["realmem"] += memory_get_usage(TRUE) - $this->data["realmem_start"]; + $this->data["emalloc"] += memory_get_usage() - $this->data["emalloc_start"]; + + $this->started = 0; + $this->running = FALSE; + } + + /** + * Return the timer runtime. + * + * @return mixed + */ + public function getRuntime() + { + if($this->isRunning()) + { + $this->stop(); + $this->start(); + } + + return $this->data["runtime"]; + } + + /** + * Returns the amount of memory allocated to PHP in bytes. + * + * @param boolean $realmem + * @return integer + */ + public function getMemUsage($realmem = FALSE) + { + if($this->isRunning()) + { + $this->stop(); + $this->start(); + } + + return ($realmem !== FALSE) ? $this->data["realmem"] : $this->data["emalloc"]; + } + + /** + * Returns TRUE if the timer is running. + * + * @return boolean + */ + public function isRunning() + { + return $this->running; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal.php new file mode 100644 index 0000000..40010c7 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal.php @@ -0,0 +1,213 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Signal + * @brief Helper class for signal slots. + */ +class TeamSpeak3_Helper_Signal +{ + /** + * Stores the TeamSpeak3_Helper_Signal object. + * + * @var TeamSpeak3_Helper_Signal + */ + protected static $instance = null; + + /** + * Stores subscribed signals and their slots. + * + * @var array + */ + protected $sigslots = array(); + + /** + * Emits a signal with a given set of parameters. + * + * @param string $signal + * @param mixed $params + * @return mixed + */ + public function emit($signal, $params = null) + { + if(!$this->hasHandlers($signal)) + { + return; + } + + if(!is_array($params)) + { + $params = func_get_args(); + $params = array_slice($params, 1); + } + + foreach($this->sigslots[$signal] as $slot) + { + $return = $slot->call($params); + } + + return $return; + } + + /** + * Generates a MD5 hash based on a given callback. + * + * @param mixed $callback + * @param string + * @return void + */ + public function getCallbackHash($callback) + { + if(!is_callable($callback, TRUE, $callable_name)) + { + throw new TeamSpeak3_Helper_Signal_Exception("invalid callback specified"); + } + + return md5($callable_name); + } + + /** + * Subscribes to a signal and returns the signal handler. + * + * @param string $signal + * @param mixed $callback + * @return TeamSpeak3_Helper_Signal_Handler + */ + public function subscribe($signal, $callback) + { + if(empty($this->sigslots[$signal])) + { + $this->sigslots[$signal] = array(); + } + + $index = $this->getCallbackHash($callback); + + if(!array_key_exists($index, $this->sigslots[$signal])) + { + $this->sigslots[$signal][$index] = new TeamSpeak3_Helper_Signal_Handler($signal, $callback); + } + + return $this->sigslots[$signal][$index]; + } + + /** + * Unsubscribes from a signal. + * + * @param string $signal + * @param mixed $callback + * @return void + */ + public function unsubscribe($signal, $callback = null) + { + if(!$this->hasHandlers($signal)) + { + return; + } + + if($callback !== null) + { + $index = $this->getCallbackHash($callback); + + if(!array_key_exists($index, $this->sigslots[$signal])) + { + return; + } + + unset($this->sigslots[$signal][$index]); + } + else + { + unset($this->sigslots[$signal]); + } + } + + /** + * Returns all registered signals. + * + * @return array + */ + public function getSignals() + { + return array_keys($this->sigslots); + } + + /** + * Returns TRUE there are slots subscribed for a specified signal. + * + * @param string $signal + * @return boolean + */ + public function hasHandlers($signal) + { + return empty($this->sigslots[$signal]) ? FALSE : TRUE; + } + + /** + * Returns all slots for a specified signal. + * + * @param string $signal + * @return array + */ + public function getHandlers($signal) + { + if(!$this->hasHandlers($signal)) + { + return $this->sigslots[$signal]; + } + + return array(); + } + + /** + * Clears all slots for a specified signal. + * + * @param string $signal + * @return void + */ + public function clearHandlers($signal) + { + if(!$this->hasHandlers($signal)) + { + unset($this->sigslots[$signal]); + } + } + + /** + * Returns a singleton instance of TeamSpeak3_Helper_Signal. + * + * @return TeamSpeak3_Helper_Signal + */ + public static function getInstance() + { + if(self::$instance === null) + { + self::$instance = new self(); + } + + return self::$instance; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Exception.php new file mode 100644 index 0000000..70c1e7f --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Signal_Exception + * @brief Enhanced exception class for TeamSpeak3_Helper_Signal objects. + */ +class TeamSpeak3_Helper_Signal_Exception extends TeamSpeak3_Helper_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Handler.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Handler.php new file mode 100644 index 0000000..0050b21 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Handler.php @@ -0,0 +1,78 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Signal_Handler + * @brief Helper class providing handler functions for signals. + */ +class TeamSpeak3_Helper_Signal_Handler +{ + /** + * Stores the name of the subscribed signal. + * + * @var string + */ + protected $signal = null; + + /** + * Stores the callback function for the subscribed signal. + * + * @var mixed + */ + protected $callback = null; + + /** + * The TeamSpeak3_Helper_Signal_Handler constructor. + * + * @param string $signal + * @param mixed $callback + * @throws TeamSpeak3_Helper_Signal_Exception + * @return TeamSpeak3_Helper_Signal_Handler + */ + public function __construct($signal, $callback) + { + $this->signal = (string) $signal; + + if(!is_callable($callback)) + { + throw new TeamSpeak3_Helper_Signal_Exception("invalid callback specified for signal '" . $signal . "'"); + } + + $this->callback = $callback; + } + + /** + * Invoke the signal handler. + * + * @param array $args + * @return mixed + */ + public function call(array $args = array()) + { + return call_user_func_array($this->callback, $args); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Interface.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Interface.php new file mode 100644 index 0000000..854b7d5 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Signal/Interface.php @@ -0,0 +1,353 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Signal_Interface + * @brief Interface class describing the layout for TeamSpeak3_Helper_Signal callbacks. + */ +interface TeamSpeak3_Helper_Signal_Interface +{ + /** + * Possible callback for 'Connected' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryConnected", array($object, "onConnect")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferConnected", array($object, "onConnect")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("blacklistConnected", array($object, "onConnect")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("updateConnected", array($object, "onConnect")); + * + * @param TeamSpeak3_Adapter_Abstract $adapter + * @return void + */ + public function onConnect(TeamSpeak3_Adapter_Abstract $adapter); + + /** + * Possible callback for 'Disconnected' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryDisconnected", array($object, "onDisconnect")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferDisconnected", array($object, "onDisconnect")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("blacklistDisconnected", array($object, "onDisconnect")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("updateDisconnected", array($object, "onDisconnect")); + * + * @return void + */ + public function onDisconnect(); + + /** + * Possible callback for 'serverqueryCommandStarted' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryCommandStarted", array($object, "onCommandStarted")); + * + * @param string $cmd + * @return void + */ + public function onCommandStarted($cmd); + + /** + * Possible callback for 'serverqueryCommandFinished' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryCommandFinished", array($object, "onCommandFinished")); + * + * @param string $cmd + * @param TeamSpeak3_Adapter_ServerQuery_Reply $reply + * @return void + */ + public function onCommandFinished($cmd, TeamSpeak3_Adapter_ServerQuery_Reply $reply); + + /** + * Possible callback for 'notifyEvent' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyEvent", array($object, "onEvent")); + * + * @param TeamSpeak3_Adapter_ServerQuery_Event $event + * @param TeamSpeak3_Node_Host $host + * @return void + */ + public function onEvent(TeamSpeak3_Adapter_ServerQuery_Event $event, TeamSpeak3_Node_Host $host); + + /** + * Possible callback for 'notifyError' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyError", array($object, "onError")); + * + * @param TeamSpeak3_Adapter_ServerQuery_Reply $reply + * @return void + */ + public function onError(TeamSpeak3_Adapter_ServerQuery_Reply $reply); + + /** + * Possible callback for 'notifyServerselected' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyServerselected", array($object, "onServerselected")); + * + * @param TeamSpeak3_Node_Host $host + * @return void + */ + public function onServerselected(TeamSpeak3_Node_Host $host); + + /** + * Possible callback for 'notifyServercreated' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyServercreated", array($object, "onServercreated")); + * + * @param TeamSpeak3_Node_Host $host + * @param integer $sid + * @return void + */ + public function onServercreated(TeamSpeak3_Node_Host $host, $sid); + + /** + * Possible callback for 'notifyServerdeleted' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyServerdeleted", array($object, "onServerdeleted")); + * + * @param TeamSpeak3_Node_Host $host + * @param integer $sid + * @return void + */ + public function onServerdeleted(TeamSpeak3_Node_Host $host, $sid); + + /** + * Possible callback for 'notifyServerstarted' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyServerstarted", array($object, "onServerstarted")); + * + * @param TeamSpeak3_Node_Host $host + * @param integer $sid + * @return void + */ + public function onServerstarted(TeamSpeak3_Node_Host $host, $sid); + + /** + * Possible callback for 'notifyServerstopped' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyServerstopped", array($object, "onServerstopped")); + * + * @param TeamSpeak3_Node_Host $host + * @param integer $sid + * @return void + */ + public function onServerstopped(TeamSpeak3_Node_Host $host, $sid); + + /** + * Possible callback for 'notifyServershutdown' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyServershutdown", array($object, "onServershutdown")); + * + * @param TeamSpeak3_Node_Host $host + * @return void + */ + public function onServershutdown(TeamSpeak3_Node_Host $host); + + /** + * Possible callback for 'notifyLogin' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyLogin", array($object, "onLogin")); + * + * @param TeamSpeak3_Node_Host $host + * @return void + */ + public function onLogin(TeamSpeak3_Node_Host $host); + + /** + * Possible callback for 'notifyLogout' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyLogout", array($object, "onLogout")); + * + * @param TeamSpeak3_Node_Host $host + * @return void + */ + public function onLogout(TeamSpeak3_Node_Host $host); + + /** + * Possible callback for 'notifyTokencreated' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyTokencreated", array($object, "onTokencreated")); + * + * @param TeamSpeak3_Node_Server $server + * @param string $token + * @return void + */ + public function onTokencreated(TeamSpeak3_Node_Server $server, $token); + + /** + * Possible callback for 'filetransferHandshake' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferHandshake", array($object, "onFtHandshake")); + * + * @param TeamSpeak3_Adapter_FileTransfer $adapter + * @return void + */ + public function onFtHandshake(TeamSpeak3_Adapter_FileTransfer $adapter); + + /** + * Possible callback for 'filetransferUploadStarted' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferUploadStarted", array($object, "onFtUploadStarted")); + * + * @param string $ftkey + * @param integer $seek + * @param integer $size + * @return void + */ + public function onFtUploadStarted($ftkey, $seek, $size); + + /** + * Possible callback for 'filetransferUploadProgress' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferUploadProgress", array($object, "onFtUploadProgress")); + * + * @param string $ftkey + * @param integer $seek + * @param integer $size + * @return void + */ + public function onFtUploadProgress($ftkey, $seek, $size); + + /** + * Possible callback for 'filetransferUploadFinished' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferUploadFinished", array($object, "onFtUploadFinished")); + * + * @param string $ftkey + * @param integer $seek + * @param integer $size + * @return void + */ + public function onFtUploadFinished($ftkey, $seek, $size); + + /** + * Possible callback for 'filetransferDownloadStarted' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferDownloadStarted", array($object, "onFtDownloadStarted")); + * + * @param string $ftkey + * @param integer $buff + * @param integer $size + * @return void + */ + public function onFtDownloadStarted($ftkey, $buff, $size); + + /** + * Possible callback for 'filetransferDownloadProgress' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferDownloadProgress", array($object, "onFtDownloadProgress")); + * + * @param string $ftkey + * @param integer $buff + * @param integer $size + * @return void + */ + public function onFtDownloadProgress($ftkey, $buff, $size); + + /** + * Possible callback for 'filetransferDownloadFinished' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferDownloadFinished", array($object, "onFtDownloadFinished")); + * + * @param string $ftkey + * @param integer $buff + * @param integer $size + * @return void + */ + public function onFtDownloadFinished($ftkey, $buff, $size); + + /** + * Possible callback for 'DataRead' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryDataRead", array($object, "onDebugDataRead")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferDataRead", array($object, "onDebugDataRead")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("blacklistDataRead", array($object, "onDebugDataRead")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("updateDataRead", array($object, "onDebugDataRead")); + * + * @param string $data + * @return void + */ + public function onDebugDataRead($data); + + /** + * Possible callback for 'DataSend' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryDataSend", array($object, "onDebugDataSend")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferDataSend", array($object, "onDebugDataSend")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("blacklistDataSend", array($object, "onDebugDataSend")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("updateDataSend", array($object, "onDebugDataSend")); + * + * @param string $data + * @return void + */ + public function onDebugDataSend($data); + + /** + * Possible callback for 'WaitTimeout' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("serverqueryWaitTimeout", array($object, "onWaitTimeout")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("filetransferWaitTimeout", array($object, "onWaitTimeout")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("blacklistWaitTimeout", array($object, "onWaitTimeout")); + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("updateWaitTimeout", array($object, "onWaitTimeout")); + * + * @param integer $time + * @param TeamSpeak3_Adapter_Abstract $adapter + * @return void + */ + public function onWaitTimeout($time, TeamSpeak3_Adapter_Abstract $adapter); + + /** + * Possible callback for 'errorException' signals. + * + * === Examples === + * - TeamSpeak3_Helper_Signal::getInstance()->subscribe("errorException", array($object, "onException")); + * + * @param TeamSpeak3_Exception $e + * @return void + */ + public function onException(TeamSpeak3_Exception $e); +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/String.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/String.php new file mode 100644 index 0000000..08e7483 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/String.php @@ -0,0 +1,939 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_String + * @brief Helper class for string handling. + */ +class TeamSpeak3_Helper_String implements ArrayAccess, Iterator, Countable +{ + /** + * Stores the original string. + * + * @var string + */ + protected $string; + + /** + * @ignore + */ + protected $position = 0; + + /** + * The TeamSpeak3_Helper_String constructor. + * + * @param string $string + * @return TeamSpeak3_Helper_String + */ + public function __construct($string) + { + $this->string = (string) $string; + } + + /** + * Returns a TeamSpeak3_Helper_String object for thegiven string. + * + * @param string $string + * @return TeamSpeak3_Helper_String + */ + public static function factory($string) + { + return new self($string); + } + + /** + * Replaces every occurrence of the string $search with the string $replace. + * + * @param string $search + * @param string $replace + * @param boolean $caseSensitivity + * @return TeamSpeak3_Helper_String + */ + public function replace($search, $replace, $caseSensitivity = TRUE) + { + if($caseSensitivity) + { + $this->string = str_replace($search, $replace, $this->string); + } + else + { + $this->string = str_ireplace($search, $replace, $this->string); + } + + return $this; + } + + /** + * This function replaces indexed or associative signs with given values. + * + * @param array $args + * @param string $char + * @return TeamSpeak3_Helper_String + */ + public function arg(array $args, $char = "%") + { + $args = array_reverse($args, TRUE); + + foreach($args as $key => $val) + { + $args[$char . $key] = $val; + unset($args[$key]); + } + + $this->string = strtr($this->string, $args); + + return $this; + } + + /** + * Returns true if the string starts with $pattern. + * + * @param string $pattern + * @return boolean + */ + public function startsWith($pattern) + { + return (substr($this->string, 0, strlen($pattern)) == $pattern) ? TRUE : FALSE; + } + + /** + * Returns true if the string ends with $pattern. + * + * @param string $pattern + * @return boolean + */ + public function endsWith($pattern) + { + return (substr($this->string, strlen($pattern)*-1) == $pattern) ? TRUE : FALSE; + } + + /** + * Returns the position of the first occurrence of a char in a string. + * + * @param string $needle + * @return integer + */ + public function findFirst($needle) + { + return strpos($this->string, $needle); + } + + /** + * Returns the position of the last occurrence of a char in a string. + * + * @param string $needle + * @return integer + */ + public function findLast($needle) + { + return strrpos($this->string, $needle); + } + + /** + * Returns the lowercased string. + * + * @return TeamSpeak3_Helper_String + */ + public function toLower() + { + return new self(strtolower($this->string)); + } + + /** + * Returns the uppercased string. + * + * @return TeamSpeak3_Helper_String + */ + public function toUpper() + { + return new self(strtoupper($this->string)); + } + + /** + * Returns true if the string contains $pattern. + * + * @param string $pattern + * @param boolean $regexp + * @return boolean + */ + public function contains($pattern, $regexp = FALSE) + { + if(empty($pattern)) + { + return TRUE; + } + + if($regexp) + { + return (preg_match("/" . $pattern . "/i", $this->string)) ? TRUE : FALSE; + } + else + { + return (stristr($this->string, $pattern) !== FALSE) ? TRUE : FALSE; + } + } + + /** + * Returns part of a string. + * + * @param integer $start + * @param integer $length + * @return TeamSpeak3_Helper_String + */ + public function substr($start, $length = null) + { + $string = ($length !== null) ? substr($this->string, $start, $length) : substr($this->string, $start); + + return new self($string); + } + + /** + * Splits the string into substrings wherever $separator occurs. + * + * @param string $separator + * @param integer $limit + * @return array + */ + public function split($separator, $limit = 0) + { + $parts = explode($separator, $this->string, ($limit) ? intval($limit) : $this->count()); + + foreach($parts as $key => $val) + { + $parts[$key] = new self($val); + } + + return $parts; + } + + /** + * Appends $part to the string. + * + * @param string $part + * @return TeamSpeak3_Helper_String + */ + public function append($part) + { + $this->string = $this->string . strval($part); + + return $this; + } + + /** + * Prepends $part to the string. + * + * @param string $part + * @return TeamSpeak3_Helper_String + */ + public function prepend($part) + { + $this->string = strval($part) . $this->string; + + return $this; + } + + /** + * Returns a section of the string. + * + * @param string $separator + * @param integer $first + * @param integer $last + * @return TeamSpeak3_Helper_String + */ + public function section($separator, $first = 0, $last = 0) + { + $sections = explode($separator, $this->string); + + $total = count($sections); + $first = intval($first); + $last = intval($last); + + if($first > $total) return null; + if($first > $last) $last = $first; + + for($i = 0; $i < $total; $i++) + { + if($i < $first || $i > $last) + { + unset($sections[$i]); + } + } + + $string = implode($separator, $sections); + + return new self($string); + } + + /** + * Sets the size of the string to $size characters. + * + * @param integer $size + * @param string $char + * @return TeamSpeak3_Helper_String + */ + public function resize($size, $char = "\0") + { + $chars = ($size - $this->count()); + + if($chars < 0) + { + $this->string = substr($this->string, 0, $chars); + } + elseif($chars > 0) + { + $this->string = str_pad($this->string, $size, strval($char)); + } + + return $this; + } + + /** + * Strips whitespaces (or other characters) from the beginning and end of the string. + * + * @return TeamSpeak3_Helper_String + */ + public function trim() + { + $this->string = trim($this->string); + + return $this; + } + + /** + * Escapes a string using the TeamSpeak 3 escape patterns. + * + * @return TeamSpeak3_Helper_String + */ + public function escape() + { + foreach(TeamSpeak3::getEscapePatterns() as $search => $replace) + { + $this->string = str_replace($search, $replace, $this->string); + } + + return $this; + } + + /** + * Unescapes a string using the TeamSpeak 3 escape patterns. + * + * @return TeamSpeak3_Helper_String + */ + public function unescape() + { + $this->string = strtr($this->string, array_flip(TeamSpeak3::getEscapePatterns())); + + return $this; + } + + /** + * Removes any non alphanumeric characters from the string. + * + * @return TeamSpeak3_Helper_String + */ + public function filterAlnum() + { + $this->string = preg_replace("/[^[:alnum:]]/", "", $this->string); + + return $this; + } + + /** + * Removes any non alphabetic characters from the string. + * + * @return TeamSpeak3_Helper_String + */ + public function filterAlpha() + { + $this->string = preg_replace("/[^[:alpha:]]/", "", $this->string); + + return $this; + } + + /** + * Removes any non numeric characters from the string. + * + * @return TeamSpeak3_Helper_String + */ + public function filterDigits() + { + $this->string = preg_replace("/[^[:digit:]]/", "", $this->string); + + return $this; + } + + /** + * Returns TRUE if the string is a numeric value. + * + * @return boolean + */ + public function isInt() + { + return (is_numeric($this->string) && !$this->contains(".")) ? TRUE : FALSE; + } + + /** + * Returns the integer value of the string. + * + * @return float + * @return integer + */ + public function toInt() + { + if($this->string == pow(2, 63) || $this->string == pow(2, 64)) + { + return -1; + } + + return ($this->string > pow(2, 31)) ? floatval($this->string) : intval($this->string); + } + + /** + * Calculates and returns the crc32 polynomial of the string. + * + * @return string + */ + public function toCrc32() + { + return crc32($this->string); + } + + /** + * Calculates and returns the md5 checksum of the string. + * + * @return string + */ + public function toMd5() + { + return md5($this->string); + } + + /** + * Calculates and returns the sha1 checksum of the string. + * + * @return string + */ + public function toSha1() + { + return sha1($this->string); + } + + /** + * Returns TRUE if the string is UTF-8 encoded. This method searches for non-ascii multibyte + * sequences in the UTF-8 range. + * + * @return boolean + */ + public function isUtf8() + { + $pattern = array(); + + $pattern[] = "[\xC2-\xDF][\x80-\xBF]"; // non-overlong 2-byte + $pattern[] = "\xE0[\xA0-\xBF][\x80-\xBF]"; // excluding overlongs + $pattern[] = "[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}"; // straight 3-byte + $pattern[] = "\xED[\x80-\x9F][\x80-\xBF]"; // excluding surrogates + $pattern[] = "\xF0[\x90-\xBF][\x80-\xBF]{2}"; // planes 1-3 + $pattern[] = "[\xF1-\xF3][\x80-\xBF]{3}"; // planes 4-15 + $pattern[] = "\xF4[\x80-\x8F][\x80-\xBF]{2}"; // plane 16 + + return preg_match("%(?:" . implode("|", $pattern) . ")+%xs", $this->string); + } + + /** + * Converts the string to UTF-8. + * + * @return TeamSpeak3_Helper_String + */ + public function toUtf8() + { + if(!$this->isUtf8()) + { + $this->string = utf8_encode($this->string); + } + + return $this; + } + + /** + * Encodes the string with MIME base64 and returns the result. + * + * @return string + */ + public function toBase64() + { + return base64_encode($this->string); + } + + /** + * Decodes the string with MIME base64 and returns the result as an TeamSpeak3_Helper_String + * + * @param string $base64 + * @return TeamSpeak3_Helper_String + */ + public static function fromBase64($base64) + { + return new self(base64_decode($base64)); + } + + /** + * Returns the hexadecimal value of the string. + * + * @return string + */ + public function toHex() + { + $hex = ""; + + foreach($this as $char) + { + $hex .= $char->toHex(); + } + + return $hex; + } + + /** + * Returns the TeamSpeak3_Helper_String based on a given hex value. + * + * @param string $hex + * @throws TeamSpeak3_Helper_Exception + * @return TeamSpeak3_Helper_String + */ + public static function fromHex($hex) + { + $string = ""; + + if(strlen($hex)%2 == 1) + { + throw new TeamSpeak3_Helper_Exception("given parameter '" . $hex . "' is not a valid hexadecimal number"); + } + + foreach(str_split($hex, 2) as $chunk) + { + $string .= chr(hexdec($chunk)); + } + + return new self($string); + } + + /** + * Returns the string transliterated from UTF-8 to Latin. + * + * @return TeamSpeak3_Helper_String + */ + public function transliterate() + { + $utf8_accents = array( + "à" => "a", + "ô" => "o", + "ď" => "d", + "ḟ" => "f", + "ë" => "e", + "š" => "s", + "ơ" => "o", + "ß" => "ss", + "ă" => "a", + "ř" => "r", + "ț" => "t", + "ň" => "n", + "ā" => "a", + "ķ" => "k", + "ŝ" => "s", + "ỳ" => "y", + "ņ" => "n", + "ĺ" => "l", + "ħ" => "h", + "ṗ" => "p", + "ó" => "o", + "ú" => "u", + "ě" => "e", + "é" => "e", + "ç" => "c", + "ẁ" => "w", + "ċ" => "c", + "õ" => "o", + "ṡ" => "s", + "ø" => "o", + "ģ" => "g", + "ŧ" => "t", + "ș" => "s", + "ė" => "e", + "ĉ" => "c", + "ś" => "s", + "î" => "i", + "ű" => "u", + "ć" => "c", + "ę" => "e", + "ŵ" => "w", + "ṫ" => "t", + "ū" => "u", + "č" => "c", + "ö" => "oe", + "è" => "e", + "ŷ" => "y", + "ą" => "a", + "ł" => "l", + "ų" => "u", + "ů" => "u", + "ş" => "s", + "ğ" => "g", + "ļ" => "l", + "ƒ" => "f", + "ž" => "z", + "ẃ" => "w", + "ḃ" => "b", + "å" => "a", + "ì" => "i", + "ï" => "i", + "ḋ" => "d", + "ť" => "t", + "ŗ" => "r", + "ä" => "ae", + "í" => "i", + "ŕ" => "r", + "ê" => "e", + "ü" => "ue", + "ò" => "o", + "ē" => "e", + "ñ" => "n", + "ń" => "n", + "ĥ" => "h", + "ĝ" => "g", + "đ" => "d", + "ĵ" => "j", + "ÿ" => "y", + "ũ" => "u", + "ŭ" => "u", + "ư" => "u", + "ţ" => "t", + "ý" => "y", + "ő" => "o", + "â" => "a", + "ľ" => "l", + "ẅ" => "w", + "ż" => "z", + "ī" => "i", + "ã" => "a", + "ġ" => "g", + "ṁ" => "m", + "ō" => "o", + "ĩ" => "i", + "ù" => "u", + "į" => "i", + "ź" => "z", + "á" => "a", + "û" => "u", + "þ" => "th", + "ð" => "dh", + "æ" => "ae", + "µ" => "u", + "ĕ" => "e", + "œ" => "oe", + "À" => "A", + "Ô" => "O", + "Ď" => "D", + "Ḟ" => "F", + "Ë" => "E", + "Š" => "S", + "Ơ" => "O", + "Ă" => "A", + "Ř" => "R", + "Ț" => "T", + "Ň" => "N", + "Ā" => "A", + "Ķ" => "K", + "Ŝ" => "S", + "Ỳ" => "Y", + "Ņ" => "N", + "Ĺ" => "L", + "Ħ" => "H", + "Ṗ" => "P", + "Ó" => "O", + "Ú" => "U", + "Ě" => "E", + "É" => "E", + "Ç" => "C", + "Ẁ" => "W", + "Ċ" => "C", + "Õ" => "O", + "Ṡ" => "S", + "Ø" => "O", + "Ģ" => "G", + "Ŧ" => "T", + "Ș" => "S", + "Ė" => "E", + "Ĉ" => "C", + "Ś" => "S", + "Î" => "I", + "Ű" => "U", + "Ć" => "C", + "Ę" => "E", + "Ŵ" => "W", + "Ṫ" => "T", + "Ū" => "U", + "Č" => "C", + "Ö" => "Oe", + "È" => "E", + "Ŷ" => "Y", + "Ą" => "A", + "Ł" => "L", + "Ų" => "U", + "Ů" => "U", + "Ş" => "S", + "Ğ" => "G", + "Ļ" => "L", + "Ƒ" => "F", + "Ž" => "Z", + "Ẃ" => "W", + "Ḃ" => "B", + "Å" => "A", + "Ì" => "I", + "Ï" => "I", + "Ḋ" => "D", + "Ť" => "T", + "Ŗ" => "R", + "Ä" => "Ae", + "Í" => "I", + "Ŕ" => "R", + "Ê" => "E", + "Ü" => "Ue", + "Ò" => "O", + "Ē" => "E", + "Ñ" => "N", + "Ń" => "N", + "Ĥ" => "H", + "Ĝ" => "G", + "Đ" => "D", + "Ĵ" => "J", + "Ÿ" => "Y", + "Ũ" => "U", + "Ŭ" => "U", + "Ư" => "U", + "Ţ" => "T", + "Ý" => "Y", + "Ő" => "O", + "Â" => "A", + "Ľ" => "L", + "Ẅ" => "W", + "Ż" => "Z", + "Ī" => "I", + "Ã" => "A", + "Ġ" => "G", + "Ṁ" => "M", + "Ō" => "O", + "Ĩ" => "I", + "Ù" => "U", + "Į" => "I", + "Ź" => "Z", + "Á" => "A", + "Û" => "U", + "Þ" => "Th", + "Ð" => "Dh", + "Æ" => "Ae", + "Ĕ" => "E", + "Œ" => "Oe", + ); + + return new self($this->toUtf8()->replace(array_keys($utf8_accents), array_values($utf8_accents))); + } + + /** + * Processes the string and replaces all accented UTF-8 characters by unaccented ASCII-7 "equivalents", + * whitespaces are replaced by a pre-defined spacer and the string is lowercase. + * + * @param string $spacer + * @return TeamSpeak3_Helper_String + */ + public function uriSafe($spacer = "-") + { + $this->string = str_replace($spacer, " ", $this->string); + $this->string = $this->transliterate(); + $this->string = preg_replace("/(\s|[^A-Za-z0-9\-])+/", $spacer, trim(strtolower($this->string))); + $this->string = trim($this->string, $spacer); + + return new self($this->string); + } + + /** + * Replaces space characters with percent encoded strings. + * + * @return string + */ + public function spaceToPercent() + { + return str_replace(" ", "%20", $this->string); + } + + /** + * Returns the string as a standard string + * + * @return string + */ + public function toString() + { + return $this->string; + } + + /** + * Magical function that allows you to call PHP's built-in string functions on the TeamSpeak3_Helper_String object. + * + * @param string $function + * @param array $args + * @throws TeamSpeak3_Helper_Exception + * @return TeamSpeak3_Helper_String + */ + public function __call($function, $args) + { + if(!function_exists($function)) + { + throw new TeamSpeak3_Helper_Exception("cannot call undefined function '" . $function . "' on this object"); + } + + if(count($args)) + { + if(($key = array_search($this, $args, TRUE)) !== FALSE) + { + $args[$key] = $this->string; + } + else + { + throw new TeamSpeak3_Helper_Exception("cannot call undefined function '" . $function . "' without the " . __CLASS__ . " object parameter"); + } + + $return = call_user_func_array($function, $args); + } + else + { + $return = call_user_func($function, $this->string); + } + + if(is_string($return)) + { + $this->string = $return; + } + else + { + return $return; + } + + return $this; + } + + /** + * Returns the character as a standard string. + * + * @return string + */ + public function __toString() + { + return (string) $this->string; + } + + /** + * @ignore + */ + public function count() + { + return strlen($this->string); + } + + /** + * @ignore + */ + public function rewind() + { + $this->position = 0; + } + + /** + * @ignore + */ + public function valid() + { + return $this->position < $this->count(); + } + + /** + * @ignore + */ + public function key() + { + return $this->position; + } + + /** + * @ignore + */ + public function current() + { + return new TeamSpeak3_Helper_Char($this->string{$this->position}); + } + + /** + * @ignore + */ + public function next() + { + $this->position++; + } + + /** + * @ignore + */ + public function offsetExists($offset) + { + return ($offset < strlen($this->string)) ? TRUE : FALSE; + } + + /** + * @ignore + */ + public function offsetGet($offset) + { + return ($this->offsetExists($offset)) ? new TeamSpeak3_Helper_Char($this->string{$offset}) : null; + } + + /** + * @ignore + */ + public function offsetSet($offset, $value) + { + if(!$this->offsetExists($offset)) return; + + $this->string{$offset} = strval($value); + } + + /** + * @ignore + */ + public function offsetUnset($offset) + { + if(!$this->offsetExists($offset)) return; + + $this->string = substr_replace($this->string, "", $offset, 1); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Uri.php b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Uri.php new file mode 100644 index 0000000..4e89930 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Helper/Uri.php @@ -0,0 +1,717 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Helper_Uri + * @brief Helper class for URI handling. + */ +class TeamSpeak3_Helper_Uri +{ + /** + * Stores the URI scheme. + * + * @var string + */ + protected $scheme = null; + + /** + * Stores the URI username + * + * @var string + */ + protected $user = null; + + /** + * Stores the URI password. + * + * @var string + */ + protected $pass = null; + + /** + * Stores the URI host. + * + * @var string + */ + protected $host = null; + + /** + * Stores the URI port. + * + * @var string + */ + protected $port = null; + + /** + * Stores the URI path. + * + * @var string + */ + protected $path = null; + + /** + * Stores the URI query string. + * + * @var string + */ + protected $query = null; + + /** + * Stores the URI fragment string. + * + * @var string + */ + protected $fragment = null; + + /** + * Stores grammar rules for validation via regex. + * + * @var array + */ + protected $regex = array(); + + /** + * The TeamSpeak3_Helper_Uri constructor. + * + * @param string $uri + * @throws TeamSpeak3_Helper_Exception + * @return TeamSpeak3_Helper_Uri + */ + public function __construct($uri) + { + $uri = explode(":", strval($uri), 2); + + $this->scheme = strtolower($uri[0]); + $uriString = isset($uri[1]) ? $uri[1] : ""; + + if(!ctype_alnum($this->scheme)) + { + throw new TeamSpeak3_Helper_Exception("invalid URI scheme '" . $this->scheme . "' supplied"); + } + + /* grammar rules for validation */ + $this->regex["alphanum"] = "[^\W_]"; + $this->regex["escaped"] = "(?:%[\da-fA-F]{2})"; + $this->regex["mark"] = "[-_.!~*'()\[\]]"; + $this->regex["reserved"] = "[;\/?:@&=+$,]"; + $this->regex["unreserved"] = "(?:" . $this->regex["alphanum"] . "|" . $this->regex["mark"] . ")"; + $this->regex["segment"] = "(?:(?:" . $this->regex["unreserved"] . "|" . $this->regex["escaped"] . "|[:@&=+$,;])*)"; + $this->regex["path"] = "(?:\/" . $this->regex["segment"] . "?)+"; + $this->regex["uric"] = "(?:" . $this->regex["reserved"] . "|" . $this->regex["unreserved"] . "|" . $this->regex["escaped"] . ")"; + + if(strlen($uriString) > 0) + { + $this->parseUri($uriString); + } + + if(!$this->isValid()) + { + throw new TeamSpeak3_Helper_Exception("invalid URI supplied"); + } + } + + /** + * Parses the scheme-specific portion of the URI and place its parts into instance variables. + * + * @throws TeamSpeak3_Helper_Exception + * @return void + */ + protected function parseUri($uriString = '') + { + $status = @preg_match("~^((//)([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))?$~", $uriString, $matches); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI scheme-specific decomposition failed"); + } + + if(!$status) return; + + $this->path = (isset($matches[4])) ? $matches[4] : ''; + $this->query = (isset($matches[6])) ? $matches[6] : ''; + $this->fragment = (isset($matches[8])) ? $matches[8] : ''; + + $status = @preg_match("~^(([^:@]*)(:([^@]*))?@)?((?(?=[[])[[][^]]+[]]|[^:]+))(:(.*))?$~", (isset($matches[3])) ? $matches[3] : "", $matches); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI scheme-specific authority decomposition failed"); + } + + if(!$status) return; + + $this->user = isset($matches[2]) ? $matches[2] : ""; + $this->pass = isset($matches[4]) ? $matches[4] : ""; + $this->host = isset($matches[5]) === TRUE ? preg_replace('~^\[([^]]+)\]$~', '\1', $matches[5]) : ""; + $this->port = isset($matches[7]) ? $matches[7] : ""; + } + + /** + * Validate the current URI from the instance variables. + * + * @return boolean + */ + public function isValid() + { + return ($this->checkUser() && $this->checkPass() && $this->checkHost() && $this->checkPort() && $this->checkPath() && $this->checkQuery() && $this->checkFragment()); + } + + /** + * Returns TRUE if a given URI is valid. + * + * @param string $uri + * @return boolean + */ + public static function check($uri) + { + try + { + $uri = new self(strval($uri)); + } + catch(Exception $e) + { + return FALSE; + } + + return $uri->valid(); + } + + /** + * Returns TRUE if the URI has a scheme. + * + * @return boolean + */ + public function hasScheme() + { + return strlen($this->scheme) ? TRUE : FALSE; + } + + /** + * Returns the scheme. + * + * @param mixed default + * @return TeamSpeak3_Helper_String + */ + public function getScheme($default = null) + { + return ($this->hasScheme()) ? new TeamSpeak3_Helper_String($this->scheme) : $default; + } + + /** + * Returns TRUE if the username is valid. + * + * @param string $username + * @throws TeamSpeak3_Helper_Exception + * @return boolean + */ + public function checkUser($username = null) + { + if($username === null) + { + $username = $this->user; + } + + if(strlen($username) == 0) + { + return TRUE; + } + + $pattern = "/^(" . $this->regex["alphanum"] . "|" . $this->regex["mark"] . "|" . $this->regex["escaped"] . "|[;:&=+$,])+$/"; + $status = @preg_match($pattern, $username); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI username validation failed"); + } + + return ($status == 1); + } + + /** + * Returns TRUE if the URI has a username. + * + * @return boolean + */ + public function hasUser() + { + return strlen($this->user) ? TRUE : FALSE; + } + + /** + * Returns the username. + * + * @param mixed default + * @return TeamSpeak3_Helper_String + */ + public function getUser($default = null) + { + return ($this->hasUser()) ? new TeamSpeak3_Helper_String($this->user) : $default; + } + + /** + * Returns TRUE if the password is valid. + * + * @param string $password + * @throws TeamSpeak3_Helper_Exception + * @return boolean + */ + public function checkPass($password = null) + { + if($password === null) { + $password = $this->pass; + } + + if(strlen($password) == 0) + { + return TRUE; + } + + $pattern = "/^(" . $this->regex["alphanum"] . "|" . $this->regex["mark"] . "|" . $this->regex["escaped"] . "|[;:&=+$,])+$/"; + $status = @preg_match($pattern, $password); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI password validation failed"); + } + + return ($status == 1); + } + + /** + * Returns TRUE if the URI has a password. + * + * @return boolean + */ + public function hasPass() + { + return strlen($this->pass) ? TRUE : FALSE; + } + + /** + * Returns the password. + * + * @param mixed default + * @return TeamSpeak3_Helper_String + */ + public function getPass($default = null) + { + return ($this->hasPass()) ? new TeamSpeak3_Helper_String($this->pass) : $default; + } + + /** + * Returns TRUE if the host is valid. + * + * @param string $host + * @return boolean + */ + public function checkHost($host = null) + { + if($host === null) + { + $host = $this->host; + } + + return TRUE; + } + + /** + * Returns TRUE if the URI has a host. + * + * @return boolean + */ + public function hasHost() + { + return strlen($this->host) ? TRUE : FALSE; + } + + /** + * Returns the host. + * + * @param mixed default + * @return TeamSpeak3_Helper_String + */ + public function getHost($default = null) + { + return ($this->hasHost()) ? new TeamSpeak3_Helper_String($this->host) : $default; + } + + /** + * Returns TRUE if the port is valid. + * + * @param integer $port + * @return boolean + */ + public function checkPort($port = null) + { + if($port === null) + { + $port = $this->port; + } + + return TRUE; + } + + /** + * Returns TRUE if the URI has a port. + * + * @return boolean + */ + public function hasPort() + { + return strlen($this->port) ? TRUE : FALSE; + } + + /** + * Returns the port. + * + * @param mixed default + * @return integer + */ + public function getPort($default = null) + { + return ($this->hasPort()) ? intval($this->port) : $default; + } + + /** + * Returns TRUE if the path is valid. + * + * @param string $path + * @throws TeamSpeak3_Helper_Exception + * @return boolean + */ + public function checkPath($path = null) + { + if($path === null) + { + $path = $this->path; + } + + if(strlen($path) == 0) + { + return TRUE; + } + + $pattern = "/^" . $this->regex["path"] . "$/"; + $status = @preg_match($pattern, $path); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI path validation failed"); + } + + return ($status == 1); + } + + /** + * Returns TRUE if the URI has a path. + * + * @return boolean + */ + public function hasPath() + { + return strlen($this->path) ? TRUE : FALSE; + } + + /** + * Returns the path. + * + * @param mixed default + * @return TeamSpeak3_Helper_String + */ + public function getPath($default = null) + { + return ($this->hasPath()) ? new TeamSpeak3_Helper_String($this->path) : $default; + } + + /** + * Returns TRUE if the query string is valid. + * + * @param string $query + * @throws TeamSpeak3_Helper_Exception + * @return boolean + */ + public function checkQuery($query = null) + { + if($query === null) + { + $query = $this->query; + } + + if(strlen($query) == 0) + { + return TRUE; + } + + $pattern = "/^" . $this->regex["uric"] . "*$/"; + $status = @preg_match($pattern, $query); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI query string validation failed"); + } + + return ($status == 1); + } + + /** + * Returns TRUE if the URI has a query string. + * + * @return boolean + */ + public function hasQuery() + { + return strlen($this->query) ? TRUE : FALSE; + } + + /** + * Returns an array containing the query string elements. + * + * @param mixed $default + * @return array + */ + public function getQuery($default = array()) + { + if(!$this->hasQuery()) + { + return $default; + } + + parse_str($this->query, $queryArray); + + return $queryArray; + } + + /** + * Returns TRUE if the URI has a query variable. + * + * @return boolean + */ + public function hasQueryVar($key) + { + if(!$this->hasQuery()) return FALSE; + + parse_str($this->query, $queryArray); + + return array_key_exists($key, $queryArray) ? TRUE : FALSE; + } + + /** + * Returns a single variable from the query string. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getQueryVar($key, $default = null) + { + if(!$this->hasQuery()) return $default; + + parse_str($this->query, $queryArray); + + if(array_key_exists($key, $queryArray)) + { + $val = $queryArray[$key]; + + if(ctype_digit($val)) + { + return intval($val); + } + elseif(is_string($val)) + { + return new TeamSpeak3_Helper_String($val); + } + else + { + return $val; + } + } + + return $default; + } + + /** + * Returns TRUE if the fragment string is valid. + * + * @param string $fragment + * @throws TeamSpeak3_Helper_Exception + * @return boolean + */ + public function checkFragment($fragment = null) + { + if($fragment === null) + { + $fragment = $this->fragment; + } + + if(strlen($fragment) == 0) + { + return TRUE; + } + + $pattern = "/^" . $this->regex["uric"] . "*$/"; + $status = @preg_match($pattern, $fragment); + + if($status === FALSE) + { + throw new TeamSpeak3_Helper_Exception("URI fragment validation failed"); + } + + return ($status == 1); + } + + /** + * Returns TRUE if the URI has a fragment string. + * + * @return boolean + */ + public function hasFragment() + { + return strlen($this->fragment) ? TRUE : FALSE; + } + + /** + * Returns the fragment. + * + * @param mixed default + * @return TeamSpeak3_Helper_String + */ + public function getFragment($default = null) + { + return ($this->hasFragment()) ? new TeamSpeak3_Helper_String($this->fragment) : $default; + } + + /** + * Returns a specified instance parameter from the $_REQUEST array. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function getUserParam($key, $default = null) + { + return (array_key_exists($key, $_REQUEST) && !empty($_REQUEST[$key])) ? self::stripslashesRecursive($_REQUEST[$key]) : $default; + } + + /** + * Returns a specified environment parameter from the $_SERVER array. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function getHostParam($key, $default = null) + { + return (array_key_exists($key, $_SERVER) && !empty($_SERVER[$key])) ? $_SERVER[$key] : $default; + } + + /** + * Returns a specified session parameter from the $_SESSION array. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function getSessParam($key, $default = null) + { + return (array_key_exists($key, $_SESSION) && !empty($_SESSION[$key])) ? $_SESSION[$key] : $default; + } + + /** + * Returns an array containing the three main parts of a FQDN (Fully Qualified Domain Name), including the + * top-level domain, the second-level domains or hostname and the third-level domain. + * + * @param string $hostname + * @return array + */ + public static function getFQDNParts($hostname) + { + if(!preg_match("/^([a-z0-9][a-z0-9-]{0,62}\.)*([a-z0-9][a-z0-9-]{0,62}\.)+([a-z]{2,6})$/i", $hostname, $matches)) + { + return array(); + } + + $parts["tld"] = $matches[3]; + $parts["2nd"] = $matches[2]; + $parts["3rd"] = $matches[1]; + + return $parts; + } + + /** + * Returns the applications host address. + * + * @return TeamSpeak3_Helper_String + */ + public static function getHostUri() + { + $sheme = (self::getHostParam("HTTPS") == "on") ? "https" : "http"; + + $serverName = new TeamSpeak3_Helper_String(self::getHostParam("HTTP_HOST")); + $serverPort = self::getHostParam("SERVER_PORT"); + $serverPort = ($serverPort != 80 && $serverPort != 443) ? ":" . $serverPort : ""; + + if($serverName->endsWith($serverPort)) + { + $serverName = $serverName->replace($serverPort, ""); + } + + return new TeamSpeak3_Helper_String($sheme . "://" . $serverName . $serverPort); + } + + /** + * Returns the applications base address. + * + * @return string + */ + public static function getBaseUri() + { + $scriptPath = new TeamSpeak3_Helper_String(dirname(self::getHostParam("SCRIPT_NAME"))); + + return self::getHostUri()->append(($scriptPath == DIRECTORY_SEPARATOR ? "" : $scriptPath) . "/"); + } + + /** + * Strips slashes from each element of an array using stripslashes(). + * + * @param mixed $var + * @return mixed + */ + protected static function stripslashesRecursive($var) + { + if(!is_array($var)) + { + return stripslashes(strval($var)); + } + + foreach($var as $key => $val) + { + $var[$key] = (is_array($val)) ? stripslashesRecursive($val) : stripslashes(strval($val)); + } + + return $var; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Abstract.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Abstract.php new file mode 100644 index 0000000..d6244f8 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Abstract.php @@ -0,0 +1,624 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Abstract + * @brief Abstract class describing a TeamSpeak 3 node and all it's parameters. + */ +abstract class TeamSpeak3_Node_Abstract implements RecursiveIterator, ArrayAccess, Countable +{ + /** + * @ignore + */ + protected $parent = null; + + /** + * @ignore + */ + protected $server = null; + + /** + * @ignore + */ + protected $nodeId = 0x00; + + /** + * @ignore + */ + protected $nodeList = null; + + /** + * @ignore + */ + protected $nodeInfo = array(); + + /** + * @ignore + */ + protected $storage = array(); + + /** + * Sends a prepared command to the server and returns the result. + * + * @param string $cmd + * @param boolean $throw + * @return TeamSpeak3_Adapter_ServerQuery_Reply + */ + public function request($cmd, $throw = TRUE) + { + return $this->getParent()->request($cmd, $throw); + } + + /** + * Uses given parameters and returns a prepared ServerQuery command. + * + * @param string $cmd + * @param array $params + * @return TeamSpeak3_Helper_String + */ + public function prepare($cmd, array $params = array()) + { + return $this->getParent()->prepare($cmd, $params); + } + + /** + * Prepares and executes a ServerQuery command and returns the result. + * + * @param string $cmd + * @param array $params + * @return TeamSpeak3_Adapter_ServerQuery_Reply + */ + public function execute($cmd, array $params = array()) + { + return $this->request($this->prepare($cmd, $params)); + } + + /** + * Returns the parent object of the current node. + * + * @return TeamSpeak3_Adapter_ServerQuery + * @return TeamSpeak3_Node_Abstract + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns the primary ID of the current node. + * + * @return integer + */ + public function getId() + { + return $this->nodeId; + } + + /** + * Returns TRUE if the node icon has a local source. + * + * @param string $key + * @return boolean + */ + public function iconIsLocal($key) + { + return ($this[$key] > 0 && $this[$key] < 1000) ? TRUE : FALSE; + } + + /** + * Returns the internal path of the node icon. + * + * @param string $key + * @return TeamSpeak3_Helper_String + */ + public function iconGetName($key) + { + $iconid = ($this[$key] < 0) ? (pow(2, 32))-($this[$key]*-1) : $this[$key]; + + return new TeamSpeak3_Helper_String("/icon_" . $iconid); + } + + /** + * Returns a possible classname for the node which can be used as a HTML property. + * + * @param string $prefix + * @return string + */ + public function getClass($prefix = "ts3_") + { + if($this instanceof TeamSpeak3_Node_Channel && $this->isSpacer()) + { + return $prefix . "spacer"; + } + elseif($this instanceof TeamSpeak3_Node_Client && $this["client_type"]) + { + return $prefix . "query"; + } + + return $prefix . TeamSpeak3_Helper_String::factory(get_class($this))->section("_", 2)->toLower(); + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + abstract public function getUniqueId(); + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + abstract public function getIcon(); + + /** + * Returns a symbol representing the node. + * + * @return string + */ + abstract public function getSymbol(); + + /** + * Returns the HTML code to display a TeamSpeak 3 viewer. + * + * @param TeamSpeak3_Viewer_Interface $viewer + * @return string + */ + public function getViewer(TeamSpeak3_Viewer_Interface $viewer) + { + $html = $viewer->fetchObject($this); + + $iterator = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST); + + foreach($iterator as $node) + { + $siblings = array(); + + for($level = 0; $level < $iterator->getDepth(); $level++) + { + $siblings[] = ($iterator->getSubIterator($level)->hasNext()) ? 1 : 0; + } + + $siblings[] = (!$iterator->getSubIterator($level)->hasNext()) ? 1 : 0; + + $html .= $viewer->fetchObject($node, $siblings); + } + + return $html; + } + + /** + * Filters given node list array using specified filter rules. + * + * @param array $nodes + * @param array $rules + * @return array + */ + protected function filterList(array $nodes = array(), array $rules = array()) + { + if(!empty($rules)) + { + foreach($nodes as $node) + { + if(!$node instanceof TeamSpeak3_Node_Abstract) continue; + + $props = $node->getInfo(FALSE); + $props = array_intersect_key($props, $rules); + + foreach($props as $key => $val) + { + if($val instanceof TeamSpeak3_Helper_String) + { + $match = $val->contains($rules[$key], TRUE); + } + else + { + $match = $val == $rules[$key]; + } + + if($match === FALSE) + { + unset($nodes[$node->getId()]); + } + } + } + } + + return $nodes; + } + + /** + * Returns all information available on this node. If $convert is enabled, some property + * values will be converted to human-readable values. + * + * @param boolean $extend + * @param boolean $convert + * @return array + */ + public function getInfo($extend = TRUE, $convert = FALSE) + { + if($extend) + { + $this->fetchNodeInfo(); + } + + if($convert) + { + $info = $this->nodeInfo; + + foreach($info as $key => $val) + { + $key = TeamSpeak3_Helper_String::factory($key); + + if($key->contains("_bytes_")) + { + $info[$key->toString()] = TeamSpeak3_Helper_Convert::bytes($val); + } + elseif($key->contains("_bandwidth_")) + { + $info[$key->toString()] = TeamSpeak3_Helper_Convert::bytes($val) . "/s"; + } + elseif($key->contains("_packets_")) + { + $info[$key->toString()] = number_format($val, null, null, "."); + } + elseif($key->contains("_packetloss_")) + { + $info[$key->toString()] = sprintf("%01.2f", floatval($val->toString())*100) . "%"; + } + elseif($key->endsWith("_uptime")) + { + $info[$key->toString()] = TeamSpeak3_Helper_Convert::seconds($val); + } + elseif($key->endsWith("_version")) + { + $info[$key->toString()] = TeamSpeak3_Helper_Convert::version($val); + } + elseif($key->endsWith("_icon_id")) + { + $info[$key->toString()] = $this->iconGetName($key)->filterDigits(); + } + } + + return $info; + } + + return $this->nodeInfo; + } + + /** + * Returns the specified property or a pre-defined default value from the node info array. + * + * @param string $property + * @param mixed $default + * @return mixed + */ + public function getProperty($property, $default = null) + { + if(!$this->offsetExists($property)) + { + $this->fetchNodeInfo(); + } + + if(!$this->offsetExists($property)) + { + return $default; + } + + return $this->nodeInfo[(string) $property]; + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return get_class($this); + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function toString() + { + return $this->__toString(); + } + + /** + * Returns an assoc array filled with current node info properties. + * + * @return array + */ + public function toArray() + { + return $this->nodeList; + } + + /** + * Called whenever we're using an unknown method. + * + * @param string $name + * @param array $args + * @throws TeamSpeak3_Node_Exception + * @return mixed + */ + public function __call($name, array $args) + { + if($this->getParent() instanceof TeamSpeak3_Node_Abstract) + { + return call_user_func_array(array($this->getParent(), $name), $args); + } + + throw new TeamSpeak3_Node_Exception("node method '" . $name . "()' does not exist"); + } + + /** + * Writes data to the internal storage array. + * + * @param string $key + * @param mixed $val + * @return void + */ + protected function setStorage($key, $val) + { + $this->storage[$key] = $val; + } + + /** + * Returns data from the internal storage array. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + protected function getStorage($key, $default = null) + { + return !empty($this->storage[$key]) ? $this->storage[$key] : $default; + } + + /** + * Deletes data from the internal storage array. + * + * @param string $key + * @return void + */ + protected function delStorage($key) + { + unset($this->storage[$key]); + } + + /** + * Commit pending data. + * + * @return array + */ + public function __sleep() + { + return array("parent", "storage", "nodeId"); + } + + /** + * @ignore + */ + protected function fetchNodeList() + { + $this->nodeList = array(); + } + + /** + * @ignore + */ + protected function fetchNodeInfo() + { + return; + } + + /** + * @ignore + */ + protected function resetNodeInfo() + { + $this->nodeInfo = array(); + } + + /** + * @ignore + */ + protected function verifyNodeList() + { + if($this->nodeList === null) + { + $this->fetchNodeList(); + } + } + + /** + * @ignore + */ + protected function resetNodeList() + { + $this->nodeList = null; + } + + /** + * @ignore + */ + public function count() + { + $this->verifyNodeList(); + + return count($this->nodeList); + } + + /** + * @ignore + */ + public function current() + { + $this->verifyNodeList(); + + return current($this->nodeList); + } + + /** + * @ignore + */ + public function getChildren() + { + $this->verifyNodeList(); + + return $this->current(); + } + + /** + * @ignore + */ + public function hasChildren() + { + $this->verifyNodeList(); + + return $this->current()->count() > 0; + } + + /** + * @ignore + */ + public function hasNext() + { + $this->verifyNodeList(); + + return $this->key()+1 < $this->count(); + } + + /** + * @ignore + */ + public function key() + { + $this->verifyNodeList(); + + return key($this->nodeList); + } + + /** + * @ignore + */ + public function valid() + { + $this->verifyNodeList(); + + return $this->key() !== null; + } + + /** + * @ignore + */ + public function next() + { + $this->verifyNodeList(); + + return next($this->nodeList); + } + + /** + * @ignore + */ + public function rewind() + { + $this->verifyNodeList(); + + return reset($this->nodeList); + } + + /** + * @ignore + */ + public function offsetExists($offset) + { + return array_key_exists((string) $offset, $this->nodeInfo) ? TRUE : FALSE; + } + + /** + * @ignore + */ + public function offsetGet($offset) + { + if(!$this->offsetExists($offset)) + { + $this->fetchNodeInfo(); + } + + if(!$this->offsetExists($offset)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid parameter", 0x602); + } + + return $this->nodeInfo[(string) $offset]; + } + + /** + * @ignore + */ + public function offsetSet($offset, $value) + { + if(method_exists($this, "modify")) + { + return $this->modify(array((string) $offset => $value)); + } + + throw new TeamSpeak3_Node_Exception("node '" . get_class($this) . "' is read only"); + } + + /** + * @ignore + */ + public function offsetUnset($offset) + { + unset($this->nodeInfo[(string) $offset]); + } + + /** + * @ignore + */ + public function __get($offset) + { + return $this->offsetGet($offset); + } + + /** + * @ignore + */ + public function __set($offset, $value) + { + $this->offsetSet($offset, $value); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Channel.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Channel.php new file mode 100644 index 0000000..cbc1835 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Channel.php @@ -0,0 +1,588 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Channel + * @brief Class describing a TeamSpeak 3 channel and all it's parameters. + */ +class TeamSpeak3_Node_Channel extends TeamSpeak3_Node_Abstract +{ + /** + * The TeamSpeak3_Node_Channel constructor. + * + * @param TeamSpeak3_Node_Server $server + * @param array $info + * @param string $index + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channel + */ + public function __construct(TeamSpeak3_Node_Server $server, array $info, $index = "cid") + { + $this->parent = $server; + $this->nodeInfo = $info; + + if(!array_key_exists($index, $this->nodeInfo)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channelID", 0x300); + } + + $this->nodeId = $this->nodeInfo[$index]; + } + + /** + * Returns an array filled with TeamSpeak3_Node_Channel objects. + * + * @param array $filter + * @return array + */ + public function subChannelList(array $filter = array()) + { + $channels = array(); + + foreach($this->getParent()->channelList() as $channel) + { + if($channel["pid"] == $this->getId()) + { + $channels[$channel->getId()] = $channel; + } + } + + return $this->filterList($channels, $filter); + } + + /** + * Returns the TeamSpeak3_Node_Channel object matching the given ID. + * + * @param integer $cid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channel + */ + public function subChannelGetById($cid) + { + if(!array_key_exists((string) $cid, $this->subChannelList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channelID", 0x300); + } + + return $this->channelList[(string) $cid]; + } + + /** + * Returns the TeamSpeak3_Node_Channel object matching the given name. + * + * @param integer $name + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channel + */ + public function subChannelGetByName($name) + { + foreach($this->subChannelList() as $channel) + { + if($channel["channel_name"] == $name) return $channel; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channelID", 0x300); + } + + /** + * Returns an array filled with TeamSpeak3_Node_Client objects. + * + * @param array $filter + * @return array + */ + public function clientList(array $filter = array()) + { + $clients = array(); + + foreach($this->getParent()->clientList() as $client) + { + if($client["cid"] == $this->getId()) + { + $clients[$client->getId()] = $client; + } + } + + return $this->filterList($clients, $filter); + } + + /** + * Returns the TeamSpeak3_Node_Client object matching the given ID. + * + * @param integer $clid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function clientGetById($clid) + { + if(!array_key_exists($clid, $this->clientList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + return $this->clientList[intval($clid)]; + } + + /** + * Returns the TeamSpeak3_Node_Client object matching the given name. + * + * @param integer $name + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function clientGetByName($name) + { + foreach($this->clientList() as $client) + { + if($client["client_nickname"] == $name) return $client; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + /** + * Returns a list of permissions defined for a client in the channel. + * + * @param integer $cldbid + * @param boolean $permsid + * @return array + */ + public function clientPermList($cldbid, $permsid = FALSE) + { + return $this->getParent()->channelClientPermList($this->getId(), $cldbid, $permsid); + } + + /** + * Adds a set of specified permissions to a client in a specific channel. Multiple permissions can be added by + * providing the two parameters of each permission. + * + * @param integer $cldbid + * @param integer $permid + * @param integer $permvalue + * @return void + */ + public function clientPermAssign($cldbid, $permid, $permvalue) + { + $this->getParent()->channelClientPermAssign($this->getId(), $cldbid, $permid, $permvalue); + } + + /** + * Alias for clientPermAssign(). + * + * @deprecated + */ + public function clientPermAssignByName($cldbid, $permname, $permvalue) + { + $this->clientPermAssign($cldbid, $permname, $permvalue); + } + + /** + * Removes a set of specified permissions from a client in the channel. Multiple permissions can be removed at once. + * + * @param integer $cldbid + * @param integer $permid + * @return void + */ + public function clientPermRemove($cldbid, $permid) + { + $this->getParent()->channelClientPermRemove($this->getId(), $cldbid, $permid); + } + + /** + * Alias for clientPermRemove(). + * + * @deprecated + */ + public function clientPermRemoveByName($cldbid, $permname) + { + $this->clientPermRemove($cldbid, $permname); + } + + /** + * Returns a list of permissions defined for the channel. + * + * @param boolean $permsid + * @return array + */ + public function permList($permsid = FALSE) + { + return $this->getParent()->channelPermList($this->getId(), $permsid); + } + + /** + * Adds a set of specified permissions to the channel. Multiple permissions can be added by + * providing the two parameters of each permission. + * + * @param integer $permid + * @param integer $permvalue + * @return void + */ + public function permAssign($permid, $permvalue) + { + $this->getParent()->channelPermAssign($this->getId(), $permid, $permvalue); + } + + /** + * Alias for permAssign(). + * + * @deprecated + */ + public function permAssignByName($permname, $permvalue) + { + $this->permAssign($permname, $permvalue); + } + + /** + * Removes a set of specified permissions from the channel. Multiple permissions can be removed at once. + * + * @param integer $permid + * @return void + */ + public function permRemove($permid) + { + $this->getParent()->channelPermRemove($this->getId(), $permid); + } + + /** + * Alias for permRemove(). + * + * @deprecated + */ + public function permRemoveByName($permname) + { + $this->permRemove($permname); + } + + /** + * Returns a list of files and directories stored in the channels file repository. + * + * @param string $cpw + * @param string $path + * @param boolean $recursive + * @return array + */ + public function fileList($cpw = "", $path = "/", $recursive = FALSE) + { + return $this->getParent()->channelFileList($this->getId(), $cpw, $path, $recursive); + } + + /** + * Returns detailed information about the specified file stored in the channels file repository. + * + * @param string $cpw + * @param string $name + * @return array + */ + public function fileInfo($cpw = "", $name = "/") + { + return $this->getParent()->channelFileInfo($this->getId(), $cpw, $name); + } + + /** + * Renames a file in the channels file repository. If the two parameters $tcid and $tcpw are specified, the file + * will be moved into another channels file repository. + * + * @param string $cpw + * @param string $oldname + * @param string $newname + * @param integer $tcid + * @param string $tcpw + * @return void + */ + public function fileRename($cpw = "", $oldname = "/", $newname = "/", $tcid = null, $tcpw = null) + { + $this->getParent()->channelFileRename($this->getId(), $cpw, $oldname, $newname, $tcid, $tcpw); + } + + /** + * Deletes one or more files stored in the channels file repository. + * + * @param string $cpw + * @param string $path + * @return void + */ + public function fileDelete($cpw = "", $name = "/") + { + $this->getParent()->channelFileDelete($this->getId(), $cpw, $name); + } + + /** + * Creates new directory in a channels file repository. + * + * @param string $cpw + * @param string $dirname + * @return void + */ + public function dirCreate($cpw = "", $dirname = "/") + { + $this->getParent()->channelDirCreate($this->getId(), $cpw, $dirname); + } + + /** + * Returns the level of the channel. + * + * @return integer + */ + public function getLevel() + { + return $this->getParent()->channelGetLevel($this->getId()); + } + + /** + * Returns the pathway of the channel which can be used as a clients default channel. + * + * @return string + */ + public function getPathway() + { + return $this->getParent()->channelGetPathway($this->getId()); + } + + /** + * Returns the possible spacer type of the channel. + * + * @return integer + */ + public function spacerGetType() + { + return $this->getParent()->channelSpacerGetType($this->getId()); + } + + /** + * Returns the possible spacer alignment of the channel. + * + * @return integer + */ + public function spacerGetAlign() + { + return $this->getParent()->channelSpacerGetAlign($this->getId()); + } + + /** + * Returns TRUE if the channel is a spacer. + * + * @return boolean + */ + public function isSpacer() + { + return $this->getParent()->channelIsSpacer($this); + } + + /** + * Downloads and returns the channels icon file content. + * + * @return TeamSpeak3_Helper_String + */ + public function iconDownload() + { + if($this->iconIsLocal("channel_icon_id") || $this["channel_icon_id"] == 0) return; + + $download = $this->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->iconGetName("channel_icon_id")); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); + + return $transfer->download($download["ftkey"], $download["size"]); + } + + /** + * Changes the channel configuration using given properties. + * + * @param array $properties + * @return void + */ + public function modify(array $properties) + { + $properties["cid"] = $this->getId(); + + $this->execute("channeledit", $properties); + $this->resetNodeInfo(); + } + + /** + * Sends a text message to all clients in the channel. + * + * @param string $msg + * @param string $cpw + * @return void + */ + public function message($msg, $cpw = null) + { + if($this->getId() != $this->getParent()->whoamiGet("client_channel_id")) + { + $this->getParent()->clientMove($this->getParent()->whoamiGet("client_id"), $this->getId(), $cpw); + } + + $this->execute("sendtextmessage", array("msg" => $msg, "target" => $this->getId(), "targetmode" => TeamSpeak3::TEXTMSG_CHANNEL)); + } + + /** + * Deletes the channel. + * + * @param boolean $force + * @return void + */ + public function delete($force = FALSE) + { + $this->getParent()->channelDelete($this->getId(), $force); + + unset($this); + } + + /** + * Moves the channel to the parent channel specified with $pid. + * + * @param integer $pid + * @param integer $order + * @return void + */ + public function move($pid, $order = null) + { + $this->getParent()->channelMove($this->getId(), $pid, $order); + } + + /** + * Sends a plugin command to all clients in the channel. + * + * @param string $plugin + * @param string $data + * @param string $cpw + * @param boolean $subscribed + * @return void + */ + public function sendPluginCmd($plugin, $data, $cpw = null, $subscribed = FALSE) + { + if($this->getId() != $this->getParent()->whoamiGet("client_channel_id")) + { + $this->getParent()->clientMove($this->getParent()->whoamiGet("client_id"), $this->getId(), $cpw); + } + + $this->execute("plugincmd", array("name" => $plugin, "data" => $data, "targetmode" => $subscribed ? TeamSpeak3::PLUGINCMD_CHANNEL_SUBSCRIBED : TeamSpeak3::PLUGINCMD_CHANNEL)); + } + + /** + * @ignore + */ + protected function fetchNodeList() + { + $this->nodeList = array(); + + if($this->getParent()->getLoadClientlistFirst()) + { + foreach($this->clientList() as $client) + { + if($client["cid"] == $this->getId()) + { + $this->nodeList[] = $client; + } + } + + foreach($this->subChannelList() as $channel) + { + if($channel["pid"] == $this->getId()) + { + $this->nodeList[] = $channel; + } + } + } + else + { + foreach($this->subChannelList() as $channel) + { + if($channel["pid"] == $this->getId()) + { + $this->nodeList[] = $channel; + } + } + + foreach($this->clientList() as $client) + { + if($client["cid"] == $this->getId()) + { + $this->nodeList[] = $client; + } + } + } + } + + /** + * @ignore + */ + protected function fetchNodeInfo() + { + $this->nodeInfo = array_merge($this->nodeInfo, $this->execute("channelinfo", array("cid" => $this->getId()))->toList()); + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + public function getUniqueId() + { + return $this->getParent()->getUniqueId() . "_ch" . $this->getId(); + } + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + public function getIcon() + { + if($this["channel_maxclients"] != -1 && $this["channel_maxclients"] <= $this["total_clients"]) + { + return "channel_full"; + } + elseif($this["channel_flag_password"]) + { + return "channel_pass"; + } + else + { + return "channel_open"; + } + } + + /** + * Returns a symbol representing the node. + * + * @return string + */ + public function getSymbol() + { + return "#"; + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return (string) $this["channel_name"]; + } +} + diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Channelgroup.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Channelgroup.php new file mode 100644 index 0000000..dadc70f --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Channelgroup.php @@ -0,0 +1,276 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Channelgroup + * @brief Class describing a TeamSpeak 3 channel group and all it's parameters. + */ +class TeamSpeak3_Node_Channelgroup extends TeamSpeak3_Node_Abstract +{ + /** + * The TeamSpeak3_Node_Channelgroup constructor. + * + * @param TeamSpeak3_Node_Server $server + * @param array $info + * @param string $index + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channelgroup + */ + public function __construct(TeamSpeak3_Node_Server $server, array $info, $index = "cgid") + { + $this->parent = $server; + $this->nodeInfo = $info; + + if(!array_key_exists($index, $this->nodeInfo)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid groupID", 0xA00); + } + + $this->nodeId = $this->nodeInfo[$index]; + } + + /** + * Renames the channel group specified. + * + * @param string $name + * @return void + */ + public function rename($name) + { + $this->getParent()->channelGroupRename($this->getId(), $name); + } + + /** + * Deletes the channel group. If $force is set to TRUE, the channel group will be + * deleted even if there are clients within. + * + * @param boolean $force + * @return void + */ + public function delete($force = FALSE) + { + $this->getParent()->channelGroupDelete($this->getId(), $force); + + unset($this); + } + + /** + * Creates a copy of the channel group and returns the new groups ID. + * + * @param string $name + * @param integer $tcgid + * @param integer $type + * @return integer + */ + public function copy($name = null, $tcgid = 0, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + return $this->getParent()->channelGroupCopy($this->getId(), $name, $tcgid, $type); + } + + /** + * Returns a list of permissions assigned to the channel group. + * + * @param boolean $permsid + * @return array + */ + public function permList($permsid = FALSE) + { + return $this->getParent()->channelGroupPermList($this->getId(), $permsid); + } + + /** + * Adds a set of specified permissions to the channel group. Multiple permissions + * can be added by providing the two parameters of each permission in separate arrays. + * + * @param integer $permid + * @param integer $permvalue + * @return void + */ + public function permAssign($permid, $permvalue) + { + $this->getParent()->channelGroupPermAssign($this->getId(), $permid, $permvalue); + } + + /** + * Alias for permAssign(). + * + * @deprecated + */ + public function permAssignByName($permname, $permvalue) + { + $this->permAssign($permname, $permvalue); + } + + /** + * Removes a set of specified permissions from the channel group. Multiple + * permissions can be removed at once. + * + * @param integer $permid + * @return void + */ + public function permRemove($permid) + { + $this->getParent()->channelGroupPermRemove($this->getId(), $permid); + } + + /** + * Alias for permAssign(). + * + * @deprecated + */ + public function permRemoveByName($permname) + { + $this->permRemove($permname); + } + + /** + * Returns a list of clients assigned to the server group specified. + * + * @return array + */ + public function clientList() + { + return $this->getParent()->channelGroupClientList($this->getId()); + } + + /** + * Alias for privilegeKeyCreate(). + * + * @deprecated + */ + public function tokenCreate($cid, $description = null, $customset = null) + { + return $this->privilegeKeyCreate($cid, $description, $customset); + } + + /** + * Creates a new privilege key (token) for the channel group and returns the key. + * + * @param integer $cid + * @param string $description + * @param string $customset + * @return TeamSpeak3_Helper_String + */ + public function privilegeKeyCreate($cid, $description = null, $customset = null) + { + return $this->getParent()->privilegeKeyCreate(TeamSpeak3::TOKEN_CHANNELGROUP, $this->getId(), $cid, $description, $customset); + } + + /** + * Sends a text message to all clients residing in the channel group on the virtual server. + * + * @param string $msg + * @return void + */ + public function message($msg) + { + foreach($this as $client) + { + try + { + $this->execute("sendtextmessage", array("msg" => $msg, "target" => $client, "targetmode" => TeamSpeak3::TEXTMSG_CLIENT)); + } + catch(TeamSpeak3_Adapter_ServerQuery_Exception $e) + { + /* ERROR_client_invalid_id */ + if($e->getCode() != 0x0200) throw $e; + } + } + } + + /** + * Downloads and returns the channel groups icon file content. + * + * @return TeamSpeak3_Helper_String + */ + public function iconDownload() + { + if($this->iconIsLocal("iconid") || $this["iconid"] == 0) return; + + $download = $this->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->iconGetName("iconid")); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); + + return $transfer->download($download["ftkey"], $download["size"]); + } + + /** + * @ignore + */ + protected function fetchNodeList() + { + $this->nodeList = array(); + + foreach($this->getParent()->clientList() as $client) + { + if($client["client_channel_group_id"] == $this->getId()) + { + $this->nodeList[] = $client; + } + } + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + public function getUniqueId() + { + return $this->getParent()->getUniqueId() . "_cg" . $this->getId(); + } + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + public function getIcon() + { + return "group_channel"; + } + + /** + * Returns a symbol representing the node. + * + * @return string + */ + public function getSymbol() + { + return "%"; + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return (string) $this["name"]; + } +} + diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Client.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Client.php new file mode 100644 index 0000000..0744267 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Client.php @@ -0,0 +1,441 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Client + * @brief Class describing a TeamSpeak 3 client and all it's parameters. + */ +class TeamSpeak3_Node_Client extends TeamSpeak3_Node_Abstract +{ + /** + * The TeamSpeak3_Node_Client constructor. + * + * @param TeamSpeak3_Node_Server $server + * @param array $info + * @param string $index + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function __construct(TeamSpeak3_Node_Server $server, array $info, $index = "clid") + { + $this->parent = $server; + $this->nodeInfo = $info; + + if(!array_key_exists($index, $this->nodeInfo)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + $this->nodeId = $this->nodeInfo[$index]; + } + + /** + * Changes the clients properties using given properties. + * + * @param array $properties + * @return void + */ + public function modify(array $properties) + { + $properties["clid"] = $this->getId(); + + $this->execute("clientedit", $properties); + $this->resetNodeInfo(); + } + + /** + * Changes the clients properties using given properties. + * + * @param array $properties + * @return void + */ + public function modifyDb(array $properties) + { + $this->getParent()->clientModifyDb($this["client_database_id"], $properties); + } + + /** + * Deletes the clients properties from the database. + * + * @return void + */ + public function deleteDb() + { + $this->getParent()->clientDeleteDb($this["client_database_id"]); + } + + /** + * Returns a list of properties from the database for the client. + * + * @return array + */ + public function infoDb() + { + return $this->getParent()->clientInfoDb($this["client_database_id"]); + } + + /** + * Sends a text message to the client. + * + * @param string $msg + * @return void + */ + public function message($msg) + { + $this->execute("sendtextmessage", array("msg" => $msg, "target" => $this->getId(), "targetmode" => TeamSpeak3::TEXTMSG_CLIENT)); + } + + /** + * Moves the client to another channel. + * + * @param integer $cid + * @param string $cpw + * @return void + */ + public function move($cid, $cpw = null) + { + $this->getParent()->clientMove($this->getId(), $cid, $cpw); + } + + /** + * Kicks the client from his currently joined channel or from the server. + * + * @param integer $reasonid + * @param string $reasonmsg + * @return void + */ + public function kick($reasonid = TeamSpeak3::KICK_CHANNEL, $reasonmsg = null) + { + $this->getParent()->clientKick($this->getId(), $reasonid, $reasonmsg); + } + + /** + * Sends a poke message to the client. + * + * @param string $msg + * @return void + */ + public function poke($msg) + { + $this->getParent()->clientPoke($this->getId(), $msg); + } + + /** + * Bans the client from the server. Please note that this will create two separate + * ban rules for the targeted clients IP address and his unique identifier. + * + * @param integer $timeseconds + * @param string $reason + * @return array + */ + public function ban($timeseconds = null, $reason = null) + { + return $this->getParent()->clientBan($this->getId(), $timeseconds, $reason); + } + + /** + * Returns a list of custom properties for the client. + * + * @return array + */ + public function customInfo() + { + return $this->getParent()->customInfo($this["client_database_id"]); + } + + /** + * Returns an array containing the permission overview of the client. + * + * @param integer $cid + * @return array + */ + public function permOverview($cid) + { + return $this->execute("permoverview", array("cldbid" => $this["client_database_id"], "cid" => $cid, "permid" => 0))->toArray(); + } + + /** + * Returns a list of permissions defined for the client. + * + * @param boolean $permsid + * @return array + */ + public function permList($permsid = FALSE) + { + return $this->getParent()->clientPermList($this["client_database_id"], $permsid); + } + + /** + * Adds a set of specified permissions to the client. Multiple permissions can be added by providing + * the three parameters of each permission. + * + * @param integer $permid + * @param integer $permvalue + * @param integer $permskip + * @return void + */ + public function permAssign($permid, $permvalue, $permskip = FALSE) + { + $this->getParent()->clientPermAssign($this["client_database_id"], $permid, $permvalue, $permskip); + } + + /** + * Alias for permAssign(). + * + * @deprecated + */ + public function permAssignByName($permname, $permvalue, $permskip = FALSE) + { + $this->permAssign($permname, $permvalue, $permskip); + } + + /** + * Removes a set of specified permissions from a client. Multiple permissions can be removed at once. + * + * @param integer $permid + * @return void + */ + public function permRemove($permid) + { + $this->getParent()->clientPermRemove($this["client_database_id"], $permid); + } + + /** + * Alias for permRemove(). + * + * @deprecated + */ + public function permRemoveByName($permname) + { + $this->permRemove($permname); + } + + /** + * Sets the channel group of a client to the ID specified. + * + * @param integer $cid + * @param integer $cgid + * @return void + */ + public function setChannelGroup($cid, $cgid) + { + $this->getParent()->clientSetChannelGroup($this["client_database_id"], $cid, $cgid); + } + + /** + * Adds the client to the server group specified with $sgid. + * + * @param integer $sgid + * @return void + */ + public function addServerGroup($sgid) + { + $this->getParent()->serverGroupClientAdd($sgid, $this["client_database_id"]); + } + + /** + * Removes the client from the server group specified with $sgid. + * + * @param integer $sgid + * @return void + */ + public function remServerGroup($sgid) + { + $this->getParent()->serverGroupClientDel($sgid, $this["client_database_id"]); + } + + /** + * Returns the possible name of the clients avatar. + * + * @return TeamSpeak3_Helper_String + */ + public function avatarGetName() + { + return new TeamSpeak3_Helper_String("/avatar_" . $this["client_base64HashClientUID"]); + } + + /** + * Downloads and returns the clients avatar file content. + * + * @return TeamSpeak3_Helper_String + */ + public function avatarDownload() + { + if($this["client_flag_avatar"] == 0) return; + + $download = $this->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->avatarGetName()); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); + + return $transfer->download($download["ftkey"], $download["size"]); + } + + /** + * Returns a list of client connections using the same identity as this client. + * + * @return array + */ + public function getClones() + { + return $this->execute("clientgetids", array("cluid" => $this["client_unique_identifier"]))->toAssocArray("clid"); + } + + /** + * Returns the revision/build number from the clients version string. + * + * @return integer + */ + public function getRev() + { + return $this["client_type"] ? null : $this["client_version"]->section("[", 1)->filterDigits(); + } + + /** + * Returns all server and channel groups the client is currently residing in. + * + * @return array + */ + public function memberOf() + { + $groups = array($this->getParent()->channelGroupGetById($this["client_channel_group_id"])); + + foreach(explode(",", $this["client_servergroups"]) as $sgid) + { + $groups[] = $this->getParent()->serverGroupGetById($sgid); + } + + return $groups; + } + + /** + * Downloads and returns the clients icon file content. + * + * @return TeamSpeak3_Helper_String + */ + public function iconDownload() + { + if($this->iconIsLocal("client_icon_id") || $this["client_icon_id"] == 0) return; + + $download = $this->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->iconGetName("client_icon_id")); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); + + return $transfer->download($download["ftkey"], $download["size"]); + } + + /** + * Sends a plugin command to the client. + * + * @param string $plugin + * @param string $data + * @return void + */ + public function sendPluginCmd($plugin, $data) + { + $this->execute("plugincmd", array("name" => $plugin, "data" => $data, "targetmode" => TeamSpeak3::PLUGINCMD_CLIENT, "target" => $this->getId())); + } + + /** + * @ignore + */ + protected function fetchNodeInfo() + { + if($this->offsetExists("client_type") && $this["client_type"] == 1) return; + + $this->nodeInfo = array_merge($this->nodeInfo, $this->execute("clientinfo", array("clid" => $this->getId()))->toList()); + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + public function getUniqueId() + { + return $this->getParent()->getUniqueId() . "_cl" . $this->getId(); + } + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + public function getIcon() + { + if($this["client_type"]) + { + return "client_query"; + } + elseif($this["client_away"]) + { + return "client_away"; + } + elseif(!$this["client_output_hardware"]) + { + return "client_snd_disabled"; + } + elseif($this["client_output_muted"]) + { + return "client_snd_muted"; + } + elseif(!$this["client_input_hardware"]) + { + return "client_mic_disabled"; + } + elseif($this["client_input_muted"]) + { + return "client_mic_muted"; + } + elseif($this["client_is_channel_commander"]) + { + return $this["client_flag_talking"] ? "client_cc_talk" : "client_cc_idle"; + } + else + { + return $this["client_flag_talking"] ? "client_talk" : "client_idle"; + } + } + + /** + * Returns a symbol representing the node. + * + * @return string + */ + public function getSymbol() + { + return "@"; + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return (string) $this["client_nickname"]; + } +} + diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Exception.php new file mode 100644 index 0000000..2d1b769 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Exception + * @brief Enhanced exception class for TeamSpeak3_Node_Abstract objects. + */ +class TeamSpeak3_Node_Exception extends TeamSpeak3_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Host.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Host.php new file mode 100644 index 0000000..a6e0913 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Host.php @@ -0,0 +1,1202 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Host + * @brief Class describing a TeamSpeak 3 server instance and all it's parameters. + */ +class TeamSpeak3_Node_Host extends TeamSpeak3_Node_Abstract +{ + /** + * @ignore + */ + protected $whoami = null; + + /** + * @ignore + */ + protected $version = null; + + /** + * @ignore + */ + protected $serverList = null; + + /** + * @ignore + */ + protected $permissionEnds = null; + + /** + * @ignore + */ + protected $permissionList = null; + + /** + * @ignore + */ + protected $permissionCats = null; + + /** + * @ignore + */ + protected $predefined_query_name = null; + + /** + * @ignore + */ + protected $exclude_query_clients = FALSE; + + /** + * @ignore + */ + protected $start_offline_virtual = FALSE; + + /** + * @ignore + */ + protected $sort_clients_channels = FALSE; + + /** + * The TeamSpeak3_Node_Host constructor. + * + * @param TeamSpeak3_Adapter_ServerQuery $squery + * @return TeamSpeak3_Node_Host + */ + public function __construct(TeamSpeak3_Adapter_ServerQuery $squery) + { + $this->parent = $squery; + } + + /** + * Returns the primary ID of the selected virtual server. + * + * @return integer + */ + public function serverSelectedId() + { + return $this->whoamiGet("virtualserver_id", 0); + } + + /** + * Returns the primary UDP port of the selected virtual server. + * + * @return integer + */ + public function serverSelectedPort() + { + return $this->whoamiGet("virtualserver_port", 0); + } + + /** + * Returns the servers version information including platform and build number. + * + * @param string $ident + * @return mixed + */ + public function version($ident = null) + { + if($this->version === null) + { + $this->version = $this->request("version")->toList(); + } + + return ($ident && isset($this->version[$ident])) ? $this->version[$ident] : $this->version; + } + + /** + * Selects a virtual server by ID to allow further interaction. + * + * @param integer $sid + * @param boolean $virtual + * @return void + */ + public function serverSelect($sid, $virtual = null) + { + if($this->whoami !== null && $this->serverSelectedId() == $sid) return; + + $virtual = ($virtual !== null) ? $virtual : $this->start_offline_virtual; + $getargs = func_get_args(); + + $this->execute("use", array("sid" => $sid, $virtual ? "-virtual" : null)); + + if($sid != 0 && $this->predefined_query_name !== null) + { + $this->execute("clientupdate", array("client_nickname" => (string) $this->predefined_query_name)); + } + + $this->whoamiReset(); + + $this->setStorage("_server_use", array(__FUNCTION__, $getargs)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServerselected", $this); + } + + /** + * Alias for serverSelect(). + * + * @param integer $sid + * @param boolean $virtual + * @return void + */ + public function serverSelectById($sid, $virtual = null) + { + $this->serverSelect($sid, $virtual); + } + + /** + * Selects a virtual server by UDP port to allow further interaction. + * + * @param integer $port + * @param boolean $virtual + * @return void + */ + public function serverSelectByPort($port, $virtual = null) + { + if($this->whoami !== null && $this->serverSelectedPort() == $port) return; + + $virtual = ($virtual !== null) ? $virtual : $this->start_offline_virtual; + $getargs = func_get_args(); + + $this->execute("use", array("port" => $port, $virtual ? "-virtual" : null)); + + if($port != 0 && $this->predefined_query_name !== null) + { + $this->execute("clientupdate", array("client_nickname" => (string) $this->predefined_query_name)); + } + + $this->whoamiReset(); + + $this->setStorage("_server_use", array(__FUNCTION__, $getargs)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServerselected", $this); + } + + /** + * Deselects the active virtual server. + * + * @return void + */ + public function serverDeselect() + { + $this->serverSelect(0); + + $this->delStorage("_server_use"); + } + + /** + * Returns the ID of a virtual server matching the given port. + * + * @param integer $port + * @return integer + */ + public function serverIdGetByPort($port) + { + $sid = $this->execute("serveridgetbyport", array("virtualserver_port" => $port))->toList(); + + return $sid["server_id"]; + } + + /** + * Returns the port of a virtual server matching the given ID. + * + * @param integer $sid + * @return integer + */ + public function serverGetPortById($sid) + { + if(!array_key_exists((string) $sid, $this->serverList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid serverID", 0x400); + } + + return $this->serverList[intval((string) $sid)]["virtualserver_port"]; + } + + /** + * Returns the TeamSpeak3_Node_Server object matching the currently selected ID. + * + * @return TeamSpeak3_Node_Server + */ + public function serverGetSelected() + { + return $this->serverGetById($this->serverSelectedId()); + } + + /** + * Returns the TeamSpeak3_Node_Server object matching the given ID. + * + * @param integer $sid + * @return TeamSpeak3_Node_Server + */ + public function serverGetById($sid) + { + $this->serverSelectById($sid); + + return new TeamSpeak3_Node_Server($this, array("virtualserver_id" => intval($sid))); + } + + /** + * Returns the TeamSpeak3_Node_Server object matching the given port number. + * + * @param integer $port + * @return TeamSpeak3_Node_Server + */ + public function serverGetByPort($port) + { + $this->serverSelectByPort($port); + + return new TeamSpeak3_Node_Server($this, array("virtualserver_id" => $this->serverSelectedId())); + } + + /** + * Returns the first TeamSpeak3_Node_Server object matching the given name. + * + * @param string $name + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Server + */ + public function serverGetByName($name) + { + foreach($this->serverList() as $server) + { + if($server["virtualserver_name"] == $name) return $server; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid serverID", 0x400); + } + + /** + * Returns the first TeamSpeak3_Node_Server object matching the given unique identifier. + * + * @param string $uid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Server + */ + public function serverGetByUid($uid) + { + foreach($this->serverList() as $server) + { + if($server["virtualserver_unique_identifier"] == $uid) return $server; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid serverID", 0x400); + } + + /** + * Returns the first TeamSpeak3_Node_Server object matching the given TSDNS hostname. Like the + * TeamSpeak 3 Client, this method will start looking for a TSDNS server on the second-level + * domain including a fallback to the third-level domain of the specified $tsdns parameter. + * + * @param string $tsdns + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Server + */ + public function serverGetByTSDNS($tsdns) + { + $parts = TeamSpeak3_Helper_Uri::getFQDNParts($tsdns); + $query = TeamSpeak3_Helper_String::factory(array_shift($parts)); + + while($part = array_shift($parts)) + { + $query->prepend($part); + + try + { + $port = TeamSpeak3::factory("tsdns://" . $query . "/?timeout=3")->resolve($tsdns)->section(":", 1); + + return $this->serverGetByPort($port == "" ? 9987 : $port); + } + catch(TeamSpeak3_Transport_Exception $e) + { + /* skip "Connection timed out" and "Connection refused" */ + if($e->getCode() != 10060 && $e->getCode() != 10061) throw $e; + } + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid serverID", 0x400); + } + + /** + * Creates a new virtual server using given properties and returns an assoc + * array containing the new ID and initial admin token. + * + * @param array $properties + * @return array + */ + public function serverCreate(array $properties = array()) + { + $this->serverListReset(); + + $detail = $this->execute("servercreate", $properties)->toList(); + $server = new TeamSpeak3_Node_Server($this, array("virtualserver_id" => intval($detail["sid"]))); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServercreated", $this, $detail["sid"]); + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyTokencreated", $server, $detail["token"]); + + return $detail; + } + + /** + * Deletes the virtual server specified by ID. + * + * @param integer $sid + * @return void + */ + public function serverDelete($sid) + { + $this->serverListReset(); + + $this->execute("serverdelete", array("sid" => $sid)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServerdeleted", $this, $sid); + } + + /** + * Starts the virtual server specified by ID. + * + * @param integer $sid + * @return void + */ + public function serverStart($sid) + { + if($sid == $this->serverSelectedId()) + { + $this->serverDeselect(); + } + + $this->execute("serverstart", array("sid" => $sid)); + $this->serverListReset(); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServerstarted", $this, $sid); + } + + /** + * Stops the virtual server specified by ID. + * + * @param integer $sid + * @return void + */ + public function serverStop($sid) + { + if($sid == $this->serverSelectedId()) + { + $this->serverDeselect(); + } + + $this->execute("serverstop", array("sid" => $sid)); + $this->serverListReset(); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServerstopped", $this, $sid); + } + + /** + * Stops the entire TeamSpeak 3 Server instance by shutting down the process. + * + * @return void + */ + public function serverStopProcess() + { + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServershutdown", $this); + + $this->execute("serverprocessstop"); + } + + /** + * Returns an array filled with TeamSpeak3_Node_Server objects. + * + * @param array $filter + * @return array + */ + public function serverList(array $filter = array()) + { + if($this->serverList === null) + { + $servers = $this->request("serverlist -uid")->toAssocArray("virtualserver_id"); + + $this->serverList = array(); + + foreach($servers as $sid => $server) + { + $this->serverList[$sid] = new TeamSpeak3_Node_Server($this, $server); + } + + $this->resetNodeList(); + } + + return $this->filterList($this->serverList, $filter); + } + + /** + * Resets the list of virtual servers. + * + * @return void + */ + public function serverListReset() + { + $this->resetNodeList(); + $this->serverList = null; + } + + /** + * Returns a list of IP addresses used by the server instance on multi-homed machines. + * + * @return array + */ + public function bindingList($subsystem = "voice") + { + return $this->execute("bindinglist", array("subsystem" => $subsystem))->toArray(); + } + + /** + * Returns a list of permissions available on the server instance. + * + * @return array + */ + public function permissionList() + { + if($this->permissionList === null) + { + $this->fetchPermissionList(); + } + + foreach($this->permissionList as $permname => $permdata) + { + if(isset($permdata["permcatid"]) && $permdata["permgrant"]) + { + continue; + } + + $this->permissionList[$permname]["permcatid"] = $this->permissionGetCategoryById($permdata["permid"]); + $this->permissionList[$permname]["permgrant"] = $this->permissionGetGrantById($permdata["permid"]); + + $grantsid = "i_needed_modify_power_" . substr($permname, 2); + + if(!$permdata["permname"]->startsWith("i_needed_modify_power_") && !isset($this->permissionList[$grantsid])) + { + $this->permissionList[$grantsid]["permid"] = $this->permissionList[$permname]["permgrant"]; + $this->permissionList[$grantsid]["permname"] = TeamSpeak3_Helper_String::factory($grantsid); + $this->permissionList[$grantsid]["permdesc"] = null; + $this->permissionList[$grantsid]["permcatid"] = 0xFF; + $this->permissionList[$grantsid]["permgrant"] = $this->permissionList[$permname]["permgrant"]; + } + } + + return $this->permissionList; + } + + /** + * Returns a list of permission categories available on the server instance. + * + * @return array + */ + public function permissionCats() + { + if($this->permissionCats === null) + { + $this->fetchPermissionCats(); + } + + return $this->permissionCats; + } + + /** + * Returns a list of permission category endings available on the server instance. + * + * @return array + */ + public function permissionEnds() + { + if($this->permissionEnds === null) + { + $this->fetchPermissionList(); + } + + return $this->permissionCats; + } + + /** + * Returns an array filled with all permission categories known to the server including + * their ID, name and parent. + * + * @return array + */ + public function permissionTree() + { + $permtree = array(); + + foreach($this->permissionCats() as $key => $val) + { + $permtree[$val]["permcatid"] = $val; + $permtree[$val]["permcathex"] = "0x" . dechex($val); + $permtree[$val]["permcatname"] = TeamSpeak3_Helper_String::factory(TeamSpeak3_Helper_Convert::permissionCategory($val)); + $permtree[$val]["permcatparent"] = $permtree[$val]["permcathex"]{3} == 0 ? 0 : hexdec($permtree[$val]["permcathex"]{2} . 0); + $permtree[$val]["permcatchilren"] = 0; + $permtree[$val]["permcatcount"] = 0; + + if(isset($permtree[$permtree[$val]["permcatparent"]])) + { + $permtree[$permtree[$val]["permcatparent"]]["permcatchilren"]++; + } + + if($permtree[$val]["permcatname"]->contains("/")) + { + $permtree[$val]["permcatname"] = $permtree[$val]["permcatname"]->section("/", 1)->trim(); + } + + foreach($this->permissionList() as $permission) + { + if($permission["permid"]["permcatid"] == $val) + { + $permtree[$val]["permcatcount"]++; + } + } + } + + return $permtree; + } + + /** + * Returns the IDs of all clients, channels or groups using the permission with the + * specified ID. + * + * @param integer $permid + * @return array + */ + public function permissionFind($permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + return $this->execute("permfind", array($permident => $permid))->toArray(); + } + + /** + * Returns the ID of the permission matching the given name. + * + * @param string $name + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return integer + */ + public function permissionGetIdByName($name) + { + if(!array_key_exists((string) $name, $this->permissionList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid permission ID", 0xA02); + } + + return $this->permissionList[(string) $name]["permid"]; + } + + /** + * Returns the name of the permission matching the given ID. + * + * @param integer $permid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Helper_String + */ + public function permissionGetNameById($permid) + { + foreach($this->permissionList() as $name => $perm) + { + if($perm["permid"] == $permid) return new TeamSpeak3_Helper_String($name); + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid permission ID", 0xA02); + } + + /** + * Returns the internal category of the permission matching the given ID. + * + * All pre-3.0.7 permission IDs are are 2 bytes wide. The first byte identifies the category while + * the second byte is the permission count within that group. + * + * @param integer $permid + * @return integer + */ + public function permissionGetCategoryById($permid) + { + if(!is_numeric($permid)) + { + $permid = $this->permissionGetIdByName($permid); + } + + if($permid < 0x1000) + { + if($this->permissionEnds === null) + { + $this->fetchPermissionList(); + } + + if($this->permissionCats === null) + { + $this->fetchPermissionCats(); + } + + $catids = array_values($this->permissionCats()); + + foreach($this->permissionEnds as $key => $val) + { + if($val >= $permid && isset($catids[$key])) + { + return $catids[$key]; + } + } + + return 0; + } + else + { + return (int) $permid >> 8; + } + } + + /** + * Returns the internal ID of the i_needed_modify_power_* or grant permission. + * + * Every permission has an associated i_needed_modify_power_* permission, for example b_client_ban_create has an + * associated permission called i_needed_modify_power_client_ban_create. + * + * @param integer $permid + * @return integer + */ + public function permissionGetGrantById($permid) + { + if(!is_numeric($permid)) + { + $permid = $this->permissionGetIdByName($permid); + } + + if($permid < 0x1000) + { + return (int) $permid+0x8000; + } + else + { + return (int) bindec(substr(decbin($permid), -8))+0xFF00; + } + } + + /** + * Adds a set of specified permissions to all regular server groups on all virtual servers. The target groups will + * be identified by the value of their i_group_auto_update_type permission specified with $sgtype. + * + * @param integer $sgtype + * @param integer $permid + * @param integer $permvalue + * @param integer $permnegated + * @param integer $permskip + * @return void + */ + public function serverGroupPermAutoAssign($sgtype, $permid, $permvalue, $permnegated = FALSE, $permskip = FALSE) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("servergroupautoaddperm", array("sgtype" => $sgtype, $permident => $permid, "permvalue" => $permvalue, "permnegated" => $permnegated, "permskip" => $permskip)); + } + + /** + * Removes a set of specified permissions from all regular server groups on all virtual servers. The target groups + * will be identified by the value of their i_group_auto_update_type permission specified with $sgtype. + * + * @param integer $sgtype + * @param integer $permid + * @return void + */ + public function serverGroupPermAutoRemove($sgtype, $permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("servergroupautodelperm", array("sgtype" => $sgtype, $permident => $permid)); + } + + /** + * Returns an array containing the value of a specified permission for your own client. + * + * @param integer $permid + * @return array + */ + public function selfPermCheck($permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + return $this->execute("permget", array($permident => $permid))->toAssocArray("permsid"); + } + + /** + * Changes the server instance configuration using given properties. + * + * @param array $properties + * @return void + */ + public function modify(array $properties) + { + $this->execute("instanceedit", $properties); + $this->resetNodeInfo(); + } + + /** + * Sends a text message to all clients on all virtual servers in the TeamSpeak 3 Server instance. + * + * @param string $msg + * @return void + */ + public function message($msg) + { + $this->execute("gm", array("msg" => $msg)); + } + + /** + * Displays a specified number of entries (1-100) from the servers log. + * + * @param integer $lines + * @param integer $begin_pos + * @param boolean $reverse + * @param boolean $instance + * @return array + */ + public function logView($lines = 30, $begin_pos = null, $reverse = null, $instance = TRUE) + { + return $this->execute("logview", array("lines" => $lines, "begin_pos" => $begin_pos, "instance" => $instance, "reverse" => $reverse))->toArray(); + } + + /** + * Writes a custom entry into the server instance log. + * + * @param string $logmsg + * @param integer $loglevel + * @return void + */ + public function logAdd($logmsg, $loglevel = TeamSpeak3::LOGLEVEL_INFO) + { + $sid = $this->serverSelectedId(); + + $this->serverDeselect(); + $this->execute("logadd", array("logmsg" => $logmsg, "loglevel" => $loglevel)); + $this->serverSelect($sid); + } + + /** + * Authenticates with the TeamSpeak 3 Server instance using given ServerQuery login credentials. + * + * @param string $username + * @param string $password + * @return void + */ + public function login($username, $password) + { + $this->execute("login", array("client_login_name" => $username, "client_login_password" => $password)); + $this->whoamiReset(); + + $crypt = new TeamSpeak3_Helper_Crypt($username); + + $this->setStorage("_login_user", $username); + $this->setStorage("_login_pass", $crypt->encrypt($password)); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyLogin", $this); + } + + /** + * Deselects the active virtual server and logs out from the server instance. + * + * @return void + */ + public function logout() + { + $this->request("logout"); + $this->whoamiReset(); + + $this->delStorage("_login_user"); + $this->delStorage("_login_pass"); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyLogout", $this); + } + + /** + * Returns information about your current ServerQuery connection. + * + * @return array + */ + public function whoami() + { + if($this->whoami === null) + { + $this->whoami = $this->request("whoami")->toList(); + } + + return $this->whoami; + } + + /** + * Returns a single value from the current ServerQuery connection info. + * + * @param string $ident + * @param mixed $default + * @return mixed + */ + public function whoamiGet($ident, $default = null) + { + if(array_key_exists($ident, $this->whoami())) + { + return $this->whoami[$ident]; + } + + return $default; + } + + /** + * Sets a single value in the current ServerQuery connection info. + * + * @param string $ident + * @param mixed $value + * @return mixed + */ + public function whoamiSet($ident, $value = null) + { + $this->whoami(); + + $this->whoami[$ident] = (is_numeric($value)) ? (int) $value : TeamSpeak3_Helper_String::factory($value); + } + + /** + * Resets the current ServerQuery connection info. + * + * @return void + */ + public function whoamiReset() + { + $this->whoami = null; + } + + /** + * Returns the hostname or IPv4 address the adapter is connected to. + * + * @return string + */ + public function getAdapterHost() + { + return $this->getParent()->getTransportHost(); + } + + /** + * Returns the network port the adapter is connected to. + * + * @return string + */ + public function getAdapterPort() + { + return $this->getParent()->getTransportPort(); + } + + /** + * @ignore + */ + protected function fetchNodeList() + { + $servers = $this->serverList(); + + foreach($servers as $server) + { + $this->nodeList[] = $server; + } + } + + /** + * @ignore + */ + protected function fetchNodeInfo() + { + $info1 = $this->request("hostinfo")->toList(); + $info2 = $this->request("instanceinfo")->toList(); + + $this->nodeInfo = array_merge($this->nodeInfo, $info1, $info2); + } + + /** + * @ignore + */ + protected function fetchPermissionList() + { + $reply = $this->request("permissionlist -new")->toArray(); + $start = 1; + + $this->permissionEnds = array(); + $this->permissionList = array(); + + foreach($reply as $line) + { + if(array_key_exists("group_id_end", $line)) + { + $this->permissionEnds[] = $line["group_id_end"]; + } + else + { + $this->permissionList[$line["permname"]->toString()] = array_merge(array("permid" => $start++), $line); + } + } + } + + /** + * @ignore + */ + protected function fetchPermissionCats() + { + $permcats = array(); + $reflects = new ReflectionClass("TeamSpeak3"); + + foreach($reflects->getConstants() as $key => $val) + { + if(!TeamSpeak3_Helper_String::factory($key)->startsWith("PERM_CAT") || $val == 0xFF) + { + continue; + } + + $permcats[$key] = $val; + } + + $this->permissionCats = $permcats; + } + + /** + * Sets a pre-defined nickname for ServerQuery clients which will be used automatically + * after selecting a virtual server. + * + * @param string $name + * @return void + */ + public function setPredefinedQueryName($name = null) + { + $this->setStorage("_query_nick", $name); + + $this->predefined_query_name = $name; + } + + /** + * Returns the pre-defined nickname for ServerQuery clients which will be used automatically + * after selecting a virtual server. + * + * @return string + */ + public function getPredefinedQueryName() + { + return $this->predefined_query_name; + } + + /** + * Sets the option to decide whether ServerQuery clients should be excluded from node + * lists or not. + * + * @param boolean $exclude + * @return void + */ + public function setExcludeQueryClients($exclude = FALSE) + { + $this->setStorage("_query_hide", $exclude); + + $this->exclude_query_clients = $exclude; + } + + /** + * Returns the option to decide whether ServerQuery clients should be excluded from node + * lists or not. + * + * @return boolean + */ + public function getExcludeQueryClients() + { + return $this->exclude_query_clients; + } + + /** + * Sets the option to decide whether offline servers will be started in virtual mode + * by default or not. + * + * @param boolean $virtual + * @return void + */ + public function setUseOfflineAsVirtual($virtual = FALSE) + { + $this->setStorage("_do_virtual", $virtual); + + $this->start_offline_virtual = $virtual; + } + + /** + * Returns the option to decide whether offline servers will be started in virtual mode + * by default or not. + * + * @return boolean + */ + public function getUseOfflineAsVirtual() + { + return $this->start_offline_virtual; + } + + /** + * Sets the option to decide whether clients should be sorted before sub-channels to support + * the new TeamSpeak 3 Client display mode or not. + * + * @param boolean $first + * @return void + */ + public function setLoadClientlistFirst($first = FALSE) + { + $this->setStorage("_client_top", $first); + + $this->sort_clients_channels = $first; + } + + /** + * Returns the option to decide whether offline servers will be started in virtual mode + * by default or not. + * + * @return boolean + */ + public function getLoadClientlistFirst() + { + return $this->sort_clients_channels; + } + + /** + * Returns the underlying TeamSpeak3_Adapter_ServerQuery object. + * + * @return TeamSpeak3_Adapter_ServerQuery + */ + public function getAdapter() + { + return $this->getParent(); + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + public function getUniqueId() + { + return "ts3_h"; + } + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + public function getIcon() + { + return "host"; + } + + /** + * Returns a symbol representing the node. + * + * @return string + */ + public function getSymbol() + { + return "+"; + } + + /** + * Re-authenticates with the TeamSpeak 3 Server instance using given ServerQuery login + * credentials and re-selects a previously selected virtual server. + * + * @return void + */ + public function __wakeup() + { + $username = $this->getStorage("_login_user"); + $password = $this->getStorage("_login_pass"); + + if($username && $password) + { + $crypt = new TeamSpeak3_Helper_Crypt($username); + + $this->login($username, $crypt->decrypt($password)); + } + + $this->predefined_query_name = $this->getStorage("_query_nick"); + $this->exclude_query_clients = $this->getStorage("_query_hide", FALSE); + $this->start_offline_virtual = $this->getStorage("_do_virtual", FALSE); + $this->sort_clients_channels = $this->getStorage("_client_top", FALSE); + + if($server = $this->getStorage("_server_use")) + { + $func = array_shift($server); + $args = array_shift($server); + + try + { + call_user_func_array(array($this, $func), $args); + } + catch(Exception $e) + { + $class = get_class($e); + + throw new $class($e->getMessage(), $e->getCode()); + } + } + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return (string) $this->getAdapterHost(); + } +} + diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Server.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Server.php new file mode 100644 index 0000000..0d15926 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Server.php @@ -0,0 +1,2536 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Server + * @brief Class describing a TeamSpeak 3 virtual server and all it's parameters. + */ +class TeamSpeak3_Node_Server extends TeamSpeak3_Node_Abstract +{ + /** + * @ignore + */ + protected $channelList = null; + + /** + * @ignore + */ + protected $clientList = null; + + /** + * @ignore + */ + protected $sgroupList = null; + + /** + * @ignore + */ + protected $cgroupList = null; + + /** + * The TeamSpeak3_Node_Server constructor. + * + * @param TeamSpeak3_Node_Host $host + * @param array $info + * @param string $index + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Server + */ + public function __construct(TeamSpeak3_Node_Host $host, array $info, $index = "virtualserver_id") + { + $this->parent = $host; + $this->nodeInfo = $info; + + if(!array_key_exists($index, $this->nodeInfo)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid serverID", 0x400); + } + + $this->nodeId = $this->nodeInfo[$index]; + } + + /** + * Sends a prepared command to the server and returns the result. + * + * @param string $cmd + * @param boolean $throw + * @return TeamSpeak3_Adapter_ServerQuery_Reply + */ + public function request($cmd, $throw = TRUE) + { + if($this->getId() != $this->getParent()->serverSelectedId()) + { + $this->getParent()->serverSelect($this->getId()); + } + + return $this->getParent()->request($cmd, $throw); + } + + /** + * Returns an array filled with TeamSpeak3_Node_Channel objects. + * + * @param array $filter + * @return array + */ + public function channelList(array $filter = array()) + { + if($this->channelList === null) + { + $channels = $this->request("channellist -topic -flags -voice -limits -icon")->toAssocArray("cid"); + + $this->channelList = array(); + + foreach($channels as $cid => $channel) + { + $this->channelList[$cid] = new TeamSpeak3_Node_Channel($this, $channel); + } + + $this->resetNodeList(); + } + + return $this->filterList($this->channelList, $filter); + } + + /** + * Resets the list of channels online. + * + * @return void + */ + public function channelListReset() + { + $this->resetNodeList(); + $this->channelList = null; + } + + /** + * Creates a new channel using given properties and returns the new ID. + * + * @param array $properties + * @return integer + */ + public function channelCreate(array $properties) + { + $cid = $this->execute("channelcreate", $properties)->toList(); + $this->channelListReset(); + + if(!isset($properties["channel_flag_permanent"]) && !isset($properties["channel_flag_semi_permanent"])) + { + $this->getParent()->whoamiSet("client_channel_id", $cid["cid"]); + } + + return $cid["cid"]; + } + + /** + * Deletes the channel specified by $cid. + * + * @param integer $cid + * @param boolean $force + * @return void + */ + public function channelDelete($cid, $force = FALSE) + { + $this->execute("channeldelete", array("cid" => $cid, "force" => $force)); + $this->channelListReset(); + + if(($cid instanceof TeamSpeak3_Node_Abstract ? $cid->getId() : $cid) == $this->whoamiGet("client_channel_id")) + { + $this->getParent()->whoamiReset(); + } + } + + /** + * Moves the channel specified by $cid to the parent channel specified with $pid. + * + * @param integer $cid + * @param integer $pid + * @param integer $order + * @return void + */ + public function channelMove($cid, $pid, $order = null) + { + $this->execute("channelmove", array("cid" => $cid, "cpid" => $pid, "order" => $order)); + $this->channelListReset(); + } + + /** + * Returns TRUE if the given TeamSpeak3_Node_Channel object is a spacer. + * + * @param TeamSpeak3_Node_Channel $channel + * @return boolean + */ + public function channelIsSpacer(TeamSpeak3_Node_Channel $channel) + { + return (preg_match("/\[[^\]]*spacer[^\]]*\]/", $channel) && $channel["channel_flag_permanent"] && !$channel["pid"]) ? TRUE : FALSE; + } + + /** + * Creates a new channel spacer and returns the new ID. The first parameter $ident is used to create a + * unique spacer name on the virtual server. + * + * @param string $ident + * @param mixed $type + * @param integer $align + * @param integer $order + * @param integer $maxclients + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return integer + */ + public function channelSpacerCreate($ident, $type = TeamSpeak3::SPACER_SOLIDLINE, $align = TeamSpeak3::SPACER_ALIGN_REPEAT, $order = null, $maxclients = 0) + { + $properties = array( + "channel_name_phonetic" => "channel spacer", + "channel_codec" => TeamSpeak3::CODEC_OPUS_VOICE, + "channel_codec_quality" => 0x00, + "channel_flag_permanent" => TRUE, + "channel_flag_maxclients_unlimited" => FALSE, + "channel_flag_maxfamilyclients_unlimited" => FALSE, + "channel_flag_maxfamilyclients_inherited" => FALSE, + "channel_maxclients" => $maxclients, + "channel_order" => $order, + ); + + switch($align) + { + case TeamSpeak3::SPACER_ALIGN_REPEAT: + $properties["channel_name"] = "[*spacer" . strval($ident) . "]"; + break; + + case TeamSpeak3::SPACER_ALIGN_LEFT: + $properties["channel_name"] = "[lspacer" . strval($ident) . "]"; + break; + + case TeamSpeak3::SPACER_ALIGN_RIGHT: + $properties["channel_name"] = "[rspacer" . strval($ident) . "]"; + break; + + case TeamSpeak3::SPACER_ALIGN_CENTER: + $properties["channel_name"] = "[cspacer" . strval($ident) . "]"; + break; + + default: + throw new TeamSpeak3_Adapter_ServerQuery_Exception("missing required parameter", 0x606); + break; + } + + switch($type) + { + case (string) TeamSpeak3::SPACER_SOLIDLINE: + $properties["channel_name"] .= "___"; + break; + + case (string) TeamSpeak3::SPACER_DASHLINE: + $properties["channel_name"] .= "---"; + break; + + case (string) TeamSpeak3::SPACER_DOTLINE: + $properties["channel_name"] .= "..."; + break; + + case (string) TeamSpeak3::SPACER_DASHDOTLINE: + $properties["channel_name"] .= "-.-"; + break; + + case (string) TeamSpeak3::SPACER_DASHDOTDOTLINE: + $properties["channel_name"] .= "-.."; + break; + + default: + $properties["channel_name"] .= strval($type); + break; + } + + return $this->channelCreate($properties); + } + + /** + * Returns the possible type of a channel spacer. + * + * @param integer $cid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return integer + */ + public function channelSpacerGetType($cid) + { + $channel = $this->channelGetById($cid); + + if(!$this->channelIsSpacer($channel)) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channel flags", 0x307); + } + + switch($channel["channel_name"]->section("]", 1)) + { + case "___": + return TeamSpeak3::SPACER_SOLIDLINE; + + case "---": + return TeamSpeak3::SPACER_DASHLINE; + + case "...": + return TeamSpeak3::SPACER_DOTLINE; + + case "-.-": + return TeamSpeak3::SPACER_DASHDOTLINE; + + case "-..": + return TeamSpeak3::SPACER_DASHDOTDOTLINE; + + default: + return TeamSpeak3::SPACER_CUSTOM; + } + } + + /** + * Returns the possible alignment of a channel spacer. + * + * @param integer $cid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return integer + */ + public function channelSpacerGetAlign($cid) + { + $channel = $this->channelGetById($cid); + + if(!$this->channelIsSpacer($channel) || !preg_match("/\[(.*)spacer.*\]/", $channel, $matches) || !isset($matches[1])) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channel flags", 0x307); + } + + switch($matches[1]) + { + case "*": + return TeamSpeak3::SPACER_ALIGN_REPEAT; + + case "c": + return TeamSpeak3::SPACER_ALIGN_CENTER; + + case "r": + return TeamSpeak3::SPACER_ALIGN_RIGHT; + + default: + return TeamSpeak3::SPACER_ALIGN_LEFT; + } + } + + /** + * Returns a list of permissions defined for a specific channel. + * + * @param integer $cid + * @param boolean $permsid + * @return array + */ + public function channelPermList($cid, $permsid = FALSE) + { + return $this->execute("channelpermlist", array("cid" => $cid, $permsid ? "-permsid" : null))->toAssocArray($permsid ? "permsid" : "permid"); + } + + /** + * Adds a set of specified permissions to a channel. Multiple permissions can be added by + * providing the two parameters of each permission. + * + * @param integer $cid + * @param integer $permid + * @param integer $permvalue + * @return void + */ + public function channelPermAssign($cid, $permid, $permvalue) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("channeladdperm", array("cid" => $cid, $permident => $permid, "permvalue" => $permvalue)); + } + + /** + * Removes a set of specified permissions from a channel. Multiple permissions can be removed at once. + * + * @param integer $cid + * @param integer $permid + * @return void + */ + public function channelPermRemove($cid, $permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("channeldelperm", array("cid" => $cid, $permident => $permid)); + } + + /** + * Returns a list of permissions defined for a client in a specific channel. + * + * @param integer $cid + * @param integer $cldbid + * @param boolean $permsid + * @return array + */ + public function channelClientPermList($cid, $cldbid, $permsid = FALSE) + { + return $this->execute("channelclientpermlist", array("cid" => $cid, "cldbid" => $cldbid, $permsid ? "-permsid" : null))->toAssocArray($permsid ? "permsid" : "permid"); + } + + /** + * Adds a set of specified permissions to a client in a specific channel. Multiple permissions can be added by + * providing the two parameters of each permission. + * + * @param integer $cid + * @param integer $cldbid + * @param integer $permid + * @param integer $permvalue + * @return void + */ + public function channelClientPermAssign($cid, $cldbid, $permid, $permvalue) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("channelclientaddperm", array("cid" => $cid, "cldbid" => $cldbid, $permident => $permid, "permvalue" => $permvalue)); + } + + /** + * Removes a set of specified permissions from a client in a specific channel. Multiple permissions can be removed at once. + * + * @param integer $cid + * @param integer $cldbid + * @param integer $permid + * @return void + */ + public function channelClientPermRemove($cid, $cldbid, $permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("channelclientdelperm", array("cid" => $cid, "cldbid" => $cldbid, $permident => $permid)); + } + + /** + * Returns a list of files and directories stored in the specified channels file repository. + * + * @param integer $cid + * @param string $cpw + * @param string $path + * @param boolean $recursive + * @return array + */ + public function channelFileList($cid, $cpw = "", $path = "/", $recursive = FALSE) + { + $files = $this->execute("ftgetfilelist", array("cid" => $cid, "cpw" => $cpw, "path" => $path))->toArray(); + $count = count($files); + + for($i = 0; $i < $count; $i++) + { + $files[$i]["sid"] = $this->getId(); + $files[$i]["cid"] = $files[0]["cid"]; + $files[$i]["path"] = $files[0]["path"]; + $files[$i]["src"] = new TeamSpeak3_Helper_String($cid ? $files[$i]["path"] : "/"); + + if(!$files[$i]["src"]->endsWith("/")) + { + $files[$i]["src"]->append("/"); + } + + $files[$i]["src"]->append($files[$i]["name"]); + + if($recursive && $files[$i]["type"] == TeamSpeak3::FILE_TYPE_DIRECTORY) + { + $files = array_merge($files, $this->channelFileList($cid, $cpw, $path . $files[$i]["name"], $recursive)); + } + } + + uasort($files, array(__CLASS__, "sortFileList")); + + return $files; + } + + /** + * Returns detailed information about the specified file stored in a channels file repository. + * + * @param integer $cid + * @param string $cpw + * @param string $name + * @return array + */ + public function channelFileInfo($cid, $cpw = "", $name = "/") + { + return array_pop($this->execute("ftgetfileinfo", array("cid" => $cid, "cpw" => $cpw, "name" => $name))->toArray()); + } + + /** + * Renames a file in a channels file repository. If the two parameters $tcid and $tcpw are specified, the file + * will be moved into another channels file repository. + * + * @param integer $cid + * @param string $cpw + * @param string $oldname + * @param string $newname + * @param integer $tcid + * @param string $tcpw + * @return void + */ + public function channelFileRename($cid, $cpw = "", $oldname = "/", $newname = "/", $tcid = null, $tcpw = null) + { + $this->execute("ftrenamefile", array("cid" => $cid, "cpw" => $cpw, "oldname" => $oldname, "newname" => $newname, "tcid" => $tcid, "tcpw" => $tcpw)); + } + + /** + * Deletes one or more files stored in a channels file repository. + * + * @param integer $cid + * @param string $cpw + * @param string $name + * @return void + */ + public function channelFileDelete($cid, $cpw = "", $name = "/") + { + $this->execute("ftdeletefile", array("cid" => $cid, "cpw" => $cpw, "name" => $name)); + } + + /** + * Creates new directory in a channels file repository. + * + * @param integer $cid + * @param string $cpw + * @param string $dirname + * @return void + */ + public function channelDirCreate($cid, $cpw = "", $dirname = "/") + { + $this->execute("ftcreatedir", array("cid" => $cid, "cpw" => $cpw, "dirname" => $dirname)); + } + + /** + * Returns the level of a channel. + * + * @param integer $cid + * @return integer + */ + public function channelGetLevel($cid) + { + $channel = $this->channelGetById($cid); + $levelno = 0; + + if($channel["pid"]) + { + $levelno = $this->channelGetLevel($channel["pid"])+1; + } + + return $levelno; + } + + /** + * Returns the pathway of a channel which can be used as a clients default channel. + * + * @param integer $cid + * @return string + */ + public function channelGetPathway($cid) + { + $channel = $this->channelGetById($cid); + $pathway = $channel["channel_name"]; + + if($channel["pid"]) + { + $pathway = $this->channelGetPathway($channel["pid"]) . "/" . $channel["channel_name"]; + } + + return $pathway; + } + + /** + * Returns the TeamSpeak3_Node_Channel object matching the given ID. + * + * @param integer $cid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channel + */ + public function channelGetById($cid) + { + if(!array_key_exists((string) $cid, $this->channelList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channelID", 0x300); + } + + return $this->channelList[intval((string) $cid)]; + } + + /** + * Returns the TeamSpeak3_Node_Channel object matching the given name. + * + * @param string $name + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channel + */ + public function channelGetByName($name) + { + foreach($this->channelList() as $channel) + { + if($channel["channel_name"] == $name) return $channel; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid channelID", 0x300); + } + + /** + * Returns an array filled with TeamSpeak3_Node_Client objects. + * + * @param array $filter + * @return array + */ + public function clientList(array $filter = array()) + { + if($this->clientList === null) + { + $clients = $this->request("clientlist -uid -away -badges -voice -info -times -groups -icon -country -ip")->toAssocArray("clid"); + + $this->clientList = array(); + + foreach($clients as $clid => $client) + { + if($this->getParent()->getExcludeQueryClients() && $client["client_type"]) continue; + + $this->clientList[$clid] = new TeamSpeak3_Node_Client($this, $client); + } + + uasort($this->clientList, array(__CLASS__, "sortClientList")); + + $this->resetNodeList(); + } + + return $this->filterList($this->clientList, $filter); + } + + /** + * Resets the list of clients online. + * + * @return void + */ + public function clientListReset() + { + $this->resetNodeList(); + $this->clientList = null; + } + + /** + * Returns a list of clients matching a given name pattern. + * + * @param string $pattern + * @return array + */ + public function clientFind($pattern) + { + return $this->execute("clientfind", array("pattern" => $pattern))->toAssocArray("clid"); + } + + /** + * Returns a list of client identities known by the virtual server. By default, the server spits out 25 entries + * at once. + * + * @param integer $offset + * @param integer $limit + * @return array + */ + public function clientListDb($offset = null, $limit = null) + { + return $this->execute("clientdblist -count", array("start" => $offset, "duration" => $limit))->toAssocArray("cldbid"); + } + + /** + * Returns the number of client identities known by the virtual server. + * + * @return integer + */ + public function clientCountDb() + { + return current($this->execute("clientdblist -count", array("duration" => 1))->toList("count")); + } + + /** + * Returns a list of properties from the database for the client specified by $cldbid. + * + * @param integer $cldbid + * @return array + */ + public function clientInfoDb($cldbid) + { + return $this->execute("clientdbinfo", array("cldbid" => $cldbid))->toList(); + } + + /** + * Returns a list of client database IDs matching a given pattern. You can either search for a clients + * last known nickname or his unique identity by using the $uid option. + * + * @param string $pattern + * @param boolean $uid + * @return array + */ + public function clientFindDb($pattern, $uid = FALSE) + { + return array_keys($this->execute("clientdbfind", array("pattern" => $pattern, ($uid) ? "-uid" : null))->toAssocArray("cldbid")); + } + + /** + * Returns the number of regular clients online. + * + * @return integer + */ + public function clientCount() + { + if($this->isOffline()) return 0; + + return $this["virtualserver_clientsonline"]-$this["virtualserver_queryclientsonline"]; + } + + /** + * Returns the TeamSpeak3_Node_Client object matching the given ID. + * + * @param integer $clid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function clientGetById($clid) + { + if(!array_key_exists((string) $clid, $this->clientList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + return $this->clientList[intval((string) $clid)]; + } + + /** + * Returns the TeamSpeak3_Node_Client object matching the given name. + * + * @param string $name + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function clientGetByName($name) + { + foreach($this->clientList() as $client) + { + if($client["client_nickname"] == $name) return $client; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + /** + * Returns the TeamSpeak3_Node_Client object matching the given unique identifier. + * + * @param string $uid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function clientGetByUid($uid) + { + foreach($this->clientList() as $client) + { + if($client["client_unique_identifier"] == $uid) return $client; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + /** + * Returns the TeamSpeak3_Node_Client object matching the given database ID. + * + * @param integer $dbid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Client + */ + public function clientGetByDbid($dbid) + { + foreach($this->clientList() as $client) + { + if($client["client_database_id"] == $dbid) return $client; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid clientID", 0x200); + } + + /** + * Returns an array containing the last known nickname and the database ID of the client matching + * the unique identifier specified with $cluid. + * + * @param string $cluid + * @return array + */ + public function clientGetNameByUid($cluid) + { + return $this->execute("clientgetnamefromuid", array("cluid" => $cluid))->toList(); + } + + /** + * Returns an array containing a list of active client connections using the unique identifier + * specified with $cluid. + * + * @param string $cluid + * @return array + */ + public function clientGetIdsByUid($cluid) + { + return $this->execute("clientgetids", array("cluid" => $cluid))->toAssocArray("clid"); + } + + /** + * Returns an array containing the last known nickname and the unique identifier of the client + * matching the database ID specified with $cldbid. + * + * @param string $cldbid + * @return array + */ + public function clientGetNameByDbid($cldbid) + { + return $this->execute("clientgetnamefromdbid", array("cldbid" => $cldbid))->toList(); + } + + /** + * Returns an array containing the names and IDs of all server groups the client specified with + * $cldbid is is currently residing in. + * + * @param string $cldbid + * @return array + */ + public function clientGetServerGroupsByDbid($cldbid) + { + return $this->execute("servergroupsbyclientid", array("cldbid" => $cldbid))->toAssocArray("sgid"); + } + + /** + * Moves a client to another channel. + * + * @param integer $clid + * @param integer $cid + * @param string $cpw + * @return void + */ + public function clientMove($clid, $cid, $cpw = null) + { + $this->clientListReset(); + + $this->execute("clientmove", array("clid" => $clid, "cid" => $cid, "cpw" => $cpw)); + + if($clid instanceof TeamSpeak3_Node_Abstract) + { + $clid = $clid->getId(); + } + + if($cid instanceof TeamSpeak3_Node_Abstract) + { + $cid = $cid->getId(); + } + + if(!is_array($clid) && $clid == $this->whoamiGet("client_id")) + { + $this->getParent()->whoamiSet("client_channel_id", $cid); + } + } + + /** + * Kicks one or more clients from their currently joined channel or from the server. + * + * @param integer $clid + * @param integer $reasonid + * @param string $reasonmsg + * @return void + */ + public function clientKick($clid, $reasonid = TeamSpeak3::KICK_CHANNEL, $reasonmsg = null) + { + $this->clientListReset(); + + $this->execute("clientkick", array("clid" => $clid, "reasonid" => $reasonid, "reasonmsg" => $reasonmsg)); + } + + /** + * Sends a poke message to a client. + * + * @param integer $clid + * @param string $msg + * @return void + */ + public function clientPoke($clid, $msg) + { + $this->execute("clientpoke", array("clid" => $clid, "msg" => $msg)); + } + + /** + * Bans the client specified with ID $clid from the server. Please note that this will create two separate + * ban rules for the targeted clients IP address and his unique identifier. + * + * @param integer $clid + * @param integer $timeseconds + * @param string $reason + * @return array + */ + public function clientBan($clid, $timeseconds = null, $reason = null) + { + $this->clientListReset(); + + $bans = $this->execute("banclient", array("clid" => $clid, "time" => $timeseconds, "banreason" => $reason))->toAssocArray("banid"); + + return array_keys($bans); + } + + /** + * Changes the clients properties using given properties. + * + * @param string $cldbid + * @param array $properties + * @return void + */ + public function clientModifyDb($cldbid, array $properties) + { + $properties["cldbid"] = $cldbid; + + $this->execute("clientdbedit", $properties); + } + + /** + * Deletes a clients properties from the database. + * + * @param string $cldbid + * @return void + */ + public function clientDeleteDb($cldbid) + { + $this->execute("clientdbdelete", array("cldbid" => $cldbid)); + } + + /** + * Sets the channel group of a client to the ID specified. + * + * @param integer $cldbid + * @param integer $cid + * @param integer $cgid + * @return void + */ + public function clientSetChannelGroup($cldbid, $cid, $cgid) + { + $this->execute("setclientchannelgroup", array("cldbid" => $cldbid, "cid" => $cid, "cgid" => $cgid)); + } + + /** + * Returns a list of permissions defined for a client. + * + * @param integer $cldbid + * @param boolean $permsid + * @return array + */ + public function clientPermList($cldbid, $permsid = FALSE) + { + $this->clientListReset(); + + return $this->execute("clientpermlist", array("cldbid" => $cldbid, $permsid ? "-permsid" : null))->toAssocArray($permsid ? "permsid" : "permid"); + } + + /** + * Adds a set of specified permissions to a client. Multiple permissions can be added by providing + * the three parameters of each permission. + * + * @param integer $cldbid + * @param integer $permid + * @param integer $permvalue + * @param integer $permskip + * @return void + */ + public function clientPermAssign($cldbid, $permid, $permvalue, $permskip = FALSE) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("clientaddperm", array("cldbid" => $cldbid, $permident => $permid, "permvalue" => $permvalue, "permskip" => $permskip)); + } + + /** + * Removes a set of specified permissions from a client. Multiple permissions can be removed at once. + * + * @param integer $cldbid + * @param integer $permid + * @return void + */ + public function clientPermRemove($cldbid, $permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("clientdelperm", array("cldbid" => $cldbid, $permident => $permid)); + } + + /** + * Returns a list of server groups available. + * + * @param filter $filter + * @return array + */ + public function serverGroupList(array $filter = array()) + { + if($this->sgroupList === null) + { + $this->sgroupList = $this->request("servergrouplist")->toAssocArray("sgid"); + + foreach($this->sgroupList as $sgid => $group) + { + $this->sgroupList[$sgid] = new TeamSpeak3_Node_Servergroup($this, $group); + } + + uasort($this->sgroupList, array(__CLASS__, "sortGroupList")); + } + + return $this->filterList($this->sgroupList, $filter); + } + + /** + * Resets the list of server groups. + * + * @return void + */ + public function serverGroupListReset() + { + $this->sgroupList = null; + } + + /** + * Creates a new server group using the name specified with $name and returns its ID. + * + * @param string $name + * @param integer $type + * @return integer + */ + public function serverGroupCreate($name, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + $this->serverGroupListReset(); + + $sgid = $this->execute("servergroupadd", array("name" => $name, "type" => $type))->toList(); + + return $sgid["sgid"]; + } + + /** + * Creates a copy of an existing server group specified by $ssgid and returns the new groups ID. + * + * @param integer $ssgid + * @param string $name + * @param integer $tsgid + * @param integer $type + * @return integer + */ + public function serverGroupCopy($ssgid, $name = null, $tsgid = 0, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + $this->serverGroupListReset(); + + $sgid = $this->execute("servergroupcopy", array("ssgid" => $ssgid, "tsgid" => $tsgid, "name" => $name, "type" => $type))->toList(); + + if($tsgid && $name) + { + $this->serverGroupRename($tsgid, $name); + } + + return count($sgid) ? $sgid["sgid"] : intval($tsgid); + } + + /** + * Renames the server group specified with $sgid. + * + * @param integer $sgid + * @param string $name + * @return void + */ + public function serverGroupRename($sgid, $name) + { + $this->serverGroupListReset(); + + $this->execute("servergrouprename", array("sgid" => $sgid, "name" => $name)); + } + + /** + * Deletes the server group specified with $sgid. If $force is set to 1, the server group + * will be deleted even if there are clients within. + * + * @param integer $sgid + * @param boolean $force + * @return void + */ + public function serverGroupDelete($sgid, $force = FALSE) + { + $this->serverGroupListReset(); + + $this->execute("servergroupdel", array("sgid" => $sgid, "force" => $force)); + } + + /** + * Returns the TeamSpeak3_Node_Servergroup object matching the given ID. + * + * @param integer $sgid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Servergroup + */ + public function serverGroupGetById($sgid) + { + if(!array_key_exists((string) $sgid, $this->serverGroupList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid groupID", 0xA00); + } + + return $this->sgroupList[intval((string) $sgid)]; + } + + /** + * Returns the TeamSpeak3_Node_Servergroup object matching the given name. + * + * @param string $name + * @param integer $type + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Servergroup + */ + public function serverGroupGetByName($name, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + foreach($this->serverGroupList() as $group) + { + if($group["name"] == $name && $group["type"] == $type) return $group; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid groupID", 0xA00); + } + + /** + * Returns a list of permissions assigned to the server group specified. + * + * @param integer $sgid + * @param boolean $permsid + * @return array + */ + public function serverGroupPermList($sgid, $permsid = FALSE) + { + return $this->execute("servergrouppermlist", array("sgid" => $sgid, $permsid ? "-permsid" : null))->toAssocArray($permsid ? "permsid" : "permid"); + } + + /** + * Adds a set of specified permissions to the server group specified. Multiple permissions + * can be added by providing the four parameters of each permission in separate arrays. + * + * @param integer $sgid + * @param integer $permid + * @param integer $permvalue + * @param integer $permnegated + * @param integer $permskip + * @return void + */ + public function serverGroupPermAssign($sgid, $permid, $permvalue, $permnegated = FALSE, $permskip = FALSE) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("servergroupaddperm", array("sgid" => $sgid, $permident => $permid, "permvalue" => $permvalue, "permnegated" => $permnegated, "permskip" => $permskip)); + } + + /** + * Removes a set of specified permissions from the server group specified with $sgid. Multiple + * permissions can be removed at once. + * + * @param integer $sgid + * @param integer $permid + * @return void + */ + public function serverGroupPermRemove($sgid, $permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("servergroupdelperm", array("sgid" => $sgid, $permident => $permid)); + } + + /** + * Returns a list of clients assigned to the server group specified. + * + * @param integer $sgid + * @return array + */ + public function serverGroupClientList($sgid) + { + if($this["virtualserver_default_server_group"] == $sgid) + { + return array(); + } + + return $this->execute("servergroupclientlist", array("sgid" => $sgid, "-names"))->toAssocArray("cldbid"); + } + + /** + * Adds a client to the server group specified. Please note that a client cannot be + * added to default groups or template groups. + * + * @param integer $sgid + * @param integer $cldbid + * @return void + */ + public function serverGroupClientAdd($sgid, $cldbid) + { + $this->clientListReset(); + + $this->execute("servergroupaddclient", array("sgid" => $sgid, "cldbid" => $cldbid)); + } + + /** + * Removes a client from the server group specified. + * + * @param integer $sgid + * @param integer $cldbid + * @return void + */ + public function serverGroupClientDel($sgid, $cldbid) + { + $this->execute("servergroupdelclient", array("sgid" => $sgid, "cldbid" => $cldbid)); + } + + /** + * Returns an ordered array of regular server groups available based on a pre-defined + * set of rules. + * + * @return array + */ + public function serverGroupGetProfiles() + { + $profiles = array(); + + foreach($this->serverGroupList() as $sgid => $sgroup) + { + if($sgroup["type"] != TeamSpeak3::GROUP_DBTYPE_REGULAR) continue; + + $profiles[$sgid] = array( + "b_permission_modify_power_ignore" => 0, + "i_group_needed_member_add_power" => 0, + "i_group_member_add_power" => 0, + "i_group_needed_member_remove_power" => 0, + "i_group_member_remove_power" => 0, + "i_needed_modify_power_count" => 0, + "i_needed_modify_power_total" => 0, + "i_permission_modify_power" => 0, + "i_group_needed_modify_power" => 0, + "i_group_modify_power" => 0, + "i_client_needed_modify_power" => 0, + "i_client_modify_power" => 0, + "b_virtualserver_servergroup_create" => 0, + "b_virtualserver_servergroup_delete" => 0, + "b_client_ignore_bans" => 0, + "b_client_ignore_antiflood" => 0, + "b_group_is_permanent" => 0, + "i_client_needed_ban_power" => 0, + "i_client_needed_kick_power" => 0, + "i_client_needed_move_power" => 0, + "i_client_talk_power" => 0, + "__sgid" => $sgid, + "__name" => $sgroup->toString(), + "__node" => $sgroup, + ); + + try + { + $perms = $this->serverGroupPermList($sgid, TRUE); + $grant = isset($perms["i_permission_modify_power"]) ? $perms["i_permission_modify_power"]["permvalue"] : null; + } + catch(TeamSpeak3_Adapter_ServerQuery_Exception $e) + { + /* ERROR_database_empty_result */ + if($e->getCode() != 0x501) throw $e; + + $perms = array(); + $grant = null; + } + + foreach($perms as $permsid => $perm) + { + if(in_array($permsid, array_keys($profiles[$sgid]))) + { + $profiles[$sgid][$permsid] = $perm["permvalue"]; + } + elseif(TeamSpeak3_Helper_String::factory($permsid)->startsWith("i_needed_modify_power_")) + { + if(!$grant || $perm["permvalue"] > $grant) continue; + + $profiles[$sgid]["i_needed_modify_power_total"] = $profiles[$sgid]["i_needed_modify_power_total"]+$perm["permvalue"]; + $profiles[$sgid]["i_needed_modify_power_count"]++; + } + } + } + + array_multisort($profiles, SORT_DESC); + + return $profiles; + } + + /** + * Tries to identify the post powerful/weakest server group on the virtual server and returns + * the ID. + * + * @param integer $mode + * @return TeamSpeak3_Node_Servergroup + */ + public function serverGroupIdentify($mode = TeamSpeak3::GROUP_IDENTIFIY_STRONGEST) + { + $profiles = $this->serverGroupGetProfiles(); + + $best_guess_profile = ($mode == TeamSpeak3::GROUP_IDENTIFIY_STRONGEST) ? array_shift($profiles) : array_pop($profiles); + + return $this->serverGroupGetById($best_guess_profile["__sgid"]); + } + + /** + * Returns a list of channel groups available. + * + * @param array $filter + * @return array + */ + public function channelGroupList(array $filter = array()) + { + if($this->cgroupList === null) + { + $this->cgroupList = $this->request("channelgrouplist")->toAssocArray("cgid"); + + foreach($this->cgroupList as $cgid => $group) + { + $this->cgroupList[$cgid] = new TeamSpeak3_Node_Channelgroup($this, $group); + } + + uasort($this->cgroupList, array(__CLASS__, "sortGroupList")); + } + + return $this->filterList($this->cgroupList, $filter); + } + + /** + * Resets the list of channel groups. + * + * @return void + */ + public function channelGroupListReset() + { + $this->cgroupList = null; + } + + /** + * Creates a new channel group using the name specified with $name and returns its ID. + * + * @param string $name + * @param integer $type + * @return integer + */ + public function channelGroupCreate($name, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + $this->channelGroupListReset(); + + $cgid = $this->execute("channelgroupadd", array("name" => $name, "type" => $type))->toList(); + + return $cgid["cgid"]; + } + + /** + * Creates a copy of an existing channel group specified by $scgid and returns the new groups ID. + * + * @param integer $scgid + * @param string $name + * @param integer $tcgid + * @param integer $type + * @return integer + */ + public function channelGroupCopy($scgid, $name = null, $tcgid = 0, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + $this->channelGroupListReset(); + + $cgid = $this->execute("channelgroupcopy", array("scgid" => $scgid, "tcgid" => $tcgid, "name" => $name, "type" => $type))->toList(); + + if($tcgid && $name) + { + $this->channelGroupRename($tcgid, $name); + } + + return count($cgid) ? $cgid["cgid"] : intval($tcgid); + } + + /** + * Renames the channel group specified with $cgid. + * + * @param integer $cgid + * @param string $name + * @return void + */ + public function channelGroupRename($cgid, $name) + { + $this->channelGroupListReset(); + + $this->execute("channelgrouprename", array("cgid" => $cgid, "name" => $name)); + } + + /** + * Deletes the channel group specified with $cgid. If $force is set to 1, the channel group + * will be deleted even if there are clients within. + * + * @param integer $sgid + * @param boolean $force + * @return void + */ + public function channelGroupDelete($cgid, $force = FALSE) + { + $this->channelGroupListReset(); + + $this->execute("channelgroupdel", array("cgid" => $cgid, "force" => $force)); + } + + /** + * Returns the TeamSpeak3_Node_Channelgroup object matching the given ID. + * + * @param integer $cgid + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channelgroup + */ + public function channelGroupGetById($cgid) + { + if(!array_key_exists((string) $cgid, $this->channelGroupList())) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid groupID", 0xA00); + } + + return $this->cgroupList[intval((string) $cgid)]; + } + + /** + * Returns the TeamSpeak3_Node_Channelgroup object matching the given name. + * + * @param string $name + * @param integer $type + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return TeamSpeak3_Node_Channelgroup + */ + public function channelGroupGetByName($name, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + foreach($this->channelGroupList() as $group) + { + if($group["name"] == $name && $group["type"] == $type) return $group; + } + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid groupID", 0xA00); + } + + /** + * Returns a list of permissions assigned to the channel group specified. + * + * @param integer $cgid + * @param boolean $permsid + * @return array + */ + public function channelGroupPermList($cgid, $permsid = FALSE) + { + return $this->execute("channelgrouppermlist", array("cgid" => $cgid, $permsid ? "-permsid" : null))->toAssocArray($permsid ? "permsid" : "permid"); + } + + /** + * Adds a set of specified permissions to the channel group specified. Multiple permissions + * can be added by providing the two parameters of each permission in separate arrays. + * + * @param integer $cgid + * @param integer $permid + * @param integer $permvalue + * @return void + */ + public function channelGroupPermAssign($cgid, $permid, $permvalue) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("channelgroupaddperm", array("cgid" => $cgid, $permident => $permid, "permvalue" => $permvalue)); + } + + /** + * Removes a set of specified permissions from the channel group specified with $cgid. Multiple + * permissions can be removed at once. + * + * @param integer $cgid + * @param integer $permid + * @return void + */ + public function channelGroupPermRemove($cgid, $permid) + { + if(!is_array($permid)) + { + $permident = (is_numeric($permid)) ? "permid" : "permsid"; + } + else + { + $permident = (is_numeric(current($permid))) ? "permid" : "permsid"; + } + + $this->execute("channelgroupdelperm", array("cgid" => $cgid, $permident => $permid)); + } + + /** + * Returns all the client and/or channel IDs currently assigned to channel groups. All three + * parameters are optional so you're free to choose the most suitable combination for your + * requirements. + * + * @param integer $cgid + * @param integer $cid + * @param integer $cldbid + * @return array + */ + public function channelGroupClientList($cgid = null, $cid = null, $cldbid = null) + { + if($this["virtualserver_default_channel_group"] == $cgid) + { + return array(); + } + + return $this->execute("channelgroupclientlist", array("cgid" => $cgid, "cid" => $cid, "cldbid" => $cldbid))->toArray(); + } + + /** + * Restores the default permission settings on the virtual server and returns a new initial + * administrator privilege key. + * + * @return TeamSpeak3_Helper_String + */ + public function permReset() + { + $token = $this->request("permreset")->toList(); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyTokencreated", $this, $token["token"]); + + return $token["token"]; + } + + /** + * Removes any assignment of the permission specified with $permid on the selected virtual server + * and returns the number of removed assignments on success. + * + * @param integer $permid + * @return integer + */ + public function permRemoveAny($permid) + { + $assignments = $this->permissionFind($permid); + + foreach($assignments as $assignment) + { + switch($assignment["t"]) + { + case TeamSpeak3::PERM_TYPE_SERVERGROUP: + $this->serverGroupPermRemove($assignment["id1"], $assignment["p"]); + break; + + case TeamSpeak3::PERM_TYPE_CLIENT: + $this->clientPermRemove($assignment["id2"], $assignment["p"]); + break; + + case TeamSpeak3::PERM_TYPE_CHANNEL: + $this->channelPermRemove($assignment["id2"], $assignment["p"]); + break; + + case TeamSpeak3::PERM_TYPE_CHANNELGROUP: + $this->channelGroupPermRemove($assignment["id1"], $assignment["p"]); + break; + + case TeamSpeak3::PERM_TYPE_CHANNELCLIENT: + $this->channelClientPermRemove($assignment["id2"], $assignment["id1"], $assignment["p"]); + break; + + default: + throw new TeamSpeak3_Adapter_ServerQuery_Exception("convert error", 0x604); + } + } + + return count($assignments); + } + + /** + * Initializes a file transfer upload. $clientftfid is an arbitrary ID to identify the file transfer on client-side. + * + * @param integer $clientftfid + * @param integer $cid + * @param string $name + * @param integer $size + * @param string $cpw + * @param boolean $overwrite + * @param boolean $resume + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return array + */ + public function transferInitUpload($clientftfid, $cid, $name, $size, $cpw = "", $overwrite = FALSE, $resume = FALSE) + { + $upload = $this->execute("ftinitupload", array("clientftfid" => $clientftfid, "cid" => $cid, "name" => $name, "cpw" => $cpw, "size" => $size, "overwrite" => $overwrite, "resume" => $resume))->toList(); + + if(array_key_exists("status", $upload) && $upload["status"] != 0x00) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception($upload["msg"], $upload["status"]); + } + + $upload["cid"] = $cid; + $upload["file"] = $name; + + if(!array_key_exists("ip", $upload) || $upload["ip"]->startsWith("0.0.0.0")) + { + $upload["ip"] = $this->getParent()->getAdapterHost(); + $upload["host"] = $upload["ip"]; + } + else + { + $upload["ip"] = $upload["ip"]->section(","); + $upload["host"] = $upload["ip"]; + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferUploadInit", $upload["ftkey"], $upload); + + return $upload; + } + + /** + * Initializes a file transfer download. $clientftfid is an arbitrary ID to identify the file transfer on client-side. + * + * @param integer $clientftfid + * @param integer $cid + * @param string $name + * @param string $cpw + * @param integer $seekpos + * @throws TeamSpeak3_Adapter_ServerQuery_Exception + * @return array + */ + public function transferInitDownload($clientftfid, $cid, $name, $cpw = "", $seekpos = 0) + { + $download = $this->execute("ftinitdownload", array("clientftfid" => $clientftfid, "cid" => $cid, "name" => $name, "cpw" => $cpw, "seekpos" => $seekpos))->toList(); + + if(array_key_exists("status", $download) && $download["status"] != 0x00) + { + throw new TeamSpeak3_Adapter_ServerQuery_Exception($download["msg"], $download["status"]); + } + + $download["cid"] = $cid; + $download["file"] = $name; + + if(!array_key_exists("ip", $download) || $download["ip"]->startsWith("0.0.0.0")) + { + $download["ip"] = $this->getParent()->getAdapterHost(); + $download["host"] = $download["ip"]; + } + else + { + $download["ip"] = $download["ip"]->section(","); + $download["host"] = $download["ip"]; + } + + TeamSpeak3_Helper_Signal::getInstance()->emit("filetransferDownloadInit", $download["ftkey"], $download); + + return $download; + } + + /** + * Displays a list of running file transfers on the selected virtual server. The output contains the path to + * which a file is uploaded to, the current transfer rate in bytes per second, etc. + * + * @return array + */ + public function transferList() + { + return $this->request("ftlist")->toAssocArray("serverftfid"); + } + + /** + * Stops the running file transfer with server-side ID $serverftfid. + * + * @param integer $serverftfid + * @param boolean $delete + * @return void + */ + public function transferStop($serverftfid, $delete = FALSE) + { + $this->execute("ftstop", array("serverftfid" => $serverftfid, "delete" => $delete)); + } + + /** + * Downloads and returns the servers icon file content. + * + * @return TeamSpeak3_Helper_String + */ + public function iconDownload() + { + if($this->iconIsLocal("virtualserver_icon_id") || $this["virtualserver_icon_id"] == 0) return; + + $download = $this->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->iconGetName("virtualserver_icon_id")); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); + + return $transfer->download($download["ftkey"], $download["size"]); + } + + /** + * Uploads a given icon file content to the server and returns the ID of the icon. + * + * @param string $data + * @return integer + */ + public function iconUpload($data) + { + $crc = crc32($data); + $size = strlen($data); + + $upload = $this->transferInitUpload(rand(0x0000, 0xFFFF), 0, "/icon_" . $crc, $size); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($upload["host"], ":") !== FALSE ? "[" . $upload["host"] . "]" : $upload["host"]) . ":" . $upload["port"]); + + $transfer->upload($upload["ftkey"], $upload["seekpos"], $data); + + return $crc; + } + + /** + * Changes the virtual server configuration using given properties. + * + * @param array $properties + * @return void + */ + public function modify(array $properties) + { + $this->execute("serveredit", $properties); + $this->resetNodeInfo(); + } + + /** + * Sends a text message to all clients on the virtual server. + * + * @param string $msg + * @return void + */ + public function message($msg) + { + $this->execute("sendtextmessage", array("msg" => $msg, "target" => $this->getId(), "targetmode" => TeamSpeak3::TEXTMSG_SERVER)); + } + + /** + * Returns a list of offline messages you've received. The output contains the senders unique identifier, + * the messages subject, etc. + * + * @return array + */ + public function messageList() + { + return $this->request("messagelist")->toAssocArray("msgid"); + } + + /** + * Sends an offline message to the client specified by $cluid. + * + * @param string $cluid + * @param string $subject + * @param string $message + * @return void + */ + public function messageCreate($cluid, $subject, $message) + { + $this->execute("messageadd", array("cluid" => $cluid, "subject" => $subject, "message" => $message)); + } + + /** + * Deletes an existing offline message with ID $msgid from your inbox. + * + * @param integer $msgid + * @return void + */ + public function messageDelete($msgid) + { + $this->execute("messagedel", array("msgid" => $msgid)); + } + + /** + * Returns an existing offline message with ID $msgid from your inbox. + * + * @param integer $msgid + * @param boolean $flag_read + * @return array + */ + public function messageRead($msgid, $flag_read = TRUE) + { + $msg = $this->execute("messageget", array("msgid" => $msgid))->toList(); + + if($flag_read) + { + $this->execute("messageget", array("msgid" => $msgid, "flag" => $flag_read)); + } + + return $msg; + } + + /** + * Creates and returns snapshot data for the selected virtual server. + * + * @param string $mode + * @return string + */ + public function snapshotCreate($mode = TeamSpeak3::SNAPSHOT_STRING) + { + $snapshot = $this->request("serversnapshotcreate")->toString(FALSE); + + switch($mode) + { + case TeamSpeak3::SNAPSHOT_BASE64: + return $snapshot->toBase64(); + break; + + case TeamSpeak3::SNAPSHOT_HEXDEC: + return $snapshot->toHex(); + break; + + default: + return (string) $snapshot; + break; + } + } + + /** + * Deploys snapshot data on the selected virtual server. If no virtual server is selected (ID 0), + * the data will be used to create a new virtual server from scratch. + * + * @param string $data + * @param string $mode + * @return array + */ + public function snapshotDeploy($data, $mode = TeamSpeak3::SNAPSHOT_STRING) + { + switch($mode) + { + case TeamSpeak3::SNAPSHOT_BASE64: + $data = TeamSpeak3_Helper_String::fromBase64($data); + break; + + case TeamSpeak3::SNAPSHOT_HEXDEC: + $data = TeamSpeak3_Helper_String::fromHex($data); + break; + + default: + $data = TeamSpeak3_Helper_String::factory($data); + break; + } + + $detail = $this->request("serversnapshotdeploy " . $data)->toList(); + + if(array_key_exists("sid", $detail)) + { + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyServercreated", $this->getParent(), $detail["sid"]); + } + + return $detail; + } + + /** + * Registers for a specified category of events on a virtual server to receive notification + * messages. Depending on the notifications you've registered for, the server will send you + * a message on every event. + * + * @param string $event + * @param integer $id + * @return void + */ + public function notifyRegister($event, $id = 0) + { + $this->execute("servernotifyregister", array("event" => $event, "id" => $id)); + } + + /** + * Unregisters all events previously registered with servernotifyregister so you will no + * longer receive notification messages. + * + * @return void + */ + public function notifyUnregister() + { + $this->request("servernotifyunregister"); + } + + /** + * Alias for privilegeKeyList(). + * + * @deprecated + */ + public function tokenList($translate = FALSE) + { + return $this->privilegeKeyList(); + } + + /** + * Returns a list of privilege keys (tokens) available. If $resolve is set to TRUE the values + * of token_id1 and token_id2 will be translated into the appropriate group and/or channel + * names. + * + * @param boolean $resolve + * @return array + */ + public function privilegeKeyList($resolve = FALSE) + { + $tokens = $this->request("privilegekeylist")->toAssocArray("token"); + + if($resolve) + { + foreach($tokens as $token => $array) + { + $func = $array["token_type"] ? "channelGroupGetById" : "serverGroupGetById"; + + try + { + $tokens[$token]["token_id1"] = $this->$func($array["token_id1"])->name; + } + catch(Exception $e) + { + /* ERROR_channel_invalid_id */ + if($e->getCode() != 0xA00) throw $e; + } + + try + { + if($array["token_type"]) $tokens[$token]["token_id2"] = $this->channelGetById($array["token_id2"])->getPathway(); + } + catch(Exception $e) + { + /* ERROR_permission_invalid_group_id */ + if($e->getCode() != 0x300) throw $e; + } + } + } + + return $tokens; + } + + /** + * Alias for privilegeKeyCreate(). + * + * @deprecated + */ + public function tokenCreate($type = TeamSpeak3::TOKEN_SERVERGROUP, $id1, $id2 = 0, $description = null, $customset = null) + { + return $this->privilegeKeyCreate($type, $id1, $id2, $description, $customset); + } + + /** + * Creates a new privilege key (token) and returns the key. + * + * @param integer $type + * @param integer $id1 + * @param integer $id2 + * @param string $description + * @param string $customset + * @return TeamSpeak3_Helper_String + */ + public function privilegeKeyCreate($type = TeamSpeak3::TOKEN_SERVERGROUP, $id1, $id2 = 0, $description = null, $customset = null) + { + $token = $this->execute("privilegekeyadd", array("tokentype" => $type, "tokenid1" => $id1, "tokenid2" => $id2, "tokendescription" => $description, "tokencustomset" => $customset))->toList(); + + TeamSpeak3_Helper_Signal::getInstance()->emit("notifyTokencreated", $this, $token["token"]); + + return $token["token"]; + } + + /** + * Alias for privilegeKeyDelete(). + * + * @deprecated + */ + public function tokenDelete($token) + { + $this->privilegeKeyDelete($token); + } + + /** + * Deletes a token specified by key $token. + * + * @param string $token + * @return void + */ + public function privilegeKeyDelete($token) + { + $this->execute("privilegekeydelete", array("token" => $token)); + } + + /** + * Alias for privilegeKeyUse(). + * + * @deprecated + */ + public function tokenUse($token) + { + $this->privilegeKeyUse($token); + } + + /** + * Use a token key gain access to a server or channel group. Please note that the server will + * automatically delete the token after it has been used. + * + * @param string $token + * @return void + */ + public function privilegeKeyUse($token) + { + $this->execute("privilegekeyuse", array("token" => $token)); + } + + /** + * Returns a list of custom client properties specified by $ident. + * + * @param string $ident + * @param string $pattern + * @return array + */ + public function customSearch($ident, $pattern = "%") + { + return $this->execute("customsearch", array("ident" => $ident, "pattern" => $pattern))->toArray(); + } + + /** + * Returns a list of custom properties for the client specified by $cldbid. + * + * @param integer $cldbid + * @return array + */ + public function customInfo($cldbid) + { + return $this->execute("custominfo", array("cldbid" => $cldbid))->toArray(); + } + + /** + * Returns a list of active bans on the selected virtual server. + * + * @return array + */ + public function banList() + { + return $this->request("banlist")->toAssocArray("banid"); + } + + /** + * Deletes all active ban rules from the server. + * + * @return void + */ + public function banListClear() + { + $this->request("bandelall"); + } + + /** + * Adds a new ban rule on the selected virtual server. All parameters are optional but at least one + * of the following rules must be set: ip, name, or uid. + * + * @param array $rules + * @param integer $timeseconds + * @param string $reason + * @return integer + */ + public function banCreate(array $rules, $timeseconds = null, $reason = null) + { + $rules["time"] = $timeseconds; + $rules["banreason"] = $reason; + + $banid = $this->execute("banadd", $rules)->toList(); + + return $banid["banid"]; + } + + /** + * Deletes the specified ban rule from the server. + * + * @param integer $banid + * @return void + */ + public function banDelete($banid) + { + $this->execute("bandel", array("banid" => $banid)); + } + + /** + * Returns a list of complaints on the selected virtual server. If $tcldbid is specified, only + * complaints about the targeted client will be shown. + * + * @param integer $tcldbid + * @return array + */ + public function complaintList($tcldbid = null) + { + return $this->execute("complainlist", array("tcldbid" => $tcldbid))->toArray(); + } + + /** + * Deletes all active complaints about the client with database ID $tcldbid from the server. + * + * @param integer $tcldbid + * @return void + */ + public function complaintListClear($tcldbid) + { + $this->execute("complaindelall", array("tcldbid" => $tcldbid)); + } + + /** + * Submits a complaint about the client with database ID $tcldbid to the server. + * + * @param integer $tcldbid + * @param string $message + * @return void + */ + public function complaintCreate($tcldbid, $message) + { + $this->execute("complainadd", array("tcldbid" => $tcldbid, "message" => $message)); + } + + /** + * Deletes the complaint about the client with ID $tcldbid submitted by the client with ID $fcldbid from the server. + * + * @param integer $tcldbid + * @param integer $fcldbid + * @return void + */ + public function complaintDelete($tcldbid, $fcldbid) + { + $this->execute("complaindel", array("tcldbid" => $tcldbid, "fcldbid" => $fcldbid)); + } + + /** + * Returns a list of temporary server passwords. + * + * @param boolean $resolve + * @return array + */ + public function tempPasswordList($resolve = FALSE) + { + $passwords = $this->request("servertemppasswordlist")->toAssocArray("pw_clear"); + + if($resolve) + { + foreach($passwords as $password => $array) + { + try + { + $channel = $this->channelGetById($array["tcid"]); + + $passwords[$password]["tcname"] = $channel->toString(); + $passwords[$password]["tcpath"] = $channel->getPathway(); + } + catch(Exception $e) + { + /* ERROR_channel_invalid_id */ + if($e->getCode() != 0xA00) throw $e; + } + } + } + + return $passwords; + } + + /** + * Sets a new temporary server password specified with $pw. The temporary password will be + * valid for the number of seconds specified with $duration. The client connecting with this + * password will automatically join the channel specified with $tcid. If tcid is set to 0, + * the client will join the default channel. + * + * @param string $pw + * @param integer $duration + * @param integer $tcid + * @param string $tcpw + * @param string $desc + * @return void + */ + public function tempPasswordCreate($pw, $duration, $tcid = 0, $tcpw = "", $desc = "") + { + $this->execute("servertemppasswordadd", array("pw" => $pw, "duration" => $duration, "tcid" => $tcid, "tcpw" => $tcpw, "desc" => $desc)); + } + + /** + * Deletes the temporary server password specified with $pw. + * + * @param string $pw + * @return void + */ + public function tempPasswordDelete($pw) + { + $this->execute("servertemppassworddel", array("pw" => $pw)); + } + + /** + * Displays a specified number of entries (1-100) from the servers log. + * + * @param integer $lines + * @param integer $begin_pos + * @param boolean $reverse + * @param boolean $instance + * @return array + */ + public function logView($lines = 30, $begin_pos = null, $reverse = null, $instance = null) + { + return $this->execute("logview", array("lines" => $lines, "begin_pos" => $begin_pos, "instance" => $instance, "reverse" => $reverse))->toArray(); + } + + /** + * Writes a custom entry into the virtual server log. + * + * @param string $logmsg + * @param integer $loglevel + * @return void + */ + public function logAdd($logmsg, $loglevel = TeamSpeak3::LOGLEVEL_INFO) + { + $this->execute("logadd", array("logmsg" => $logmsg, "loglevel" => $loglevel)); + } + + /** + * Returns detailed connection information of the virtual server. + * + * @return array + */ + public function connectionInfo() + { + return $this->request("serverrequestconnectioninfo")->toList(); + } + + /** + * Deletes the virtual server. + * + * @return void + */ + public function delete() + { + $this->getParent()->serverDelete($this->getId()); + + unset($this); + } + + /** + * Starts the virtual server. + * + * @return void + */ + public function start() + { + $this->getParent()->serverStart($this->getId()); + } + + /** + * Stops the virtual server. + * + * @return void + */ + public function stop() + { + $this->getParent()->serverStop($this->getId()); + } + + /** + * Sends a plugin command to all clients connected to the server. + * + * @param string $plugin + * @param string $data + * @return void + */ + public function sendPluginCmd($plugin, $data) + { + $this->execute("plugincmd", array("name" => $plugin, "data" => $data, "targetmode" => TeamSpeak3::PLUGINCMD_SERVER)); + } + + /** + * Changes the properties of your own client connection. + * + * @param array $properties + * @return void + */ + public function selfUpdate(array $properties) + { + $this->execute("clientupdate", $properties); + + foreach($properties as $ident => $value) + { + $this->whoamiSet($ident, $value); + } + } + + /** + * Updates your own ServerQuery login credentials using a specified username. The password + * will be auto-generated. + * + * @param string $username + * @return TeamSpeak3_Helper_String + */ + public function selfUpdateLogin($username) + { + $password = $this->execute("clientsetserverquerylogin", array("client_login_name" => $username))->toList(); + + return $password["client_login_password"]; + } + + /** + * Returns an array containing the permission overview of your own client. + * + * @return array + */ + public function selfPermOverview() + { + return $this->execute("permoverview", array("cldbid" => $this->whoamiGet("client_database_id"), "cid" => $this->whoamiGet("client_channel_id"), "permid" => 0))->toArray(); + } + + /** + * @ignore + */ + protected function fetchNodeList() + { + $this->nodeList = array(); + + foreach($this->channelList() as $channel) + { + if($channel["pid"] == 0) + { + $this->nodeList[] = $channel; + } + } + } + + /** + * @ignore + */ + protected function fetchNodeInfo() + { + $this->nodeInfo = array_merge($this->nodeInfo, $this->request("serverinfo")->toList()); + } + + /** + * Internal callback funtion for sorting of client objects. + * + * @param TeamSpeak3_Node_Client $a + * @param TeamSpeak3_Node_Client $b + * @return integer + */ + protected static function sortClientList(TeamSpeak3_Node_Client $a, TeamSpeak3_Node_Client $b) + { + if(get_class($a) != get_class($b)) + { + return 0; + + /* workaround for PHP bug #50688 */ + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid parameter", 0x602); + } + + if(!$a instanceof TeamSpeak3_Node_Client) + { + return 0; + + /* workaround for PHP bug #50688 */ + throw new TeamSpeak3_Adapter_ServerQuery_Exception("convert error", 0x604); + } + + if($a->getProperty("client_talk_power", 0) != $b->getProperty("client_talk_power", 0)) + { + return ($a->getProperty("client_talk_power", 0) > $b->getProperty("client_talk_power", 0)) ? -1 : 1; + } + + if($a->getProperty("client_is_talker", 0) != $b->getProperty("client_is_talker", 0)) + { + return ($a->getProperty("client_is_talker", 0) > $b->getProperty("client_is_talker", 0)) ? -1 : 1; + } + + return strcmp(strtolower($a["client_nickname"]), strtolower($b["client_nickname"])); + } + + /** + * Internal callback funtion for sorting of group objects. + * + * @param TeamSpeak3_Node_Abstract $a + * @param TeamSpeak3_Node_Abstract $b + * @return integer + */ + protected static function sortGroupList(TeamSpeak3_Node_Abstract $a, TeamSpeak3_Node_Abstract $b) + { + if(get_class($a) != get_class($b)) + { + return 0; + + /* workaround for PHP bug #50688 */ + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid parameter", 0x602); + } + + if(!$a instanceof TeamSpeak3_Node_Servergroup && !$a instanceof TeamSpeak3_Node_Channelgroup) + { + return 0; + + /* workaround for PHP bug #50688 */ + throw new TeamSpeak3_Adapter_ServerQuery_Exception("convert error", 0x604); + } + + if($a->getProperty("sortid", 0) != $b->getProperty("sortid", 0) && $a->getProperty("sortid", 0) != 0 && $b->getProperty("sortid", 0) != 0) + { + return ($a->getProperty("sortid", 0) < $b->getProperty("sortid", 0)) ? -1 : 1; + } + + return ($a->getId() < $b->getId()) ? -1 : 1; + } + +/** + * Internal callback funtion for sorting of file list items. + * + * @param array $a + * @param array $b + * @return integer + */ + protected static function sortFileList(array $a, array $b) + { + if(!array_key_exists("src", $a) || !array_key_exists("src", $b) || !array_key_exists("type", $a) || !array_key_exists("type", $b)) + { + return 0; + + throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid parameter", 0x602); + } + + if($a["type"] != $b["type"]) + { + return ($a["type"] < $b["type"]) ? -1 : 1; + } + + return strcmp(strtolower($a["src"]), strtolower($b["src"])); + } + + /** + * Returns TRUE if the virtual server is online. + * + * @return boolean + */ + public function isOnline() + { + return ($this["virtualserver_status"] == "online") ? TRUE : FALSE; + } + + /** + * Returns TRUE if the virtual server is offline. + * + * @return boolean + */ + public function isOffline() + { + return ($this["virtualserver_status"] == "offline") ? TRUE : FALSE; + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + public function getUniqueId() + { + return $this->getParent()->getUniqueId() . "_s" . $this->getId(); + } + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + public function getIcon() + { + if($this["virtualserver_clientsonline"]-$this["virtualserver_queryclientsonline"] >= $this["virtualserver_maxclients"]) + { + return "server_full"; + } + elseif($this["virtualserver_flag_password"]) + { + return "server_pass"; + } + else + { + return "server_open"; + } + } + + /** + * Returns a symbol representing the node. + * + * @return string + */ + public function getSymbol() + { + return "$"; + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return (string) $this["virtualserver_name"]; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Node/Servergroup.php b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Servergroup.php new file mode 100644 index 0000000..d836b34 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Node/Servergroup.php @@ -0,0 +1,300 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Node_Servergroup + * @brief Class describing a TeamSpeak 3 server group and all it's parameters. + */ +class TeamSpeak3_Node_Servergroup extends TeamSpeak3_Node_Abstract +{ + /** + * The TeamSpeak3_Node_Servergroup constructor. + * + * @param TeamSpeak3_Node_Server $server + * @param array $info + * @param string $index + * @throws TeamSpeak3_Node_Exception + * @return TeamSpeak3_Node_Servergroup + */ + public function __construct(TeamSpeak3_Node_Server $server, array $info, $index = "sgid") + { + $this->parent = $server; + $this->nodeInfo = $info; + + if(!array_key_exists($index, $this->nodeInfo)) + { + throw new TeamSpeak3_Node_Exception("invalid groupID", 0xA00); + } + + $this->nodeId = $this->nodeInfo[$index]; + } + + /** + * Renames the server group specified. + * + * @param string $name + * @return void + */ + public function rename($name) + { + $this->getParent()->serverGroupRename($this->getId(), $name); + } + + /** + * Deletes the server group. If $force is set to 1, the server group will be + * deleted even if there are clients within. + * + * @param boolean $force + * @return void + */ + public function delete($force = FALSE) + { + $this->getParent()->serverGroupDelete($this->getId(), $force); + + unset($this); + } + + /** + * Creates a copy of the server group and returns the new groups ID. + * + * @param string $name + * @param integer $tsgid + * @param integer $type + * @return integer + */ + public function copy($name = null, $tsgid = 0, $type = TeamSpeak3::GROUP_DBTYPE_REGULAR) + { + return $this->getParent()->serverGroupCopy($this->getId(), $name, $tsgid, $type); + } + + /** + * Returns a list of permissions assigned to the server group. + * + * @param boolean $permsid + * @return array + */ + public function permList($permsid = FALSE) + { + return $this->getParent()->serverGroupPermList($this->getId(), $permsid); + } + + /** + * Adds a set of specified permissions to the server group. Multiple permissions + * can be added by providing the four parameters of each permission in separate arrays. + * + * @param integer $permid + * @param integer $permvalue + * @param integer $permnegated + * @param integer $permskip + * @return void + */ + public function permAssign($permid, $permvalue, $permnegated = FALSE, $permskip = FALSE) + { + $this->getParent()->serverGroupPermAssign($this->getId(), $permid, $permvalue, $permnegated, $permskip); + } + + /** + * Alias for permAssign(). + * + * @deprecated + */ + public function permAssignByName($permname, $permvalue, $permnegated = FALSE, $permskip = FALSE) + { + $this->permAssign($permname, $permvalue, $permnegated, $permskip); + } + + /** + * Removes a set of specified permissions from the server group. Multiple + * permissions can be removed at once. + * + * @param integer $permid + * @return void + */ + public function permRemove($permid) + { + $this->getParent()->serverGroupPermRemove($this->getId(), $permid); + } + + /** + * Alias for permRemove(). + * + * @deprecated + */ + public function permRemoveByName($permname) + { + $this->permRemove($permname); + } + + /** + * Returns a list of clients assigned to the server group specified. + * + * @return array + */ + public function clientList() + { + return $this->getParent()->serverGroupClientList($this->getId()); + } + + /** + * Adds a client to the server group specified. Please note that a client cannot be + * added to default groups or template groups. + * + * @param integer $cldbid + * @return void + */ + public function clientAdd($cldbid) + { + $this->getParent()->serverGroupClientAdd($this->getId(), $cldbid); + } + + /** + * Removes a client from the server group. + * + * @param integer $cldbid + * @return void + */ + public function clientDel($cldbid) + { + $this->getParent()->serverGroupClientDel($this->getId(), $cldbid); + } + + /** + * Alias for privilegeKeyCreate(). + * + * @deprecated + */ + public function tokenCreate($description = null, $customset = null) + { + return $this->privilegeKeyCreate($description, $customset); + } + + /** + * Creates a new privilege key (token) for the server group and returns the key. + * + * @param string $description + * @param string $customset + * @return TeamSpeak3_Helper_String + */ + public function privilegeKeyCreate($description = null, $customset = null) + { + return $this->getParent()->privilegeKeyCreate(TeamSpeak3::TOKEN_SERVERGROUP, $this->getId(), 0, $description, $customset); + } + + /** + * Sends a text message to all clients residing in the server group on the virtual server. + * + * @param string $msg + * @return void + */ + public function message($msg) + { + foreach($this as $client) + { + try + { + $this->execute("sendtextmessage", array("msg" => $msg, "target" => $client, "targetmode" => TeamSpeak3::TEXTMSG_CLIENT)); + } + catch(TeamSpeak3_Adapter_ServerQuery_Exception $e) + { + /* ERROR_client_invalid_id */ + if($e->getCode() != 0x0200) throw $e; + } + } + } + + /** + * Downloads and returns the server groups icon file content. + * + * @return TeamSpeak3_Helper_String + */ + public function iconDownload() + { + if($this->iconIsLocal("iconid") || $this["iconid"] == 0) return; + + $download = $this->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->iconGetName("iconid")); + $transfer = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"]); + + return $transfer->download($download["ftkey"], $download["size"]); + } + + /** + * @ignore + */ + protected function fetchNodeList() + { + $this->nodeList = array(); + + foreach($this->getParent()->clientList() as $client) + { + if(in_array($this->getId(), explode(",", $client["client_servergroups"]))) + { + $this->nodeList[] = $client; + } + } + } + + /** + * Returns a unique identifier for the node which can be used as a HTML property. + * + * @return string + */ + public function getUniqueId() + { + return $this->getParent()->getUniqueId() . "_sg" . $this->getId(); + } + + /** + * Returns the name of a possible icon to display the node object. + * + * @return string + */ + public function getIcon() + { + return "group_server"; + } + + /** + * Returns a symbol representing the node. + * + * @return string + */ + public function getSymbol() + { + return "%"; + } + + /** + * Returns a string representation of this node. + * + * @return string + */ + public function __toString() + { + return (string) $this["name"]; + } +} + diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/TeamSpeak3.php b/lib/ts3phpframework/libraries/TeamSpeak3/TeamSpeak3.php new file mode 100644 index 0000000..deed54e --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/TeamSpeak3.php @@ -0,0 +1,974 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3 + * @brief Factory class all for TeamSpeak 3 PHP Framework objects. + */ +class TeamSpeak3 +{ + /** + * TeamSpeak 3 protocol welcome message. + */ + const READY = "TS3"; + + /** + * TeamSpeak 3 protocol greeting message prefix. + */ + const GREET = "Welcome"; + + /** + * TeamSpeak 3 protocol error message prefix. + */ + const ERROR = "error"; + + /** + * TeamSpeak 3 protocol event message prefix. + */ + const EVENT = "notify"; + + /** + * TeamSpeak 3 protocol server connection handler ID prefix. + */ + const SCHID = "selected"; + + /** + * TeamSpeak 3 PHP Framework version. + */ + const LIB_VERSION = "1.1.24"; + + /*@ + * TeamSpeak 3 protocol separators. + */ + const SEPARATOR_LINE = "\n"; //!< protocol line separator + const SEPARATOR_LIST = "|"; //!< protocol list separator + const SEPARATOR_CELL = " "; //!< protocol cell separator + const SEPARATOR_PAIR = "="; //!< protocol pair separator + + /*@ + * TeamSpeak 3 log levels. + */ + const LOGLEVEL_CRITICAL = 0x00; //!< 0: these messages stop the program + const LOGLEVEL_ERROR = 0x01; //!< 1: everything that is really bad + const LOGLEVEL_WARNING = 0x02; //!< 2: everything that might be bad + const LOGLEVEL_DEBUG = 0x03; //!< 3: output that might help find a problem + const LOGLEVEL_INFO = 0x04; //!< 4: informational output + const LOGLEVEL_DEVEL = 0x05; //!< 5: development output + + /*@ + * TeamSpeak 3 token types. + */ + const TOKEN_SERVERGROUP = 0x00; //!< 0: server group token (id1={groupID} id2=0) + const TOKEN_CHANNELGROUP = 0x01; //!< 1: channel group token (id1={groupID} id2={channelID}) + + /*@ + * TeamSpeak 3 codec identifiers. + */ + const CODEC_SPEEX_NARROWBAND = 0x00; //!< 0: speex narrowband (mono, 16bit, 8kHz) + const CODEC_SPEEX_WIDEBAND = 0x01; //!< 1: speex wideband (mono, 16bit, 16kHz) + const CODEC_SPEEX_ULTRAWIDEBAND = 0x02; //!< 2: speex ultra-wideband (mono, 16bit, 32kHz) + const CODEC_CELT_MONO = 0x03; //!< 3: celt mono (mono, 16bit, 48kHz) + const CODEC_OPUS_VOICE = 0x04; //!< 3: opus voice (interactive) + const CODEC_OPUS_MUSIC = 0x05; //!< 3: opus music (interactive) + + /*@ + * TeamSpeak 3 codec encryption modes. + */ + const CODEC_CRYPT_INDIVIDUAL = 0x00; //!< 0: configure per channel + const CODEC_CRYPT_DISABLED = 0x01; //!< 1: globally disabled + const CODEC_CRYPT_ENABLED = 0x02; //!< 2: globally enabled + + /*@ + * TeamSpeak 3 kick reason types. + */ + const KICK_CHANNEL = 0x04; //!< 4: kick client from channel + const KICK_SERVER = 0x05; //!< 5: kick client from server + + /*@ + * TeamSpeak 3 text message target modes. + */ + const TEXTMSG_CLIENT = 0x01; //!< 1: target is a client + const TEXTMSG_CHANNEL = 0x02; //!< 2: target is a channel + const TEXTMSG_SERVER = 0x03; //!< 3: target is a virtual server + + /*@ + * TeamSpeak 3 plugin command target modes. + */ + const PLUGINCMD_CHANNEL = 0x01; //!< 1: send plugincmd to all clients in current channel + const PLUGINCMD_SERVER = 0x02; //!< 2: send plugincmd to all clients on server + const PLUGINCMD_CLIENT = 0x03; //!< 3: send plugincmd to all given client ids + const PLUGINCMD_CHANNEL_SUBSCRIBED = 0x04; //!< 4: send plugincmd to all subscribed clients in current channel + + /*@ + * TeamSpeak 3 host message modes. + */ + const HOSTMSG_NONE = 0x00; //!< 0: display no message + const HOSTMSG_LOG = 0x01; //!< 1: display message in chatlog + const HOSTMSG_MODAL = 0x02; //!< 2: display message in modal dialog + const HOSTMSG_MODALQUIT = 0x03; //!< 3: display message in modal dialog and close connection + + /*@ + * TeamSpeak 3 host banner modes. + */ + const HOSTBANNER_NO_ADJUST = 0x00; //!< 0: do not adjust + const HOSTBANNER_IGNORE_ASPECT = 0x01; //!< 1: adjust but ignore aspect ratio + const HOSTBANNER_KEEP_ASPECT = 0x02; //!< 2: adjust and keep aspect ratio + + /*@ + * TeamSpeak 3 client identification types. + */ + const CLIENT_TYPE_REGULAR = 0x00; //!< 0: regular client + const CLIENT_TYPE_SERVERQUERY = 0x01; //!< 1: query client + + /*@ + * TeamSpeak 3 permission group database types. + */ + const GROUP_DBTYPE_TEMPLATE = 0x00; //!< 0: template group (used for new virtual servers) + const GROUP_DBTYPE_REGULAR = 0x01; //!< 1: regular group (used for regular clients) + const GROUP_DBTYPE_SERVERQUERY = 0x02; //!< 2: global query group (used for ServerQuery clients) + + /*@ + * TeamSpeak 3 permission group name modes. + */ + const GROUP_NAMEMODE_HIDDEN = 0x00; //!< 0: display no name + const GROUP_NAMEMODE_BEFORE = 0x01; //!< 1: display name before client nickname + const GROUP_NAMEMODE_BEHIND = 0x02; //!< 2: display name after client nickname + + /*@ + * TeamSpeak 3 permission group identification types. + */ + const GROUP_IDENTIFIY_STRONGEST = 0x01; //!< 1: identify most powerful group + const GROUP_IDENTIFIY_WEAKEST = 0x02; //!< 2: identify weakest group + + /*@ + * TeamSpeak 3 permission types. + */ + const PERM_TYPE_SERVERGROUP = 0x00; //!< 0: server group permission + const PERM_TYPE_CLIENT = 0x01; //!< 1: client specific permission + const PERM_TYPE_CHANNEL = 0x02; //!< 2: channel specific permission + const PERM_TYPE_CHANNELGROUP = 0x03; //!< 3: channel group permission + const PERM_TYPE_CHANNELCLIENT = 0x04; //!< 4: channel-client specific permission + + /*@ + * TeamSpeak 3 permission categories. + */ + const PERM_CAT_GLOBAL = 0x10; //!< 00010000: global permissions + const PERM_CAT_GLOBAL_INFORMATION = 0x11; //!< 00010001: global permissions -> global information + const PERM_CAT_GLOBAL_SERVER_MGMT = 0x12; //!< 00010010: global permissions -> virtual server management + const PERM_CAT_GLOBAL_ADM_ACTIONS = 0x13; //!< 00010011: global permissions -> global administrative actions + const PERM_CAT_GLOBAL_SETTINGS = 0x14; //!< 00010100: global permissions -> global settings + const PERM_CAT_SERVER = 0x20; //!< 00100000: virtual server permissions + const PERM_CAT_SERVER_INFORMATION = 0x21; //!< 00100001: virtual server permissions -> virtual server information + const PERM_CAT_SERVER_ADM_ACTIONS = 0x22; //!< 00100010: virtual server permissions -> virtual server administrative actions + const PERM_CAT_SERVER_SETTINGS = 0x23; //!< 00100011: virtual server permissions -> virtual server settings + const PERM_CAT_CHANNEL = 0x30; //!< 00110000: channel permissions + const PERM_CAT_CHANNEL_INFORMATION = 0x31; //!< 00110001: channel permissions -> channel information + const PERM_CAT_CHANNEL_CREATE = 0x32; //!< 00110010: channel permissions -> create channels + const PERM_CAT_CHANNEL_MODIFY = 0x33; //!< 00110011: channel permissions -> edit channels + const PERM_CAT_CHANNEL_DELETE = 0x34; //!< 00110100: channel permissions -> delete channels + const PERM_CAT_CHANNEL_ACCESS = 0x35; //!< 00110101: channel permissions -> access channels + const PERM_CAT_GROUP = 0x40; //!< 01000000: group permissions + const PERM_CAT_GROUP_INFORMATION = 0x41; //!< 01000001: group permissions -> group information + const PERM_CAT_GROUP_CREATE = 0x42; //!< 01000010: group permissions -> create groups + const PERM_CAT_GROUP_MODIFY = 0x43; //!< 01000011: group permissions -> edit groups + const PERM_CAT_GROUP_DELETE = 0x44; //!< 01000100: group permissions -> delete groups + const PERM_CAT_CLIENT = 0x50; //!< 01010000: client permissions + const PERM_CAT_CLIENT_INFORMATION = 0x51; //!< 01010001: client permissions -> client information + const PERM_CAT_CLIENT_ADM_ACTIONS = 0x52; //!< 01010010: client permissions -> client administrative actions + const PERM_CAT_CLIENT_BASICS = 0x53; //!< 01010011: client permissions -> client basic communication + const PERM_CAT_CLIENT_MODIFY = 0x54; //!< 01010100: client permissions -> edit clients + const PERM_CAT_FILETRANSFER = 0x60; //!< 01100000: file transfer permissions + const PERM_CAT_NEEDED_MODIFY_POWER = 0xFF; //!< 11111111: needed permission modify power (grant) permissions + + /*@ + * TeamSpeak 3 file types. + */ + const FILE_TYPE_DIRECTORY = 0x00; //!< 0: file is directory + const FILE_TYPE_REGULAR = 0x01; //!< 1: file is regular + + /*@ + * TeamSpeak 3 server snapshot types. + */ + const SNAPSHOT_STRING = 0x00; //!< 0: default string + const SNAPSHOT_BASE64 = 0x01; //!< 1: base64 string + const SNAPSHOT_HEXDEC = 0x02; //!< 2: hexadecimal string + + /*@ + * TeamSpeak 3 channel spacer types. + */ + const SPACER_SOLIDLINE = 0x00; //!< 0: solid line + const SPACER_DASHLINE = 0x01; //!< 1: dash line + const SPACER_DOTLINE = 0x02; //!< 2: dot line + const SPACER_DASHDOTLINE = 0x03; //!< 3: dash dot line + const SPACER_DASHDOTDOTLINE = 0x04; //!< 4: dash dot dot line + const SPACER_CUSTOM = 0x05; //!< 5: custom format + + /*@ + * TeamSpeak 3 channel spacer alignments. + */ + const SPACER_ALIGN_LEFT = 0x00; //!< 0: alignment left + const SPACER_ALIGN_RIGHT = 0x01; //!< 1: alignment right + const SPACER_ALIGN_CENTER = 0x02; //!< 2: alignment center + const SPACER_ALIGN_REPEAT = 0x03; //!< 3: repeat until the whole line is filled + + /*@ + * TeamSpeak 3 reason identifiers. + */ + const REASON_NONE = 0x00; //!< 0: no reason + const REASON_MOVE = 0x01; //!< 1: channel switched or moved + const REASON_SUBSCRIPTION = 0x02; //!< 2: subscription added or removed + const REASON_TIMEOUT = 0x03; //!< 3: client connection timed out + const REASON_CHANNEL_KICK = 0x04; //!< 4: client kicked from channel + const REASON_SERVER_KICK = 0x05; //!< 5: client kicked from server + const REASON_SERVER_BAN = 0x06; //!< 6: client banned from server + const REASON_SERVER_STOP = 0x07; //!< 7: server stopped + const REASON_DISCONNECT = 0x08; //!< 8: client disconnected + const REASON_CHANNEL_UPDATE = 0x09; //!< 9: channel information updated + const REASON_CHANNEL_EDIT = 0x0A; //!< 10: channel information edited + const REASON_DISCONNECT_SHUTDOWN = 0x0B; //!< 11: client disconnected on server shutdown + + /** + * Stores an array containing various chars which need to be escaped while communicating + * with a TeamSpeak 3 Server. + * + * @var array + */ + protected static $escape_patterns = array( + "\\" => "\\\\", // backslash + "/" => "\\/", // slash + " " => "\\s", // whitespace + "|" => "\\p", // pipe + ";" => "\\;", // semicolon + "\a" => "\\a", // bell + "\b" => "\\b", // backspace + "\f" => "\\f", // formfeed + "\n" => "\\n", // newline + "\r" => "\\r", // carriage return + "\t" => "\\t", // horizontal tab + "\v" => "\\v" // vertical tab + ); + + /** + * Factory for TeamSpeak3_Adapter_Abstract classes. $uri must be formatted as + * "://:@:/#". All parameters + * except adapter, host and port are optional. + * + * === Supported Options === + * - timeout + * - blocking + * - nickname + * - no_query_clients + * - use_offline_as_virtual + * - clients_before_channels + * - server_id|server_uid|server_port|server_name|server_tsdns + * - channel_id|channel_name + * - client_id|client_uid|client_name + * + * === Supported Flags (only one per $uri) === + * - no_query_clients + * - use_offline_as_virtual + * - clients_before_channels + * + * === URI Examples === + * - serverquery://127.0.0.1:10011/ + * - serverquery://127.0.0.1:10011/?server_port=9987&channel_id=1 + * - serverquery://127.0.0.1:10011/?server_port=9987&channel_id=1#no_query_clients + * - serverquery://127.0.0.1:10011/?server_port=9987&client_name=ScP + * - filetransfer://127.0.0.1:30011/ + * - blacklist + * - update + * + * @param string $uri + * @return TeamSpeak3_Adapter_Abstract + * @return TeamSpeak3_Node_Abstract + * @return TeamSpeak3_Node_Host + * @return TeamSpeak3_Node_Server + */ + public static function factory($uri) + { + self::init(); + + $uri = new TeamSpeak3_Helper_Uri($uri); + + $adapter = self::getAdapterName($uri->getScheme()); + $options = array("host" => $uri->getHost(), "port" => $uri->getPort(), "timeout" => (int) $uri->getQueryVar("timeout", 10), "blocking" => (int) $uri->getQueryVar("blocking", 1)); + + self::loadClass($adapter); + + $object = new $adapter($options); + + if($object instanceof TeamSpeak3_Adapter_ServerQuery) + { + $node = $object->getHost(); + + if($uri->hasUser() && $uri->hasPass()) + { + $node->login($uri->getUser(), $uri->getPass()); + } + + if($uri->hasQueryVar("nickname")) + { + $node->setPredefinedQueryName($uri->getQueryVar("nickname")); + } + + if($uri->getFragment() == "use_offline_as_virtual") + { + $node->setUseOfflineAsVirtual(TRUE); + } + elseif($uri->hasQueryVar("use_offline_as_virtual")) + { + $node->setUseOfflineAsVirtual($uri->getQueryVar("use_offline_as_virtual") ? TRUE : FALSE); + } + + if($uri->getFragment() == "clients_before_channels") + { + $node->setLoadClientlistFirst(TRUE); + } + elseif($uri->hasQueryVar("clients_before_channels")) + { + $node->setLoadClientlistFirst($uri->getQueryVar("clients_before_channels") ? TRUE : FALSE); + } + + if($uri->getFragment() == "no_query_clients") + { + $node->setExcludeQueryClients(TRUE); + } + elseif($uri->hasQueryVar("no_query_clients")) + { + $node->setExcludeQueryClients($uri->getQueryVar("no_query_clients") ? TRUE : FALSE); + } + + if($uri->hasQueryVar("server_id")) + { + $node = $node->serverGetById($uri->getQueryVar("server_id")); + } + elseif($uri->hasQueryVar("server_uid")) + { + $node = $node->serverGetByUid($uri->getQueryVar("server_uid")); + } + elseif($uri->hasQueryVar("server_port")) + { + $node = $node->serverGetByPort($uri->getQueryVar("server_port")); + } + elseif($uri->hasQueryVar("server_name")) + { + $node = $node->serverGetByName($uri->getQueryVar("server_name")); + } + elseif($uri->hasQueryVar("server_tsdns")) + { + $node = $node->serverGetByTSDNS($uri->getQueryVar("server_tsdns")); + } + + if($node instanceof TeamSpeak3_Node_Server) + { + if($uri->hasQueryVar("channel_id")) + { + $node = $node->channelGetById($uri->getQueryVar("channel_id")); + } + elseif($uri->hasQueryVar("channel_name")) + { + $node = $node->channelGetByName($uri->getQueryVar("channel_name")); + } + + if($uri->hasQueryVar("client_id")) + { + $node = $node->clientGetById($uri->getQueryVar("client_id")); + } + if($uri->hasQueryVar("client_uid")) + { + $node = $node->clientGetByUid($uri->getQueryVar("client_uid")); + } + elseif($uri->hasQueryVar("client_name")) + { + $node = $node->clientGetByName($uri->getQueryVar("client_name")); + } + } + + return $node; + } + + return $object; + } + + /** + * Loads a class from a PHP file. The filename must be formatted as "$class.php". + * + * include() is not prefixed with the @ operator because if the file is loaded and + * contains a parse error, execution will halt silently and this is difficult to debug. + * + * @param string $class + * @throws LogicException + * @return boolean + */ + protected static function loadClass($class) + { + if(class_exists($class, FALSE) || interface_exists($class, FALSE)) + { + return; + } + + if(preg_match("/[^a-z0-9\\/\\\\_.-]/i", $class)) + { + throw new LogicException("illegal characters in classname '" . $class . "'"); + } + + $file = self::getFilePath($class) . ".php"; + + if(!file_exists($file) || !is_readable($file)) + { + throw new LogicException("file '" . $file . "' does not exist or is not readable"); + } + + if(class_exists($class, FALSE) || interface_exists($class, FALSE)) + { + throw new LogicException("class '" . $class . "' does not exist"); + } + + return include_once($file); + } + + /** + * Generates a possible file path for $name. + * + * @param string $name + * @return string + */ + protected static function getFilePath($name) + { + $path = str_replace("_", DIRECTORY_SEPARATOR, $name); + $path = str_replace(__CLASS__, dirname(__FILE__), $path); + + return $path; + } + + /** + * Returns the name of an adapter class by $name. + * + * @param string $name + * @param string $namespace + * @throws TeamSpeak3_Adapter_Exception + * @return string + */ + protected static function getAdapterName($name, $namespace = "TeamSpeak3_Adapter_") + { + $path = self::getFilePath($namespace); + $scan = scandir($path); + + foreach($scan as $node) + { + $file = TeamSpeak3_Helper_String::factory($node)->toLower(); + + if($file->startsWith($name) && $file->endsWith(".php")) + { + return $namespace . str_replace(".php", "", $node); + } + } + + throw new TeamSpeak3_Adapter_Exception("adapter '" . $name . "' does not exist"); + } + + /** + * spl_autoload() suitable implementation for supporting class autoloading. + * + * @param string $class + * @return boolean + */ + public static function autoload($class) + { + if(substr($class, 0, strlen(__CLASS__)) != __CLASS__) return; + + try + { + self::loadClass($class); + + return TRUE; + } + catch(Exception $e) + { + return FALSE; + } + } + + /** + * Checks for required PHP features, enables autoloading and starts a default profiler. + * + * @throws LogicException + * @return void + */ + public static function init() + { + if(version_compare(phpversion(), "5.2.1") == -1) + { + throw new LogicException("this particular software cannot be used with the installed version of PHP"); + } + + if(!function_exists("stream_socket_client")) + { + throw new LogicException("network functions are not available in this PHP installation"); + } + + if(!function_exists("spl_autoload_register")) + { + throw new LogicException("autoload functions are not available in this PHP installation"); + } + + if(!class_exists("TeamSpeak3_Helper_Profiler")) + { + spl_autoload_register(array(__CLASS__, "autoload")); + } + + TeamSpeak3_Helper_Profiler::start(); + } + + /** + * Returns an assoc array containing all escape patterns available on a TeamSpeak 3 + * Server. + * + * @return array + */ + public static function getEscapePatterns() + { + return self::$escape_patterns; + } + + /** + * Debug helper function. This is a wrapper for var_dump() that adds the pre-format tags, + * cleans up newlines and indents, and runs htmlentities() before output. + * + * @param mixed $var + * @param bool $echo + * @return string + */ + public static function dump($var, $echo = TRUE) + { + ob_start(); + var_dump($var); + + $output = preg_replace("/\]\=\>\n(\s+)/m", "] => ", ob_get_clean()); + + if(PHP_SAPI == "cli") + { + $output = PHP_EOL . PHP_EOL . $output . PHP_EOL; + } + else + { + $output = "

" . htmlspecialchars($output, ENT_QUOTES, "utf-8") . "
"; + } + + if($echo) echo($output); + + return $output; + } +} + +/*! + * \mainpage API Documentation + * + * \section welcome_sec Introduction + * + * \subsection welcome1 What is the TS3 PHP Framework? + * Initially released in January 2010, the TS3 PHP Framework is a powerful, open source, object-oriented framework + * implemented in PHP 5 and licensed under the GNU General Public License. It's based on simplicity and a rigorously + * tested agile codebase. Extend the functionality of your servers with scripts or create powerful web applications + * to manage all features of your TeamSpeak 3 Server instances. + * + * Tested. Thoroughly. Enterprise-ready and built with agile methods, the TS3 PHP Framework has been unit-tested from + * the start to ensure that all code remains stable and easy for you to extend, re-test with your extensions, and + * further maintain. + * + * \subsection welcome2 Why should I use the TS3 PHP Framework rather than other PHP libraries? + * The TS3 PHP Framework is a is a modern use-at-will framework that provides individual components to communicate + * with the TeamSpeak 3 Server. + * + * There are lots of arguments for the TS3 PHP Framework in comparison with other PHP based libraries. It is the most + * dynamic and feature-rich piece of software in its class. In addition, it's always up-to-date and 100% compatible to + * almost any TeamSpeak 3 Server version available. + * + * \section sysreqs_sec Requirements + * The TS3 PHP Framework currently supports PHP 5.2.1 or later, but we strongly recommend the most current release of + * PHP for critical security and performance enhancements. If you want to create a web application using the TS3 PHP + * Framework, you need a PHP 5 interpreter with a web server configured to handle PHP scripts correctly. + * + * Note that the majority of TS3 PHP Framework development and deployment is done on nginx, so there is more community + * experience and testing performed on nginx than on other web servers. + * + * \section feature_sec Features + * Features of the TS3 PHP Framework include: + * + * - Fully object-oriented PHP 5 and E_STRICT compliant components + * - Access to all TeamSpeak 3 Server features via ServerQuery + * - Integrated full featured and customizable TSViewer interfaces + * - Full support for file transfers to up- and /or download custom icons and other stuff + * - Powerful error handling capablities using exceptions and customizable error messages + * - Query mechanisms for several official services such as the blacklist and auto-update servers + * - Dynamic signal slots for event based scripting + * - ... + * + * \section example_sec Usage Examples + * + * \subsection example1 1. Kick a single Client from a Virtual Server + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // kick the client with ID 123 from the server + * $ts3_VirtualServer->clientKick(123, TeamSpeak3::KICK_SERVER, "evil kick XD"); + * + * // spawn an object for the client by unique identifier and do the kick + * $ts3_VirtualServer->clientGetByUid("FPMPSC6MXqXq751dX7BKV0JniSo=")->kick(TeamSpeak3::KICK_SERVER, "evil kick XD"); + * + * // spawn an object for the client by current nickname and do the kick + * $ts3_VirtualServer->clientGetByName("ScP")->kick(TeamSpeak3::KICK_SERVER, "evil kick XD"); + * @endcode + * + * \subsection example2 2. Kick all Clients from a Virtual Server + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // query clientlist from virtual server + * $arr_ClientList = $ts3_VirtualServer->clientList(); + * + * // kick all clients online with a single command + * $ts3_VirtualServer->clientKick($arr_ClientList, TeamSpeak3::KICK_SERVER, "evil kick XD"); + * @endcode + * + * \subsection example3 3. Print the Nicknames of connected Android Clients + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // query clientlist from virtual server and filter by platform + * $arr_ClientList = $ts3_VirtualServer->clientList(array("client_platform" => "Android")); + * + * // walk through list of clients + * foreach($arr_ClientList as $ts3_Client) + * { + * echo $ts3_Client . " is using " . $ts3_Client["client_platform"] . "
\n"; + * } + * @endcode + * + * \subsection example4 4. Modify the Settings of each Virtual Server + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the server instance + * $ts3_ServerInstance = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/"); + * + * // walk through list of virtual servers + * foreach($ts3_ServerInstance as $ts3_VirtualServer) + * { + * // modify the virtual servers hostbanner URL only using the ArrayAccess interface + * $ts3_VirtualServer["virtualserver_hostbanner_gfx_url"] = "http://www.example.com/banners/banner01_468x60.jpg"; + * + * // modify the virtual servers hostbanner URL only using property overloading + * $ts3_VirtualServer->virtualserver_hostbanner_gfx_url = "http://www.example.com/banners/banner01_468x60.jpg"; + * + * // modify multiple virtual server properties at once + * $ts3_VirtualServer->modify(array( + * "virtualserver_hostbutton_tooltip" => "My Company", + * "virtualserver_hostbutton_url" => "http://www.example.com", + * "virtualserver_hostbutton_gfx_url" => "http://www.example.com/buttons/button01_24x24.jpg", + * )); + * } + * @endcode + * + * \subsection example5 5. Create a Privilege Key for a Server Group + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // spawn an object for the group using a specified name + * $arr_ServerGroup = $ts3_VirtualServer->serverGroupGetByName("Admins"); + * + * // create the privilege key + * $ts3_PrivilegeKey = $arr_ServerGroup->privilegeKeyCreate(); + * @endcode + * + * \subsection example6 6. Modify the Permissions of Admins on each Virtual Server + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the server instance + * $ts3_ServerInstance = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/"); + * + * // walk through list of virtual servers + * foreach($ts3_ServerInstance as $ts3_VirtualServer) + * { + * // identify the most powerful group on the virtual server + * $ts3_ServerGroup = $ts3_VirtualServer->serverGroupIdentify(); + * + * // assign a new permission + * $ts3_ServerGroup->permAssign("b_virtualserver_modify_hostbanner", TRUE); + * + * // revoke an existing permission + * $ts3_ServerGroup->permRemove("b_virtualserver_modify_maxclients"); + * } + * @endcode + * + * \subsection example7 7. Create a new Virtual Server + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the server instance + * $ts3_ServerInstance = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/"); + * + * // create a virtual server and get its ID + * $new_sid = $ts3_ServerInstance->serverCreate(array( + * "virtualserver_name" => "My TeamSpeak 3 Server", + * "virtualserver_maxclients" => 64, + * "virtualserver_hostbutton_tooltip" => "My Company", + * "virtualserver_hostbutton_url" => "http://www.example.com", + * "virtualserver_hostbutton_gfx_url" => "http://www.example.com/buttons/button01_24x24.jpg", + * )); + * @endcode + * + * \subsection example8 8. Create a hierarchical Channel Stucture + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // create a top-level channel and get its ID + * $top_cid = $ts3_VirtualServer->channelCreate(array( + * "channel_name" => "My Channel", + * "channel_topic" => "This is a top-level channel", + * "channel_codec" => TeamSpeak3::CODEC_SPEEX_WIDEBAND, + * "channel_flag_permanent" => TRUE, + * )); + * + * // create a sub-level channel and get its ID + * $sub_cid = $ts3_VirtualServer->channelCreate(array( + * "channel_name" => "My Sub-Channel", + * "channel_topic" => "This is a sub-level channel", + * "channel_codec" => TeamSpeak3::CODEC_SPEEX_NARROWBAND, + * "channel_flag_permanent" => TRUE, + * "cpid" => $top_cid, + * )); + * @endcode + * + * \subsection example9 9. Send a Text Message to outdated Clients + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // connect to default update server + * $ts3_UpdateServer = TeamSpeak3::factory("update"); + * + * // walk through list of clients on virtual server + * foreach($ts3_VirtualServer->clientList() as $ts3_Client) + * { + * // skip query clients + * if($ts3_Client["client_type"]) continue; + * + * // send test message if client build is outdated + * if($ts3_Client->getRev() < $ts3_UpdateServer->getClientRev()) + * { + * $ts3_Client->message("[COLOR=red]your client is [B]outdated[/B]... update to [U]" . $ts3_UpdateServer->getClientVersion() . "[/U] now![/COLOR]"); + * } + * } + * @endcode + * + * \subsection example10 10. Check if the Server Instance is Outdated or Blacklisted + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the server instance + * $ts3_ServerInstance = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/"); + * + * // connect to default update server + * $ts3_UpdateServer = TeamSpeak3::factory("update"); + * + * // send global text message if the server is outdated + * if($ts3_ServerInstance->version("build") < $ts3_UpdateServer->getServerRev()) + * { + * $ts3_ServerInstance->message("[COLOR=red]your server is [B]outdated[/B]... update to [U]" . $ts3_UpdateServer->getServerVersion() . "[/U] now![/COLOR]"); + * } + * + * // connect to default blacklist server + * $ts3_BlacklistServer = TeamSpeak3::factory("blacklist"); + * + * // send global text message if the server is blacklisted + * if($ts3_BlacklistServer->isBlacklisted($ts3_ServerInstance)) + * { + * $ts3_ServerInstance->message("[COLOR=red]your server is [B]blacklisted[/B]... disconnect now![/COLOR]"); + * } + * @endcode + * + * \subsection example11 11. Create a simple TSViewer for your Website + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // build and display HTML treeview using custom image paths (remote icons will be embedded using data URI sheme) + * echo $ts3_VirtualServer->getViewer(new TeamSpeak3_Viewer_Html("images/viewericons/", "images/countryflags/", "data:image")); + * @endcode + * + * \subsection example12 12. Update all outdated Audio Codecs to their Opus equivalent + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // walk through list of chanels + * foreach($ts3_VirtualServer->channelList() as $ts3_Channel) + * { + * if($ts3_Channel["channel_codec"] == TeamSpeak3::CODEC_CELT_MONO) + * { + * $ts3_Channel["channel_codec"] = TeamSpeak3::CODEC_OPUS_MUSIC; + * } + * elseif($ts3_Channel["channel_codec"] != TeamSpeak3::CODEC_OPUS_MUSIC) + * { + * $ts3_Channel["channel_codec"] = TeamSpeak3::CODEC_OPUS_VOICE; + * } + * } + * @endcode + * + * \subsection example13 13. Display the Avatar of a connected User + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // spawn an object for the client using a specified nickname + * $ts3_Client = $ts3_VirtualServer->clientGetByName("John Doe"); + * + * // download the clients avatar file + * $avatar = $ts3_Client->avatarDownload(); + * + * // send header and display image + * header("Content-Type: " . TeamSpeak3_Helper_Convert::imageMimeType($avatar)); + * echo $avatar; + * @endcode + * + * \subsection example14 14. Create a Simple Bot waiting for Events + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // connect to local server in non-blocking mode, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987&blocking=0"); + * + * // get notified on incoming private messages + * $ts3_VirtualServer->notifyRegister("textprivate"); + * + * // register a callback for notifyTextmessage events + * TeamSpeak3_Helper_Signal::getInstance()->subscribe("notifyTextmessage", "onTextmessage"); + * + * // wait for events + * while(1) $ts3_VirtualServer->getAdapter()->wait(); + * + * // define a callback function + * function onTextmessage(TeamSpeak3_Adapter_ServerQuery_Event $event, TeamSpeak3_Node_Host $host) + * { + * echo "Client " . $event["invokername"] . " sent textmessage: " . $event["msg"]; + * } + * @endcode + * + * \subsection example15 15. Handle Errors using Exceptions and Custom Error Messages + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // register custom error message (supported placeholders are: %file, %line, %code and %mesg) + * TeamSpeak3_Exception::registerCustomMessage(0x300, "The specified channel does not exist; server said: %mesg"); + * + * try + * { + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // spawn an object for the channel using a specified name + * $ts3_Channel = $ts3_VirtualServer->channelGetByName("I do not exist"); + * } + * catch(TeamSpeak3_Exception $e) + * { + * // print the error message returned by the server + * echo "Error " . $e->getCode() . ": " . $e->getMessage(); + * } + * @endcode + * + * \subsection example16 16. Save Connection State in Persistent Session Variable + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // start a PHP session + * session_start(); + * + * // connect to local server, authenticate and spawn an object for the virtual server on port 9987 + * $ts3_VirtualServer = TeamSpeak3::factory("serverquery://username:password@127.0.0.1:10011/?server_port=9987"); + * + * // save connection state (including login and selected virtual server) + * $_SESSION["_TS3"] = serialize($ts3_VirtualServer); + * @endcode + * + * \subsection example17 17. Restore Connection State from Persistent Session Variable + * @code + * // load framework files + * require_once("libraries/TeamSpeak3/TeamSpeak3.php"); + * + * // start a PHP session + * session_start(); + * + * // restore connection state + * $ts3_VirtualServer = unserialize($_SESSION["_TS3"]); + * + * // send a text message to the server + * $ts3_VirtualServer->message("Hello World!"); + * @endcode + * + * Speed up new development and reduce maintenance costs by using the TS3 PHP Framework! + */ diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Transport/Abstract.php b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/Abstract.php new file mode 100644 index 0000000..5350aa5 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/Abstract.php @@ -0,0 +1,270 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Transport_Abstract + * @brief Abstract class for connecting to a TeamSpeak 3 Server through different ways of transport. + */ +abstract class TeamSpeak3_Transport_Abstract +{ + /** + * Stores user-provided configuration settings. + * + * @var array + */ + protected $config = null; + + /** + * Stores the stream resource of the connection. + * + * @var resource + */ + protected $stream = null; + + /** + * Stores the TeamSpeak3_Adapter_Abstract object using this transport. + * + * @var TeamSpeak3_Adapter_Abstract + */ + protected $adapter = null; + + /** + * The TeamSpeak3_Transport_Abstract constructor. + * + * @param array $config + * @throws TeamSpeak3_Transport_Exception + * @return TeamSpeak3_Transport_Abstract + */ + public function __construct(array $config) + { + if(!array_key_exists("host", $config)) + { + throw new TeamSpeak3_Transport_Exception("config must have a key for 'host' which specifies the server host name"); + } + + if(!array_key_exists("port", $config)) + { + throw new TeamSpeak3_Transport_Exception("config must have a key for 'port' which specifies the server port number"); + } + + if(!array_key_exists("timeout", $config)) + { + $config["timeout"] = 10; + } + + if(!array_key_exists("blocking", $config)) + { + $config["blocking"] = 1; + } + + $this->config = $config; + } + + /** + * Commit pending data. + * + * @return array + */ + public function __sleep() + { + return array("config"); + } + + /** + * Reconnects to the remote server. + * + * @return void + */ + public function __wakeup() + { + $this->connect(); + } + + /** + * The TeamSpeak3_Transport_Abstract destructor. + * + * @return void + */ + public function __destruct() + { + if($this->adapter instanceof TeamSpeak3_Adapter_Abstract) + { + $this->adapter->__destruct(); + } + + $this->disconnect(); + } + + /** + * Connects to a remote server. + * + * @throws TeamSpeak3_Transport_Exception + * @return void + */ + abstract public function connect(); + + /** + * Disconnects from a remote server. + * + * @return void + */ + abstract public function disconnect(); + + /** + * Reads data from the stream. + * + * @param integer $length + * @throws TeamSpeak3_Transport_Exception + * @return TeamSpeak3_Helper_String + */ + abstract public function read($length = 4096); + + /** + * Writes data to the stream. + * + * @param string $data + * @return void + */ + abstract public function send($data); + + /** + * Returns the underlying stream resource. + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * Returns the configuration variables in this adapter. + * + * @param string $key + * @param mixed $default + * @return array + */ + public function getConfig($key = null, $default = null) + { + if($key !== null) + { + return array_key_exists($key, $this->config) ? $this->config[$key] : $default; + } + + return $this->config; + } + + /** + * Sets the TeamSpeak3_Adapter_Abstract object using this transport. + * + * @param TeamSpeak3_Adapter_Abstract $adapter + * @return void + */ + public function setAdapter(TeamSpeak3_Adapter_Abstract $adapter) + { + $this->adapter = $adapter; + } + + /** + * Returns the TeamSpeak3_Adapter_Abstract object using this transport. + * + * @return TeamSpeak3_Adapter_Abstract + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Returns the adapter type. + * + * @return string + */ + public function getAdapterType() + { + if($this->adapter instanceof TeamSpeak3_Adapter_Abstract) + { + $string = TeamSpeak3_Helper_String::factory(get_class($this->adapter)); + + return $string->substr($string->findLast("_"))->replace(array("_", " "), "")->toString(); + } + + return "Unknown"; + } + + /** + * Returns header/meta data from stream pointer. + * + * @throws TeamSpeak3_Transport_Exception + * @return array + */ + public function getMetaData() + { + if($this->stream === null) + { + throw new TeamSpeak3_Transport_Exception("unable to retrieve header/meta data from stream pointer"); + } + + return stream_get_meta_data($this->stream); + } + + /** + * Returns TRUE if the transport is connected. + * + * @return boolean + */ + public function isConnected() + { + return (is_resource($this->stream)) ? TRUE : FALSE; + } + + /** + * Blocks a stream until data is available for reading if the stream is connected + * in non-blocking mode. + * + * @param integer $time + * @return void + */ + protected function waitForReadyRead($time = 0) + { + if(!$this->isConnected() || $this->config["blocking"]) return; + + do + { + $read = array($this->stream); + $null = null; + + if($time) + { + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "WaitTimeout", $time, $this->getAdapter()); + } + + $time = $time+$this->config["timeout"]; + } + while(@stream_select($read, $null, $null, $this->config["timeout"]) == 0); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Transport/Exception.php b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/Exception.php new file mode 100644 index 0000000..c7e68eb --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/Exception.php @@ -0,0 +1,32 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Transport_Exception + * @brief Enhanced exception class for TeamSpeak3_Transport_Abstract objects. + */ +class TeamSpeak3_Transport_Exception extends TeamSpeak3_Exception {} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Transport/TCP.php b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/TCP.php new file mode 100644 index 0000000..4ee349b --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/TCP.php @@ -0,0 +1,179 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Transport_TCP + * @brief Class for connecting to a remote server through TCP. + */ +class TeamSpeak3_Transport_TCP extends TeamSpeak3_Transport_Abstract +{ + /** + * Connects to a remote server. + * + * @throws TeamSpeak3_Transport_Exception + * @return void + */ + public function connect() + { + if($this->stream !== null) return; + + $host = strval($this->config["host"]); + $port = strval($this->config["port"]); + + $address = "tcp://" . (strstr($host, ":") !== FALSE ? "[" . $host . "]" : $host) . ":" . $port; + $timeout = (int) $this->config["timeout"]; + + $this->stream = @stream_socket_client($address, $errno, $errstr, $timeout); + + if($this->stream === FALSE) + { + throw new TeamSpeak3_Transport_Exception(TeamSpeak3_Helper_String::factory($errstr)->toUtf8()->toString(), $errno); + } + + @stream_set_timeout($this->stream, $timeout); + @stream_set_blocking($this->stream, $this->config["blocking"] ? 1 : 0); + } + + /** + * Disconnects from a remote server. + * + * @return void + */ + public function disconnect() + { + if($this->stream === null) return; + + $this->stream = null; + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "Disconnected"); + } + + /** + * Reads data from the stream. + * + * @param integer $length + * @throws TeamSpeak3_Transport_Exception + * @return TeamSpeak3_Helper_String + */ + public function read($length = 4096) + { + $this->connect(); + $this->waitForReadyRead(); + + $data = @stream_get_contents($this->stream, $length); + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); + + if($data === FALSE) + { + throw new TeamSpeak3_Transport_Exception("connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "' lost"); + } + + return new TeamSpeak3_Helper_String($data); + } + + /** + * Reads a single line of data from the stream. + * + * @param string $token + * @throws TeamSpeak3_Transport_Exception + * @return TeamSpeak3_Helper_String + */ + public function readLine($token = "\n") + { + $this->connect(); + + $line = TeamSpeak3_Helper_String::factory(""); + + while(!$line->endsWith($token)) + { + $this->waitForReadyRead(); + + $data = @fgets($this->stream, 4096); + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); + + if($data === FALSE) + { + if($line->count()) + { + $line->append($token); + } + else + { + throw new TeamSpeak3_Transport_Exception("connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "' lost"); + } + } + else + { + $line->append($data); + } + } + + return $line->trim(); + } + + /** + * Writes data to the stream. + * + * @param string $data + * @return void + */ + public function send($data) + { + $this->connect(); + + @stream_socket_sendto($this->stream, $data); + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataSend", $data); + } + + /** + * Writes a line of data to the stream. + * + * @param string $data + * @param string $separator + * @return void + */ + public function sendLine($data, $separator = "\n") + { + $size = strlen($data); + $pack = 4096; + + for($seek = 0 ;$seek < $size;) + { + $rest = $size-$seek; + $pack = $rest < $pack ? $rest : $pack; + $buff = substr($data, $seek, $pack); + $seek = $seek+$pack; + + if($seek >= $size) $buff .= $separator; + + $this->send($buff); + } + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Transport/UDP.php b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/UDP.php new file mode 100644 index 0000000..e0df880 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Transport/UDP.php @@ -0,0 +1,113 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Transport_UDP + * @brief Class for connecting to a remote server through UDP. + */ +class TeamSpeak3_Transport_UDP extends TeamSpeak3_Transport_Abstract +{ + /** + * Connects to a remote server. + * + * @throws TeamSpeak3_Transport_Exception + * @return void + */ + public function connect() + { + if($this->stream !== null) return; + + $host = strval($this->config["host"]); + $port = strval($this->config["port"]); + + $address = "udp://" . (strstr($host, ":") !== FALSE ? "[" . $host . "]" : $host) . ":" . $port; + $timeout = (int) $this->config["timeout"]; + + $this->stream = @stream_socket_client($address, $errno, $errstr, $timeout); + + if($this->stream === FALSE) + { + throw new TeamSpeak3_Transport_Exception(TeamSpeak3_Helper_String::factory($errstr)->toUtf8()->toString(), $errno); + } + + @stream_set_timeout($this->stream, $timeout); + @stream_set_blocking($this->stream, $this->config["blocking"] ? 1 : 0); + } + + /** + * Disconnects from a remote server. + * + * @return void + */ + public function disconnect() + { + if($this->stream === null) return; + + $this->stream = null; + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "Disconnected"); + } + + /** + * Reads data from the stream. + * + * @param integer $length + * @throws TeamSpeak3_Transport_Exception + * @return TeamSpeak3_Helper_String + */ + public function read($length = 4096) + { + $this->connect(); + $this->waitForReadyRead(); + + $data = @fread($this->stream, $length); + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataRead", $data); + + if($data === FALSE) + { + throw new TeamSpeak3_Transport_Exception("connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "' lost"); + } + + return new TeamSpeak3_Helper_String($data); + } + + /** + * Writes data to the stream. + * + * @param string $data + * @return void + */ + public function send($data) + { + $this->connect(); + + @stream_socket_sendto($this->stream, $data); + + TeamSpeak3_Helper_Signal::getInstance()->emit(strtolower($this->getAdapterType()) . "DataSend", $data); + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Html.php b/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Html.php new file mode 100644 index 0000000..155fd54 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Html.php @@ -0,0 +1,670 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Viewer_Html + * @brief Renders nodes used in HTML-based TeamSpeak 3 viewers. + */ +class TeamSpeak3_Viewer_Html implements TeamSpeak3_Viewer_Interface +{ + /** + * A pre-defined pattern used to display a node in a TeamSpeak 3 viewer. + * + * @var string + */ + protected $pattern = "
%5%8 %9%11%12
\n"; + + /** + * The TeamSpeak3_Node_Abstract object which is currently processed. + * + * @var TeamSpeak3_Node_Abstract + */ + protected $currObj = null; + + /** + * An array filled with siblingsfor the TeamSpeak3_Node_Abstract object which is currently + * processed. + * + * @var array + */ + protected $currSib = null; + + /** + * An internal counter indicating the number of fetched TeamSpeak3_Node_Abstract objects. + * + * @var integer + */ + protected $currNum = 0; + + /** + * The relative URI path where the images used by the viewer can be found. + * + * @var string + */ + protected $iconpath = null; + + /** + * The relative URI path where the country flag icons used by the viewer can be found. + * + * @var string + */ + protected $flagpath = null; + + /** + * The relative path of the file transter client script on the server. + * + * @var string + */ + protected $ftclient = null; + + /** + * Stores an array of local icon IDs. + * + * @var array + */ + protected $cachedIcons = array(100, 200, 300, 400, 500, 600); + + /** + * Stores an array of remote icon IDs. + * + * @var array + */ + protected $remoteIcons = array(); + + /** + * The TeamSpeak3_Viewer_Html constructor. + * + * @param string $iconpath + * @param string $flagpath + * @param string $ftclient + * @param string $pattern + * @return void + */ + public function __construct($iconpath = "images/viewer/", $flagpath = null, $ftclient = null, $pattern = null) + { + $this->iconpath = $iconpath; + $this->flagpath = $flagpath; + $this->ftclient = $ftclient; + + if($pattern) + { + $this->pattern = $pattern; + } + } + + /** + * Returns the code needed to display a node in a TeamSpeak 3 viewer. + * + * @param TeamSpeak3_Node_Abstract $node + * @param array $siblings + * @return string + */ + public function fetchObject(TeamSpeak3_Node_Abstract $node, array $siblings = array()) + { + $this->currObj = $node; + $this->currSib = $siblings; + + $args = array( + $this->getContainerIdent(), + $this->getContainerClass(), + $this->getContainerSummary(), + $this->getRowClass(), + $this->getPrefixClass(), + $this->getPrefix(), + $this->getCorpusClass(), + $this->getCorpusTitle(), + $this->getCorpusIcon(), + $this->getCorpusName(), + $this->getSuffixClass(), + $this->getSuffixIcon(), + $this->getSuffixFlag(), + ); + + return TeamSpeak3_Helper_String::factory($this->pattern)->arg($args); + } + + /** + * Returns a unique identifier for the current node which can be used as a HTML id + * property. + * + * @return string + */ + protected function getContainerIdent() + { + return $this->currObj->getUniqueId(); + } + + /** + * Returns a dynamic string for the current container element which can be used as + * a HTML class property. + * + * @return string + */ + protected function getContainerClass() + { + return "ts3_viewer " . $this->currObj->getClass(null); + } + + /** + * Returns the ID of the current node which will be used as a summary element for + * the container element. + * + * @return integer + */ + protected function getContainerSummary() + { + return $this->currObj->getId(); + } + + /** + * Returns a dynamic string for the current row element which can be used as a HTML + * class property. + * + * @return string + */ + protected function getRowClass() + { + return ++$this->currNum%2 ? "row1" : "row2"; + } + + /** + * Returns a string for the current prefix element which can be used as a HTML class + * property. + * + * @return string + */ + protected function getPrefixClass() + { + return "prefix " . $this->currObj->getClass(null); + } + + /** + * Returns the HTML img tags to display the prefix of the current node. + * + * @return string + */ + protected function getPrefix() + { + $prefix = ""; + + if(count($this->currSib)) + { + $last = array_pop($this->currSib); + + foreach($this->currSib as $sibling) + { + $prefix .= ($sibling) ? $this->getImage("tree_line.gif") : $this->getImage("tree_blank.png"); + } + + $prefix .= ($last) ? $this->getImage("tree_end.gif") : $this->getImage("tree_mid.gif"); + } + + return $prefix; + } + + /** + * Returns a string for the current corpus element which can be used as a HTML class + * property. If the current node is a channel spacer the class string will contain + * additional class names to allow further customization of the content via CSS. + * + * @return string + */ + protected function getCorpusClass() + { + $extras = ""; + + if($this->currObj instanceof TeamSpeak3_Node_Channel && $this->currObj->isSpacer()) + { + switch($this->currObj->spacerGetType()) + { + case (string) TeamSpeak3::SPACER_SOLIDLINE: + $extras .= " solidline"; + break; + + case (string) TeamSpeak3::SPACER_DASHLINE: + $extras .= " dashline"; + break; + + case (string) TeamSpeak3::SPACER_DASHDOTLINE: + $extras .= " dashdotline"; + break; + + case (string) TeamSpeak3::SPACER_DASHDOTDOTLINE: + $extras .= " dashdotdotline"; + break; + + case (string) TeamSpeak3::SPACER_DOTLINE: + $extras .= " dotline"; + break; + } + + switch($this->currObj->spacerGetAlign()) + { + case TeamSpeak3::SPACER_ALIGN_CENTER: + $extras .= " center"; + break; + + case TeamSpeak3::SPACER_ALIGN_RIGHT: + $extras .= " right"; + break; + + case TeamSpeak3::SPACER_ALIGN_LEFT: + $extras .= " left"; + break; + } + } + + return "corpus " . $this->currObj->getClass(null) . $extras; + } + + /** + * Returns the HTML img tags which can be used to display the various icons for a + * TeamSpeak_Node_Abstract object. + * + * @return string + */ + protected function getCorpusTitle() + { + if($this->currObj instanceof TeamSpeak3_Node_Server) + { + return "ID: " . $this->currObj->getId() . " | Clients: " . $this->currObj->clientCount() . "/" . $this->currObj["virtualserver_maxclients"] . " | Uptime: " . TeamSpeak3_Helper_Convert::seconds($this->currObj["virtualserver_uptime"]); + } + elseif($this->currObj instanceof TeamSpeak3_Node_Channel && !$this->currObj->isSpacer()) + { + return "ID: " . $this->currObj->getId() . " | Codec: " . TeamSpeak3_Helper_Convert::codec($this->currObj["channel_codec"]) . " | Quality: " . $this->currObj["channel_codec_quality"]; + } + elseif($this->currObj instanceof TeamSpeak3_Node_Client) + { + return "ID: " . $this->currObj->getId() . " | Version: " . TeamSpeak3_Helper_Convert::versionShort($this->currObj["client_version"]) . " | Platform: " . $this->currObj["client_platform"]; + } + elseif($this->currObj instanceof TeamSpeak3_Node_Servergroup || $this->currObj instanceof TeamSpeak3_Node_Channelgroup) + { + return "ID: " . $this->currObj->getId() . " | Type: " . TeamSpeak3_Helper_Convert::groupType($this->currObj["type"]) . " (" . ($this->currObj["savedb"] ? "Permanent" : "Temporary") . ")"; + } + } + + /** + * Returns a HTML img tag which can be used to display the status icon for a + * TeamSpeak_Node_Abstract object. + * + * @return string + */ + protected function getCorpusIcon() + { + if($this->currObj instanceof TeamSpeak3_Node_Channel && $this->currObj->isSpacer()) return; + + return $this->getImage($this->currObj->getIcon() . ".png"); + } + + /** + * Returns a string for the current corpus element which contains the display name + * for the current TeamSpeak_Node_Abstract object. + * + * @return string + */ + protected function getCorpusName() + { + if($this->currObj instanceof TeamSpeak3_Node_Channel && $this->currObj->isSpacer()) + { + if($this->currObj->spacerGetType() != TeamSpeak3::SPACER_CUSTOM) return; + + $string = $this->currObj["channel_name"]->section("]", 1, 99); + + if($this->currObj->spacerGetAlign() == TeamSpeak3::SPACER_ALIGN_REPEAT) + { + $string->resize(30, $string); + } + + return htmlspecialchars($string); + } + + if($this->currObj instanceof TeamSpeak3_Node_Client) + { + $before = array(); + $behind = array(); + + foreach($this->currObj->memberOf() as $group) + { + if($group->getProperty("namemode") == TeamSpeak3::GROUP_NAMEMODE_BEFORE) + { + $before[] = "[" . htmlspecialchars($group["name"]) . "]"; + } + elseif($group->getProperty("namemode") == TeamSpeak3::GROUP_NAMEMODE_BEHIND) + { + $behind[] = "[" . htmlspecialchars($group["name"]) . "]"; + } + } + + return implode("", $before) . " " . htmlspecialchars($this->currObj) . " " . implode("", $behind); + } + + return htmlspecialchars($this->currObj); + } + + /** + * Returns a string for the current suffix element which can be used as a HTML + * class property. + * + * @return string + */ + protected function getSuffixClass() + { + return "suffix " . $this->currObj->getClass(null); + } + + /** + * Returns the HTML img tags which can be used to display the various icons for a + * TeamSpeak_Node_Abstract object. + * + * @return string + */ + protected function getSuffixIcon() + { + if($this->currObj instanceof TeamSpeak3_Node_Server) + { + return $this->getSuffixIconServer(); + } + elseif($this->currObj instanceof TeamSpeak3_Node_Channel) + { + return $this->getSuffixIconChannel(); + } + elseif($this->currObj instanceof TeamSpeak3_Node_Client) + { + return $this->getSuffixIconClient(); + } + } + + /** + * Returns the HTML img tags which can be used to display the various icons for a + * TeamSpeak_Node_Server object. + * + * @return string + */ + protected function getSuffixIconServer() + { + $html = ""; + + if($this->currObj["virtualserver_icon_id"]) + { + if(!$this->currObj->iconIsLocal("virtualserver_icon_id") && $this->ftclient) + { + if(!isset($this->cacheIcon[$this->currObj["virtualserver_icon_id"]])) + { + $download = $this->currObj->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->currObj->iconGetName("virtualserver_icon_id")); + + if($this->ftclient == "data:image") + { + $download = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"])->download($download["ftkey"], $download["size"]); + } + + $this->cacheIcon[$this->currObj["virtualserver_icon_id"]] = $download; + } + else + { + $download = $this->cacheIcon[$this->currObj["virtualserver_icon_id"]]; + } + + if($this->ftclient == "data:image") + { + $html .= $this->getImage("data:" . TeamSpeak3_Helper_Convert::imageMimeType($download) . ";base64," . base64_encode($download), "Server Icon", null, FALSE); + } + else + { + $html .= $this->getImage($this->ftclient . "?ftdata=" . base64_encode(serialize($download)), "Server Icon", null, FALSE); + } + } + elseif(in_array($this->currObj["virtualserver_icon_id"], $this->cachedIcons)) + { + $html .= $this->getImage("group_icon_" . $this->currObj["virtualserver_icon_id"] . ".png", "Server Icon"); + } + } + + return $html; + } + + /** + * Returns the HTML img tags which can be used to display the various icons for a + * TeamSpeak_Node_Channel object. + * + * @return string + */ + protected function getSuffixIconChannel() + { + if($this->currObj instanceof TeamSpeak3_Node_Channel && $this->currObj->isSpacer()) return; + + $html = ""; + + if($this->currObj["channel_flag_default"]) + { + $html .= $this->getImage("channel_flag_default.png", "Default Channel"); + } + + if($this->currObj["channel_flag_password"]) + { + $html .= $this->getImage("channel_flag_password.png", "Password-protected"); + } + + if($this->currObj["channel_codec"] == TeamSpeak3::CODEC_CELT_MONO || $this->currObj["channel_codec"] == TeamSpeak3::CODEC_OPUS_MUSIC) + { + $html .= $this->getImage("channel_flag_music.png", "Music Codec"); + } + + if($this->currObj["channel_needed_talk_power"]) + { + $html .= $this->getImage("channel_flag_moderated.png", "Moderated"); + } + + if($this->currObj["channel_icon_id"]) + { + if(!$this->currObj->iconIsLocal("channel_icon_id") && $this->ftclient) + { + if(!isset($this->cacheIcon[$this->currObj["channel_icon_id"]])) + { + $download = $this->currObj->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->currObj->iconGetName("channel_icon_id")); + + if($this->ftclient == "data:image") + { + $download = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"])->download($download["ftkey"], $download["size"]); + } + + $this->cacheIcon[$this->currObj["channel_icon_id"]] = $download; + } + else + { + $download = $this->cacheIcon[$this->currObj["channel_icon_id"]]; + } + + if($this->ftclient == "data:image") + { + $html .= $this->getImage("data:" . TeamSpeak3_Helper_Convert::imageMimeType($download) . ";base64," . base64_encode($download), "Channel Icon", null, FALSE); + } + else + { + $html .= $this->getImage($this->ftclient . "?ftdata=" . base64_encode(serialize($download)), "Channel Icon", null, FALSE); + } + } + elseif(in_array($this->currObj["channel_icon_id"], $this->cachedIcons)) + { + $html .= $this->getImage("group_icon_" . $this->currObj["channel_icon_id"] . ".png", "Channel Icon"); + } + } + + return $html; + } + + /** + * Returns the HTML img tags which can be used to display the various icons for a + * TeamSpeak_Node_Client object. + * + * @return string + */ + protected function getSuffixIconClient() + { + $html = ""; + + if($this->currObj["client_is_priority_speaker"]) + { + $html .= $this->getImage("client_priority.png", "Priority Speaker"); + } + + if($this->currObj["client_is_channel_commander"]) + { + $html .= $this->getImage("client_cc.png", "Channel Commander"); + } + + if($this->currObj["client_is_talker"]) + { + $html .= $this->getImage("client_talker.png", "Talk Power granted"); + } + elseif($cntp = $this->currObj->getParent()->channelGetById($this->currObj["cid"])->channel_needed_talk_power) + { + if($cntp > $this->currObj["client_talk_power"]) + { + $html .= $this->getImage("client_mic_muted.png", "Insufficient Talk Power"); + } + } + + foreach($this->currObj->memberOf() as $group) + { + if(!$group["iconid"]) continue; + + $type = ($group instanceof TeamSpeak3_Node_Servergroup) ? "Server Group" : "Channel Group"; + + if(!$group->iconIsLocal("iconid") && $this->ftclient) + { + if(!isset($this->cacheIcon[$group["iconid"]])) + { + $download = $group->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $group->iconGetName("iconid")); + + if($this->ftclient == "data:image") + { + $download = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"])->download($download["ftkey"], $download["size"]); + } + + $this->cacheIcon[$group["iconid"]] = $download; + } + else + { + $download = $this->cacheIcon[$group["iconid"]]; + } + + if($this->ftclient == "data:image") + { + $html .= $this->getImage("data:" . TeamSpeak3_Helper_Convert::imageMimeType($download) . ";base64," . base64_encode($download), $group . " [" . $type . "]", null, FALSE); + } + else + { + $html .= $this->getImage($this->ftclient . "?ftdata=" . base64_encode(serialize($download)), $group . " [" . $type . "]", null, FALSE); + } + } + elseif(in_array($group["iconid"], $this->cachedIcons)) + { + $html .= $this->getImage("group_icon_" . $group["iconid"] . ".png", $group . " [" . $type . "]"); + } + } + + if($this->currObj["client_icon_id"]) + { + if(!$this->currObj->iconIsLocal("client_icon_id") && $this->ftclient) + { + if(!isset($this->cacheIcon[$this->currObj["client_icon_id"]])) + { + $download = $this->currObj->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->currObj->iconGetName("client_icon_id")); + + if($this->ftclient == "data:image") + { + $download = TeamSpeak3::factory("filetransfer://" . (strstr($download["host"], ":") !== FALSE ? "[" . $download["host"] . "]" : $download["host"]) . ":" . $download["port"])->download($download["ftkey"], $download["size"]); + } + + $this->cacheIcon[$this->currObj["client_icon_id"]] = $download; + } + else + { + $download = $this->cacheIcon[$this->currObj["client_icon_id"]]; + } + + if($this->ftclient == "data:image") + { + $html .= $this->getImage("data:" . TeamSpeak3_Helper_Convert::imageMimeType($download) . ";base64," . base64_encode($download), "Client Icon", null, FALSE); + } + else + { + $html .= $this->getImage($this->ftclient . "?ftdata=" . base64_encode(serialize($download)), "Client Icon", null, FALSE); + } + } + elseif(in_array($this->currObj["client_icon_id"], $this->cachedIcons)) + { + $html .= $this->getImage("group_icon_" . $this->currObj["client_icon_id"] . ".png", "Client Icon"); + } + } + + return $html; + } + + /** + * Returns a HTML img tag which can be used to display the country flag for a + * TeamSpeak_Node_Client object. + * + * @return string + */ + protected function getSuffixFlag() + { + if(!$this->currObj instanceof TeamSpeak3_Node_Client) return; + + if($this->flagpath && $this->currObj["client_country"]) + { + return $this->getImage($this->currObj["client_country"]->toLower() . ".png", $this->currObj["client_country"], null, FALSE, TRUE); + } + } + + /** + * Returns the code to display a custom HTML img tag. + * + * @param string $name + * @param string $text + * @param string $class + * @param boolean $iconpath + * @param boolean $flagpath + * @return string + */ + protected function getImage($name, $text = "", $class = null, $iconpath = TRUE, $flagpath = FALSE) + { + $src = ""; + + if($iconpath) + { + $src = $this->iconpath; + } + + if($flagpath) + { + $src = $this->flagpath; + } + + return ""; + } +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Interface.php b/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Interface.php new file mode 100644 index 0000000..62eed20 --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Interface.php @@ -0,0 +1,42 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Viewer_Interface + * @brief Interface class describing a TeamSpeak 3 viewer. + */ +interface TeamSpeak3_Viewer_Interface +{ + /** + * Returns the code needed to display a node in a TeamSpeak 3 viewer. + * + * @param TeamSpeak3_Node_Abstract $node + * @param array $siblings + * @return string + */ + public function fetchObject(TeamSpeak3_Node_Abstract $node, array $siblings = array()); +} diff --git a/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Text.php b/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Text.php new file mode 100644 index 0000000..eecd5ee --- /dev/null +++ b/lib/ts3phpframework/libraries/TeamSpeak3/Viewer/Text.php @@ -0,0 +1,107 @@ +. + * + * @package TeamSpeak3 + * @version 1.1.24 + * @author Sven 'ScP' Paulsen + * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved. + */ + +/** + * @class TeamSpeak3_Viewer_Text + * @brief Renders nodes used in ASCII-based TeamSpeak 3 viewers. + */ +class TeamSpeak3_Viewer_Text implements TeamSpeak3_Viewer_Interface +{ + /** + * A pre-defined pattern used to display a node in a TeamSpeak 3 viewer. + * + * @var string + */ + protected $pattern = "%0%1 %2\n"; + + /** + * Returns the code needed to display a node in a TeamSpeak 3 viewer. + * + * @param TeamSpeak3_Node_Abstract $node + * @param array $siblings + * @return string + */ + public function fetchObject(TeamSpeak3_Node_Abstract $node, array $siblings = array()) + { + $this->currObj = $node; + $this->currSib = $siblings; + + $args = array( + $this->getPrefix(), + $this->getCorpusIcon(), + $this->getCorpusName(), + ); + + return TeamSpeak3_Helper_String::factory($this->pattern)->arg($args); + } + + /** + * Returns the ASCII string to display the prefix of the current node. + * + * @return string + */ + protected function getPrefix() + { + $prefix = ""; + + if(count($this->currSib)) + { + $last = array_pop($this->currSib); + + foreach($this->currSib as $sibling) + { + $prefix .= ($sibling) ? "| " : " "; + } + + $prefix .= ($last) ? "\\-" : "|-"; + } + + return $prefix; + } + + /** + * Returns an ASCII string which can be used to display the status icon for a + * TeamSpeak_Node_Abstract object. + * + * @return string + */ + protected function getCorpusIcon() + { + return $this->currObj->getSymbol(); + } + + /** + * Returns a string for the current corpus element which contains the display name + * for the current TeamSpeak_Node_Abstract object. + * + * @return string + */ + protected function getCorpusName() + { + return $this->currObj; + } +} diff --git a/news/README.txt b/news/README.txt new file mode 100644 index 0000000..5367c22 --- /dev/null +++ b/news/README.txt @@ -0,0 +1,15 @@ +Informacje na temat systemu newsów: + +Jeśli chcesz, możesz dodawać, zmieniać oraz usuwać newsy w domyślnym folderze news. Folder ten jest do zmiany w pliku config.php +Aktualnie newsy są tworzone przy użyciu Markdown mieszanego z HTMLem. Może potem dodam jakiś panel jak mi się zachcę Xd + +Prosty poradnik na temat Markdown od GitHuba: https://guides.github.com/features/mastering-markdown/ + +Przydatne informacje: +- Newsy wczytywane są z folderu w kolejności alfabetycznej +- Każdy plik z newsami musi mieć rozszerzenie .md (Markdown) +- Format plikow z newsami: + - Pierwsza linijka: tytuł newsa + - Druga linijka: autor i data + - Trzecia linijka: pusta (taki seperator) + - Reszta pliku: treść newsa (Markdown + HTML) diff --git a/news/news1.md b/news/news1.md new file mode 100644 index 0000000..e9ed224 --- /dev/null +++ b/news/news1.md @@ -0,0 +1,14 @@ +Informacje na temat systemu newsów +Wruczek, 2016-06-24, 21:00 + +Jeśli chcesz, możesz dodawać, zmieniać oraz usuwać newsy w domyślnym folderze news. Folder ten możesz zmienić w pliku config.php +Aktualnie newsy są tworzone przy użyciu Markdown mieszanego z HTMLem. Może potem dodam jakiś panel, a na razie polecam [prosty poradnik na temat Markdown od GitHuba](https://guides.github.com/features/mastering-markdown/). + +Przydatne informacje: +- Newsy wczytywane są z folderu w kolejności alfabetycznej +- Każdy plik z newsami musi mieć rozszerzenie .md (Markdown) +- Format plikow z newsami: + - Pierwsza linijka: tytuł newsa + - Druga linijka: autor i data + - Trzecia linijka: pusta (taki seperator) + - Reszta pliku: treść newsa (Markdown + HTML) diff --git a/news/news2.md b/news/news2.md new file mode 100644 index 0000000..096d819 --- /dev/null +++ b/news/news2.md @@ -0,0 +1,378 @@ +Readme od jQuery - do testów systemu +jQuery, 2016-06-24, 21:00 + +[jQuery](https://jquery.com/) — New Wave JavaScript +================================================== + +Contribution Guides +-------------------------------------- + +In the spirit of open source software development, jQuery always encourages community code contribution. To help you get started and before you jump into writing code, be sure to read these important contribution guidelines thoroughly: + +1. [Getting Involved](https://contribute.jquery.org/) +2. [Core Style Guide](https://contribute.jquery.org/style-guide/js/) +3. [Writing Code for jQuery Foundation Projects](https://contribute.jquery.org/code/) + + +Environments in which to use jQuery +-------------------------------------- + +- [Browser support](https://jquery.com/browser-support/) +- jQuery also supports Node, browser extensions, and other non-browser environments. + + +What you need to build your own jQuery +-------------------------------------- + +In order to build jQuery, you need to have the latest Node.js/npm and git 1.7 or later. Earlier versions might work, but are not supported. + +For Windows, you have to download and install [git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/en/download/). + +OS X users should install [Homebrew](http://brew.sh/). Once Homebrew is installed, run `brew install git` to install git, +and `brew install node` to install Node.js. + +Linux/BSD users should use their appropriate package managers to install git and Node.js, or build from source +if you swing that way. Easy-peasy. + + +How to build your own jQuery +---------------------------- + +Clone a copy of the main jQuery git repo by running: + +```bash +git clone git://github.com/jquery/jquery.git +``` + +Enter the jquery directory and run the build script: +```bash +cd jquery && npm run build +``` +The built version of jQuery will be put in the `dist/` subdirectory, along with the minified copy and associated map file. + +If you want to create custom build or help with jQuery development, it would be better to install [grunt command line interface](https://github.com/gruntjs/grunt-cli) as a global package: + +``` +npm install -g grunt-cli +``` +Make sure you have `grunt` installed by testing: +``` +grunt -V +``` + +Now by running the `grunt` command, in the jquery directory, you can build a full version of jQuery, just like with an `npm run build` command: +``` +grunt +``` + +There are many other tasks available for jQuery Core: +``` +grunt -help +``` + +### Modules + +Special builds can be created that exclude subsets of jQuery functionality. +This allows for smaller custom builds when the builder is certain that those parts of jQuery are not being used. +For example, an app that only used JSONP for `$.ajax()` and did not need to calculate offsets or positions of elements could exclude the offset and ajax/xhr modules. + +Any module may be excluded except for `core`, and `selector`. To exclude a module, pass its path relative to the `src` folder (without the `.js` extension). + +Some example modules that can be excluded are: + +- **ajax**: All AJAX functionality: `$.ajax()`, `$.get()`, `$.post()`, `$.ajaxSetup()`, `.load()`, transports, and ajax event shorthands such as `.ajaxStart()`. +- **ajax/xhr**: The XMLHTTPRequest AJAX transport only. +- **ajax/script**: The `