<?php

/**
 * @file
 * TeamSpeak 3 PHP Framework
 *
 * $Id: ServerQuery.php 10/11/2013 11:35:21 scp@orilla $
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * @package   TeamSpeak3
 * @version   1.1.23
 * @author    Sven 'ScP' Paulsen
 * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved.
 */

namespace TeamSpeak3\Adapter;

use TeamSpeak3\TeamSpeak3;
use TeamSpeak3\Ts3Exception;
use TeamSpeak3\Adapter\ServerQuery\Event;
use TeamSpeak3\Adapter\ServerQuery\Reply;
use TeamSpeak3\Node\Host;
use TeamSpeak3\Helper\Profiler;
use TeamSpeak3\Helper\Signal;
use TeamSpeak3\Transport\AbstractTransport;
use TeamSpeak3\Helper\StringHelper;
use TeamSpeak3\Node\AbstractNode;

/**
 * @class ServerQuery
 * @brief Provides low-level methods for ServerQuery communication with a TeamSpeak 3 Server.
 */
class ServerQuery extends AbstractAdapter
{
    /**
     * Stores a singleton instance of the active Host object.
     *
     * @var 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 AbstractTransport object and performs initial actions on the remote
     * server.
     *
     * @throws Ts3Exception
     * @return void
     */
    protected function syn()
    {
        $this->initTransport($this->options);
        $this->transport->setAdapter($this);

        Profiler::init(spl_object_hash($this));

        if (!$this->getTransport()->readLine()->startsWith(TeamSpeak3::READY)) {
            throw new Ts3Exception("invalid reply from the server");
        }

        Signal::getInstance()->emit("serverqueryConnected", $this);
    }

    /**
     * The ServerQuery destructor.
     *
     * @return void
     */
    public function __destruct()
    {
        if ($this->getTransport() instanceof AbstractTransport && $this->transport->isConnected()) {
            try {
                $this->request("quit");
            } catch (Ts3Exception $e) {
                return;
            }
        }
    }

    /**
     * Sends a prepared command to the server and returns the result.
     *
     * @param  string $cmd
     * @param  boolean $throw
     * @throws Ts3Exception
     * @throws Ts3Exception
     * @return Reply
     */
    public function request($cmd, $throw = true)
    {
        $query = StringHelper::factory($cmd)->section(TeamSpeak3::SEPARATOR_CELL);

        if (strstr($cmd, "\r") || strstr($cmd, "\n")) {
            throw new Ts3Exception("illegal characters in command '" . $query . "'");
        } elseif (in_array($query, $this->block)) {
            throw new Ts3Exception("command not found", 0x100);
        }

        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 StringHelper && $str->section(
                TeamSpeak3::SEPARATOR_CELL
            ) != TeamSpeak3::ERROR);

        $this->getProfiler()->stop();

        $reply = new Reply($rpl, $cmd, $this->getHost(), $throw);

        Signal::getInstance()->emit("serverqueryCommandFinished", $cmd, $reply);

        return $reply;
    }

    /**
     * Waits for the server to send a notification message and returns the result.
     *
     * @throws Ts3Exception
     * @return Event
     */
    public function wait()
    {
        if ($this->getTransport()->getConfig("blocking")) {
            throw new Ts3Exception("only available in non-blocking mode");
        }

        do {
            $evt = $this->getTransport()->readLine();
            var_dump($evt);
        } while ($evt instanceof StringHelper &&
                !$evt->section(TeamSpeak3::SEPARATOR_CELL)->startsWith(TeamSpeak3::EVENT));

        return new 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 AbstractNode) {
                        $value[$i] = $value[$i]->getId();
                    }

                    $cells[$i][] = $ident . StringHelper::factory($value[$i])->escape()->toUtf8();
                }
            } else {
                if ($value === null) {
                    continue;
                } elseif ($value === false) {
                    $value = 0x00;
                } elseif ($value === true) {
                    $value = 0x01;
                } elseif ($value instanceof AbstractNode) {
                    $value = $value->getId();
                }

                $args[] = $ident . StringHelper::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 Host object of the current connection.
     *
     * @return Host
     */
    public function getHost()
    {
        if ($this->host === null) {
            $this->host = new Host($this);
        }

        return $this->host;
    }
}


<?php

namespace yii\adminUi\widget;

use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;

/**
 * Column renders a Bootstrap Grid component.
 *
 * @see http://getbootstrap.com/css/#grid
 * @author Mithun Mandal <mithun12000@gmail.com>
 * @since 2.0
 */
class Column extends Widget
{
    const SIZE_FULL = 12;
    const SIZE_HALF = 6;
    const SIZE_BIG = 8;
    const SIZE_SMALL = 4;
    const SIZE_DEFAULT = 12;

    const TYPE_MOBILE = 'col-xs-';
    const TYPE_TABLET = 'col-sm-';
    const TYPE_BIG = 'col-lg-';
    const TYPE_DESKTOP = 'col-md-';

    const DEFAULT_TYPE = 'col-md-';

    /**
     * @var array the grid
     * example: [
     *      [
     *          type => self::TYPE_DESKTOP,
     *          size => self::SIZE_SMALL
     *      ],
     *      [
     *          type => self::TYPE_MOBILE,
     *          size => self::SIZE_FULL
     *      ],
     *      [
     *          type => self::TYPE_MOBILE,
     *          size => 3,
     *          positiontype => 'pull',     options are pull|push|offset
     *      ],
     * ]
     */
    public $grid;

    /**
     * Initializes the widget.
     */
    public function init()
    {
        parent::init();
        $this->initOptions();

        echo Html::beginTag('div', $this->options) . "\n";
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        echo "\n" . Html::endTag('div');
    }

    /**
     * Initializes the widget options.
     * This method sets the default values for various options.
     */
    protected function initOptions()
    {
        if($this->grid){
            $this->options = array_merge([
            'class' => $this->getGridClass().isset($this->options['class']) ? ' '.$this->options['class'] : '',
            ], $this->options);
        }else{
            $this->options = array_merge([
            'class' => self::DEFAULT_TYPE.self::SIZE_DEFAULT,
            ], $this->options);
        }
    }

    /**
     * Generate Grid Class for Div element
     * @return string Html Class Value
     */
    private function getGridClass() {
        $class = [];
        foreach($this->grid as $grid){
            if(!isset($grid['type']) || !isset($grid['size'])){
                throw new InvalidConfigException(get_called_class() . ' must have type and size.');
            }
            if(!isset($grid['positiontype']))
                $class[] = $grid['type'].$grid['size'];
            else
                $class[] = $grid['type'].$grid['positiontype'].'-'.$grid['size'];
        }
        return implode(' ',$class);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\adminUi\widget;

use Yii;
use yii\adminUi\assetsBundle\AdminUiAsset as AdminUiAsset;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;

/**
 * Header renders a Header HTML component.
 *
 * Any content enclosed between the [[begin()]] and [[end()]] calls of NavBar
 * is treated as the content of the navbar. You may use widgets such as [[Nav]]
 * or [[\yii\widgets\Menu]] to build up such content. For example,
 *
 * ```php
 * use yii\bootstrap\NavBar;
 * use yii\widgets\Menu;
 *
 * NavBar::begin(['brandLabel' => 'NavBar Test']);
 * echo Nav::widget([
 *     'items' => [
 *         ['label' => 'Home', 'url' => ['/site/index']],
 *         ['label' => 'About', 'url' => ['/site/about']],
 *     ],
 * ]);
 * NavBar::end();
 * ```
 *
 * @see http://getbootstrap.com/components/#navbar
 * @author Antonio Ramirez <amigo.cobos@gmail.com>
 * @author Alexander Kochetov <creocoder@gmail.com>
 * @since 2.0
 */
class Header extends Widget
{
    /**
     * @var array the HTML attributes for the widget container tag. The following special options are recognized:
     *
     * - tag: string, defaults to "nav", the name of the container tag.
     *
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $options = [];
    /**
     * @var array the HTML attributes for the container tag. The following special options are recognized:
     *
     * - tag: string, defaults to "div", the name of the container tag.
     *
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $containerOptions = [];
    /**
     * @var string the text of the brand. Note that this is not HTML-encoded.
     * @see http://getbootstrap.com/components/#navbar
     */
    public $brandLabel;
    /**
     * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Url::to()]]
     * and will be used for the "href" attribute of the brand link. If not set, [[\yii\web\Application::homeUrl]] will be used.
     */
    public $brandUrl;
    /**
     * @var array the HTML attributes of the brand link.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $brandOptions = [];
    /**
     * @var string text to show for screen readers for the button to toggle the navbar.
     */
    public $screenReaderToggleText = 'Toggle navigation';
    /**
     * @var boolean whether the navbar content should be included in an inner div container which by default
     * adds left and right padding. Set this to false for a 100% width navbar.
     */
    public $renderInnerContainer = false;
    /**
     * @var array the HTML attributes of the inner container.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $innerContainerOptions = [];

    /**
     * Initializes the widget.
     */
    public function init()
    {
        parent::init();
        $this->clientOptions = false;

        $options = $this->options;
        $tag = ArrayHelper::remove($options, 'tag', 'nav');
        echo Html::beginTag($tag, $options);
        if ($this->brandLabel !== null) {
            Html::addCssClass($this->brandOptions, 'logo');
            echo Html::a($this->brandLabel, $this->brandUrl === null ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions);
        }
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
        echo Html::endTag($tag);
        AdminUiAsset::register($this->getView());
    }
}


<?php

/**
 * @file
 * TeamSpeak 3 PHP Framework
 *
 * $Id: Channel.php 10/11/2013 11:35:21 scp@orilla $
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * @package   TeamSpeak3
 * @version   1.1.23
 * @author    Sven 'ScP' Paulsen
 * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved.
 */

namespace TeamSpeak3\Node;

use TeamSpeak3\TeamSpeak3;

/**
 * @class Channel
 * @brief Class describing a TeamSpeak 3 channel and all it's parameters.
 */
class Channel extends AbstractNode
{
    /**
     * The Channel constructor.
     *
     * @param  Server $server
     * @param  array $info
     * @param  string $index
     * @throws TeamSpeak3_Adapter_ServerQuery_Exception
     * @return Channel
     */
    public function __construct(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 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 Channel object matching the given ID.
     *
     * @param  integer $cid
     * @throws TeamSpeak3_Adapter_ServerQuery_Exception
     * @return 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 Channel object matching the given name.
     *
     * @param  integer $name
     * @throws TeamSpeak3_Adapter_ServerQuery_Exception
     * @return 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 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 Client object matching the given ID.
     *
     * @param  integer $clid
     * @throws TeamSpeak3_Adapter_ServerQuery_Exception
     * @return 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 Client object matching the given name.
     *
     * @param  integer $name
     * @throws TeamSpeak3_Adapter_ServerQuery_Exception
     * @return 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 void
     */
    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)
    {
        return $this->getParent()->channelClientPermAssign($this->getId(), $cldbid, $permid, $permvalue);
    }

    /**
     * Alias for clientPermAssign().
     *
     * @deprecated
     */
    public function clientPermAssignByName($cldbid, $permname, $permvalue)
    {
        return $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)
    {
        return $this->getParent()->channelClientPermRemove($this->getId(), $cldbid, $permid);
    }

    /**
     * Alias for clientPermRemove().
     *
     * @deprecated
     */
    public function clientPermRemoveByName($cldbid, $permname)
    {
        return $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)
    {
        return $this->getParent()->channelPermAssign($this->getId(), $permid, $permvalue);
    }

    /**
     * Alias for permAssign().
     *
     * @deprecated
     */
    public function permAssignByName($permname, $permvalue)
    {
        return $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)
    {
        return $this->getParent()->channelPermRemove($this->getId(), $permid);
    }

    /**
     * Alias for permRemove().
     *
     * @deprecated
     */
    public function permRemoveByName($permname)
    {
        return $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 void
     */
    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)
    {
        return $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 = "/")
    {
        return $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 = "/")
    {
        return $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 StringHelper
     */
    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://" . $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);
    }

    /**
     * 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"];
    }
}


<?php

/**
 * @file
 * TeamSpeak 3 PHP Framework
 *
 * $Id: Host.php 10/11/2013 11:35:21 scp@orilla $
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * @package   TeamSpeak3
 * @version   1.1.23
 * @author    Sven 'ScP' Paulsen
 * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved.
 */

namespace TeamSpeak3\Node;

use TeamSpeak3\Adapter\ServerQuery;
use TeamSpeak3\Helper\Convert;
use TeamSpeak3\Helper\Crypt;
use TeamSpeak3\Helper\Signal;
use TeamSpeak3\Helper\StringHelper;
use TeamSpeak3\Helper\Uri;
use TeamSpeak3\TeamSpeak3;
use TeamSpeak3\Ts3Exception;

/**
 * @class Host
 * @brief Class describing a TeamSpeak 3 server instance and all it's parameters.
 */
class Host extends AbstractNode
{
    /**
     * @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 Host constructor.
     *
     * @param  ServerQuery $squery
     * @return Host
     */
    public function __construct(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 && array_key_exists($ident, $this->version)) ? $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));

        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));

        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
     * @throws \Teamspeak3\Ts3Exception
     * @return integer
     */
    public function serverGetPortById($sid)
    {
        if (!array_key_exists((string)$sid, $this->serverList())) {
            throw new Ts3Exception("invalid serverID", 0x400);
        }

        return $this->serverList[intval((string)$sid)]["virtualserver_port"];
    }

    /**
     * Returns the Server object matching the currently selected ID.
     *
     * @return Server
     */
    public function serverGetSelected()
    {
        return $this->serverGetById($this->serverSelectedId());
    }

    /**
     * Returns the Server object matching the given ID.
     *
     * @param  integer $sid
     * @return Server
     */
    public function serverGetById($sid)
    {
        $this->serverSelectById($sid);

        return new Server($this, array("virtualserver_id" => intval($sid)));
    }

    /**
     * Returns the Server object matching the given port number.
     *
     * @param  integer $port
     * @return Server
     */
    public function serverGetByPort($port)
    {
        $this->serverSelectByPort($port);

        return new Server($this, array("virtualserver_id" => $this->serverSelectedId()));
    }

    /**
     * Returns the first Server object matching the given name.
     *
     * @param  string $name
     * @throws Ts3Exception
     * @return Server
     */
    public function serverGetByName($name)
    {
        foreach ($this->serverList() as $server) {
            if ($server["virtualserver_name"] == $name) {
                return $server;
            }
        }

        throw new Ts3Exception("invalid serverID", 0x400);
    }

    /**
     * Returns the first Server object matching the given unique identifier.
     *
     * @param  string $uid
     * @throws Ts3Exception
     * @return Server
     */
    public function serverGetByUid($uid)
    {
        foreach ($this->serverList() as $server) {
            if ($server["virtualserver_unique_identifier"] == $uid) {
                return $server;
            }
        }

        throw new Ts3Exception("invalid serverID", 0x400);
    }

    /**
     * Returns the first 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 Ts3Exception
     * @return Server
     */
    public function serverGetByTSDNS($tsdns)
    {
        $parts = Uri::getFQDNParts($tsdns);
        $query = StringHelper::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 (Ts3Exception $e) {
                /* skip "Connection timed out" and "Connection refused" */
                if ($e->getCode() != 10060 && $e->getCode() != 10061) {
                    throw $e;
                }
            }
        }

        throw new Ts3Exception("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 Server($this, array("virtualserver_id" => intval($detail["sid"])));

        Signal::getInstance()->emit("notifyServercreated", $this, $detail["sid"]);
        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));

        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();

        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();

        Signal::getInstance()->emit("notifyServerstopped", $this, $sid);
    }

    /**
     * Stops the entire TeamSpeak 3 Server instance by shutting down the process.
     *
     * @return void
     */
    public function serverStopProcess()
    {
        Signal::getInstance()->emit("notifyServershutdown", $this);

        $this->execute("serverprocessstop");
    }

    /**
     * Returns an array filled with 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 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()
    {
        return $this->request("bindinglist")->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"] = StringHelper::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"] = StringHelper::factory(
                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 Ts3Exception
     * @return integer
     */
    public function permissionGetIdByName($name)
    {
        if (!array_key_exists((string)$name, $this->permissionList())) {
            throw new Ts3Exception("invalid permission ID", 0xA02);
        }

        return $this->permissionList[(string)$name]["permid"];
    }

    /**
     * Returns the name of the permission matching the given ID.
     *
     * @param  integer $permid
     * @throws Ts3Exception
     * @return StringHelper
     */
    public function permissionGetNameById($permid)
    {
        foreach ($this->permissionList() as $name => $perm) {
            if ($perm["permid"] == $permid) {
                return new Ts3Exception($name);
            }
        }

        throw new Ts3Exception("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 bool|int $permnegated
     * @param bool|int $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 Crypt($username);

        $this->setStorage("_login_user", $username);
        $this->setStorage("_login_pass", $crypt->encrypt($password));

        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");

        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)) ? intval($value) : StringHelper::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 (!StringHelper::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 ServerQuery object.
     *
     * @return 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 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);

            call_user_func_array(array($this, $func), $args);
        }
    }

    /**
     * Returns a string representation of this node.
     *
     * @return string
     */
    public function __toString()
    {
        return (string)$this->getAdapterHost();
    }
}


<?php

/**
 * @file
 * TeamSpeak 3 PHP Framework
 *
 * $Id: Text.php 10/11/2013 11:35:22 scp@orilla $
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * @package   TeamSpeak3
 * @version   1.1.23
 * @author    Sven 'ScP' Paulsen
 * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved.
 */

namespace TeamSpeak3\Viewer;

use TeamSpeak3\TeamSpeak3;
use TeamSpeak3\Helper\StringHelper;
use TeamSpeak3\Helper\Convert;
use TeamSpeak3\Node\AbstractNode;
use TeamSpeak3\Node\Server;
use TeamSpeak3\Node\Servergroup;
use TeamSpeak3\Node\Channel;
use TeamSpeak3\Node\Channelgroup;
use TeamSpeak3\Node\Client;

/**
 * @class Html
 * @brief Renders nodes used in HTML-based TeamSpeak 3 viewers.
 */
class Html implements IViewer
{
    /**
     * 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 AbstractNode object which is currently processed.
     *
     * @var AbstractNode
     */
    protected $currObj = null;

    /**
     * An array filled with siblingsfor the  AbstractNode object which is currently
     * processed.
     *
     * @var array
     */
    protected $currSib = null;

    /**
     * An internal counter indicating the number of fetched AbstractNode 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 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  AbstractNode $node
     * @param  array $siblings
     * @return StringHelper
     */
    public function fetchObject(AbstractNode $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 StringHelper::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 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 Server) {
            return "ID: " . $this->currObj->getId() . " | Clients: " . $this->currObj->clientCount(
            ) . "/" . $this->currObj["virtualserver_maxclients"] . " | Uptime: " . Convert::seconds(
                $this->currObj["virtualserver_uptime"]
            );
        } elseif ($this->currObj instanceof Channel && !$this->currObj->isSpacer()) {
            return "ID: " . $this->currObj->getId() . " | Codec: " . Convert::codec(
                $this->currObj["channel_codec"]
            ) . " | Quality: " . $this->currObj["channel_codec_quality"];
        } elseif ($this->currObj instanceof Client) {
            return "ID: " . $this->currObj->getId() . " | Version: " . Convert::versionShort(
                $this->currObj["client_version"]
            ) . " | Platform: " . $this->currObj["client_platform"];
        } elseif ($this->currObj instanceof Servergroup || $this->currObj instanceof Channelgroup) {
            return "ID: " . $this->currObj->getId() . " | Type: " . Convert::groupType(
                $this->currObj["type"]
            ) . " (" . ($this->currObj["savedb"] ? "Permanent" : "Temporary") . ")";
        }
	    return "";
    }

    /**
     * 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 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 Channel && $this->currObj->isSpacer()) {
            if ($this->currObj->spacerGetType() != TeamSpeak3::SPACER_CUSTOM) {
                return "";
            }

            $string = $this->currObj["channel_name"]->section("]", 1, 99);

            if ($this->currObj->spacerGetAlign() == TeamSpeak3::SPACER_ALIGN_REPEAT) {
                $string->resize(30, $string);
            }

            return htmlspecialchars($string);
        }

        if ($this->currObj instanceof Client) {
            $before = array();
            $behind = array();

	        /**
	         * @var Channelgroup|Servergroup $group
	         */
	        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 Server) {
            return $this->getSuffixIconServer();
        } elseif ($this->currObj instanceof Channel) {
            return $this->getSuffixIconChannel();
        } elseif ($this->currObj instanceof Client) {
            return $this->getSuffixIconClient();
        }
	    return "";
    }

    /**
     * Returns the HTML img tags which can be used to display the various icons for a
     * TeamSpeak_Node_Server object.
     *
     * @return string
     */
    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://" . $download["host"] . ":" . $download["port"]
                        )->download($download["ftkey"], $download["size"]);
                    }

                    $this->cacheIcon[$this->currObj["virtualserver_icon_id"]] = $download;
                } else {
                    $download = $this->cacheIcon[$this->currObj["virtualserver_icon_id"]];
                }

                if ($this->ftclient == "data:image") {
                    $html .= $this->getImage(
                        "data:" . Convert::imageMimeType($download) . ";base64," . base64_encode(
                            $download
                        ),
                        "Server Icon",
                        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 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://" . $download["host"] . ":" . $download["port"]
                        )->download($download["ftkey"], $download["size"]);
                    }

                    $this->cacheIcon[$this->currObj["channel_icon_id"]] = $download;
                } else {
                    $download = $this->cacheIcon[$this->currObj["channel_icon_id"]];
                }

                if ($this->ftclient == "data:image") {
                    $html .= $this->getImage(
                        "data:" . Convert::imageMimeType($download) . ";base64," . base64_encode(
                            $download
                        ),
                        "Channel Icon",
                        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 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://" . $download["host"] . ":" . $download["port"]
                        )->download($download["ftkey"], $download["size"]);
                    }

                    $this->cacheIcon[$group["iconid"]] = $download;
                } else {
                    $download = $this->cacheIcon[$group["iconid"]];
                }

                if ($this->ftclient == "data:image") {
                    $html .= $this->getImage(
                        "data:" . Convert::imageMimeType($download) . ";base64," . base64_encode(
                            $download
                        ),
                        $group . " [" . $type . "]",
                        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://" . $download["host"] . ":" . $download["port"]
                        )->download($download["ftkey"], $download["size"]);
                    }

                    $this->cacheIcon[$this->currObj["client_icon_id"]] = $download;
                } else {
                    $download = $this->cacheIcon[$this->currObj["client_icon_id"]];
                }

                if ($this->ftclient == "data:image") {
                    $html .= $this->getImage(
                        "data:" . Convert::imageMimeType($download) . ";base64," . base64_encode(
                            $download
                        ),
                        "Client Icon",
                        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 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
            );
        }
	    return "";
    }

    /**
     * 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 "";
    }
}