Merge remote-tracking branch 'refs/remotes/ManiaControl/master'

This commit is contained in:
Tom Valk 2015-11-16 16:58:31 +01:00
commit 42f3846ce7
110 changed files with 5847 additions and 727 deletions

View File

@ -54,6 +54,7 @@
<option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="true" />
<option name="SPACE_AFTER_TYPE_CAST" value="true" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
@ -72,5 +73,4 @@
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>
</project>

View File

@ -3,5 +3,4 @@
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
</project>

View File

@ -34,11 +34,11 @@ http://www.maniacontrol.com
## REQUIREMENTS:
- MySQL Database
- PHP 5.4+
- Needed extensions:
- Needed extensions (on ManiaControl startup you will see if you have them activated):
- php_mysqli
- php_curl
- php_xmlrpc (TM only)
- php_xmlrpc (TM only, recomended for SM)
- php_zlib
### How to report bugs or request features?:
- Write a mail to bugs(at)maniacontrol(dot)com

60
changelog.txt Normal file
View File

@ -0,0 +1,60 @@
###v0.163###
#Additions
- completely reworked the filereader (new version, way more flexible), old methods are still working but deprecated
- added pause command and vote command for other gamemodes than elite (especially Chase/Combo)
- added Scriptcallbacks SCORESREADY / SCORES
- added SSL support as well as http Redirections of the FileReader
# Bug fixes
- Banning of not connected Players now possible
###v0.162###
#Additions
- added typhinting ladderStat in Player object
- added optional AsynchronousFileReader Parameter for loadFile and postFile to set additional Headers
- added ServerLogin header for Mania-Exchange downloads
###v0.161###
#Additions
- added admin chatcommand //uptime which displays the time since when the server is running
- updated playerhitstructure with new properties
#Bug Fixes
- fixed some z positions to be in front of overlays (especially in Trackmania)
- fixed limit problem on maniaexchange list
###v0.16###
#Additions
- added changelog
- added CommunicationManager which acts like a communication interface you can connect to and interact with ManiaControl (also thanks to TGYoshi for some help)
- You can call ManiaControl from a Website or from ManiaControl itself
- added "//removerights login" command
- added new EchoManager which handles Interactions between different Controllers
- It is possible to send an Echo command via the Method sendEcho, as message Parameter strings, objects or arrays can get used
- An EchoListener can be added, callable by closure or implicit use (like on callbacks)
- The Listener always returns either an Stringer or an Object back as Param (arrays get converted into objects)
- On sending an Echo Message from another controller you need to provide an string to the dedicated method or an json_encoded array or object
- 4 Echos are Implemented by ManiaControl (ManiaControl.Restart, ManiaControl.AuthenticationManager.GrandLevel, ManiaControl.AuthenticationManager.RevokeLevel, ManiaControl.PlayerManager.WarnPlayer)
- added Method getServerLoginByIndex to Server object
- added to PlayerManager's Method PlayerManager the Parameter "withoutBots" (default on true)
- added Method getSpectators() in PlayerManager
- added Method restartMap(), skipToMapByMxId(), skipToMapByUid() into MapActions
- added some missing PHP Docs
- added some depency libraries as they are used by the Socket Handler
- added additional Callback which gets triggered on ManiaControl Restart
- added class name to the BillData object
- updated some depency libraries
#Bug Fixes
- fixed TriggerDebugNotice Method
- Exception fix on kicking Players
- updated FaultException List
###v0.157###
- labelline improvements
- new usage examples:
$positions = array($posX + 5, $posX + 18, $posX + 70);
$texts = array($index, $admin->nickname, $admin->login);
$this->maniaControl->getManialinkManager()->labelLine($playerFrame, array($positions, $texts));
- improvements on Billmanager, added receiver to the BillData
- increased timeout time (fixes crashes on speedball)

View File

@ -290,7 +290,7 @@ class ActionsMenu implements CallbackListener, ManialinkPageAnswerListener {
unset($this->playerMenuItems[$order]);
}
} else {
if (isset($this->playerMenuItems[$order])) {
if (isset($this->adminMenuItems[$order])) {
unset($this->adminMenuItems[$order]);
}
}

View File

@ -133,9 +133,9 @@ class AdminLists implements ManialinkPageAnswerListener, CallbackListener {
$lineQuad->setZ(0.001);
}
$array = array($index => $posX + 5, $admin->nickname => $posX + 18, $admin->login => $posX + 70);
$this->maniaControl->getManialinkManager()->labelLine($playerFrame, $array);
$positions = array($posX + 5, $posX + 18, $posX + 70);
$texts = array($index, $admin->nickname, $admin->login);
$this->maniaControl->getManialinkManager()->labelLine($playerFrame, array($positions, $texts));
// Level Quad
$rightQuad = new Quad_BgRaceScore2();

View File

@ -32,6 +32,8 @@ class AuthCommands implements CommandListener {
$this->maniaControl->getCommandManager()->registerCommandListener('addsuperadmin', $this, 'command_AddSuperAdmin', true, 'Add Player to the AdminList as SuperAdmin.');
$this->maniaControl->getCommandManager()->registerCommandListener('addadmin', $this, 'command_AddAdmin', true, 'Add Player to the AdminList as Admin.');
$this->maniaControl->getCommandManager()->registerCommandListener('addmod', $this, 'command_AddModerator', true, 'Add Player to the AdminList as Moderator.');
$this->maniaControl->getCommandManager()->registerCommandListener('removerights', $this, 'command_RemoveRights', true, 'Remove Player from the AdminList.');
}
/**
@ -159,4 +161,53 @@ class AuthCommands implements CommandListener {
$message = "Usage Example: '//addmod login'";
return $this->maniaControl->getChat()->sendUsageInfo($message, $player);
}
/**
* Handle //removerights command
*
* @param array $chatCallback
* @param Player $player
*/
public function command_RemoveRights(array $chatCallback, Player $player) {
if (!AuthenticationManager::checkRight($player, AuthenticationManager::AUTH_LEVEL_ADMIN)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
$text = $chatCallback[1][2];
$commandParts = explode(' ', $text);
if (!array_key_exists(1, $commandParts)) {
$this->sendRemoveRightsUsageInfo($player);
return;
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($commandParts[1]);
if (!$target) {
$this->maniaControl->getChat()->sendError("Player '{$commandParts[1]}' not found!", $player);
return;
}
if ($target->authLevel == AuthenticationManager::AUTH_LEVEL_MASTERADMIN) {
$this->maniaControl->getChat()->sendError("You can't remove an MasterAdmin from the Adminlists", $player);
return;
}
$success = $this->maniaControl->getAuthenticationManager()->grantAuthLevel($target, AuthenticationManager::AUTH_LEVEL_PLAYER);
if (!$success) {
$this->maniaControl->getChat()->sendError('Error occurred.', $player);
return;
}
$message = $player->getEscapedNickname() . ' removed ' . $target->getEscapedNickname() . ' from the Adminlists!';
$this->maniaControl->getChat()->sendSuccess($message);
}
/**
* Send usage example for //removerights command
*
* @param Player $player
* @return bool
*/
private function sendRemoveRightsUsageInfo(Player $player) {
$message = "Usage Example: '//addadmin login'";
return $this->maniaControl->getChat()->sendUsageInfo($message, $player);
}
}

View File

@ -4,6 +4,10 @@ namespace ManiaControl\Admin;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Callbacks\EchoListener;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
@ -17,7 +21,7 @@ use ManiaControl\Settings\Setting;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class AuthenticationManager implements CallbackListener {
class AuthenticationManager implements CallbackListener, EchoListener, CommunicationListener {
/*
* Constants
*/
@ -33,6 +37,8 @@ class AuthenticationManager implements CallbackListener {
const AUTH_NAME_MASTERADMIN = 'MasterAdmin';
const CB_AUTH_LEVEL_CHANGED = 'AuthenticationManager.AuthLevelChanged';
const ECHO_GRANT_LEVEL = 'ManiaControl.AuthenticationManager.GrandLevel';
const ECHO_REVOKE_LEVEL = 'ManiaControl.AuthenticationManager.RevokeLevel';
/*
* Private properties
*/
@ -52,6 +58,56 @@ class AuthenticationManager implements CallbackListener {
// Callbacks
$this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::ONINIT, $this, 'handleOnInit');
// Echo Grant Level Command (Usage: sendEcho json_encode("player" => "loginName", "level" => "AUTH_LEVEL_NUMBER")
$this->maniaControl->getEchoManager()->registerEchoListener(self::ECHO_GRANT_LEVEL, $this, function ($params) {
if (property_exists($params, 'level') && property_exists($params, 'player')) {
$player = $this->maniaControl->getPlayerManager()->getPlayer($params->player);
if ($player) {
$this->grantAuthLevel($player, $params->level);
}
}
});
// Echo Revoke Level Command (Usage: sendEcho json_encode("player" => "loginName")
$this->maniaControl->getEchoManager()->registerEchoListener(self::ECHO_REVOKE_LEVEL, $this, function ($params) {
if (property_exists($params, 'player')) {
$player = $this->maniaControl->getPlayerManager()->getPlayer($params->player);
if ($player) {
$this->maniaControl->getAuthenticationManager()->grantAuthLevel($player, self::AUTH_LEVEL_PLAYER);
}
}
});
//Communication Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GRANT_AUTH_LEVEL, $this, function ($data) {
if (!is_object($data) || !property_exists($data, 'level') || !property_exists($data, 'login')) {
return new CommunicationAnswer("No valid level or player login provided!", true);
}
$player = $this->maniaControl->getPlayerManager()->getPlayer($data->login);
if ($player) {
$success = $this->grantAuthLevel($player, $data->level);
return new CommunicationAnswer(array("success" => $success));
} else {
return new CommunicationAnswer("Player not found!", true);
}
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::REVOKE_AUTH_LEVEL, $this, function ($data) {
if (!is_object($data) || !property_exists($data, 'login')) {
return new CommunicationAnswer("No valid player login provided!", true);
}
$player = $this->maniaControl->getPlayerManager()->getPlayer($data->login);
if ($player) {
$success = $this->maniaControl->getAuthenticationManager()->grantAuthLevel($player, self::AUTH_LEVEL_PLAYER);
return new CommunicationAnswer(array("success" => $success));
} else {
return new CommunicationAnswer("Player not found!", true);
}
});
}
/**
@ -83,12 +139,12 @@ class AuthenticationManager implements CallbackListener {
*/
public static function getAuthLevelInt($authLevelParam) {
if (is_object($authLevelParam) && property_exists($authLevelParam, 'authLevel')) {
return (int)$authLevelParam->authLevel;
return (int) $authLevelParam->authLevel;
}
if (is_string($authLevelParam)) {
return self::getAuthLevel($authLevelParam);
}
return (int)$authLevelParam;
return (int) $authLevelParam;
}
/**
@ -98,7 +154,7 @@ class AuthenticationManager implements CallbackListener {
* @return int
*/
public static function getAuthLevel($authLevelName) {
$authLevelName = (string)$authLevelName;
$authLevelName = (string) $authLevelName;
switch ($authLevelName) {
case self::AUTH_NAME_MASTERADMIN:
return self::AUTH_LEVEL_MASTERADMIN;
@ -189,7 +245,7 @@ class AuthenticationManager implements CallbackListener {
}
$success = true;
foreach ($loginElements as $loginElement) {
$login = (string)$loginElement;
$login = (string) $loginElement;
$adminStatement->bind_param('si', $login, $masterAdminLevel);
$adminStatement->execute();
if ($adminStatement->error) {
@ -271,7 +327,7 @@ class AuthenticationManager implements CallbackListener {
if (!$player || !is_numeric($authLevel)) {
return false;
}
$authLevel = (int)$authLevel;
$authLevel = (int) $authLevel;
if ($authLevel >= self::AUTH_LEVEL_MASTERADMIN) {
return false;
}

View File

@ -15,28 +15,34 @@ class BillData {
/*
* Public properties
*/
public $function = null;
public $pay = false;
public $player = null;
public $function = null;
public $pay = false;
public $player = null;
public $receiverLogin = null;
public $amount = 0;
public $creationTime = -1;
public $amount = 0;
public $creationTime = -1;
public $message = "";
public $class = "";
/**
* Construct new Bill Data Model
*
* @param string $class
* @param callable $function
* @param Player|string $player
* @param int $amount
* @param bool $pay
* @param string $receiverLogin
* @param string $message
*/
public function __construct(callable $function, $player, $amount, $pay = false, $receiverLogin = null) {
public function __construct($class, callable $function, $player, $amount, $pay = false, $receiverLogin = null, $message = '') {
$this->class = $class;
$this->function = $function;
$this->player = $player;
$this->amount = $amount;
$this->pay = $pay;
$this->receiverLogin = $receiverLogin;
$this->message = $message;
$this->creationTime = time();
}
}

View File

@ -6,6 +6,7 @@ use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\CallbackManager;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
use Maniaplanet\DedicatedServer\InvalidArgumentException;
use Maniaplanet\DedicatedServer\Structures\Bill;
/**
@ -24,7 +25,7 @@ class BillManager implements CallbackListener {
const PAYED_FROM_SERVER = 3;
const PLAYER_REFUSED_DONATION = 4;
const ERROR_WHILE_TRANSACTION = 5;
const CB_BILL_PAID = 'Billmanager.BillPaid';
const CB_BILL_PAID = 'Billmanager.BillPaid';
/*
* Private properties
@ -57,11 +58,21 @@ class BillManager implements CallbackListener {
* @return bool
*/
public function sendBill(callable $function, Player $player, $amount, $message, $receiver = '') {
$billId = $this->maniaControl->getClient()->sendBill($player->login, $amount, $message, $receiver);
$this->openBills[$billId] = new BillData($function, $player, $amount);
//Get the Caller Class
$backTrace = debug_backtrace();
$class = $backTrace[1]['class'];
try {
$billId = $this->maniaControl->getClient()->sendBill($player->login, intval($amount), $message, $receiver);
} catch (InvalidArgumentException $e) {
//TODO better error handling, maybe call the user func with ERROR_WHILE_TRANSACTION
return false;
}
$this->openBills[$billId] = new BillData($class, $function, $player, $amount, false, $receiver, $message);
return true;
}
/**
* Send planets from the server to a player
*
@ -72,8 +83,17 @@ class BillManager implements CallbackListener {
* @return bool
*/
public function sendPlanets(callable $function, $receiverLogin, $amount, $message) {
$billId = $this->maniaControl->getClient()->pay($receiverLogin, $amount, $message);
$this->openBills[$billId] = new BillData($function, $receiverLogin, $amount, true);
//Get the Caller Class
$backTrace = debug_backtrace();
$class = $backTrace[1]['class'];
try {
$billId = $this->maniaControl->getClient()->pay($receiverLogin, intval($amount), $message);
} catch (InvalidArgumentException $e) {
return false;
}
$this->openBills[$billId] = new BillData($class, $function, $receiverLogin, $amount, true, $receiverLogin, $message);
return true;
}

View File

@ -153,8 +153,6 @@ class CallbackManager {
return $this->removeCallbackListener($this->callbackListenings, $listener);
}
//TODO better name (used only in customvotesPlugin)
/**
* Remove the Callback Listener from the given Listeners Array
*
@ -212,6 +210,9 @@ class CallbackManager {
// Manage Timings
$this->maniaControl->getTimerManager()->manageTimings();
// Manage Socket Tickets
$this->maniaControl->getCommunicationManager()->tick();
// Server Callbacks
if (!$this->maniaControl->getClient()) {
return;

View File

@ -18,6 +18,7 @@ interface Callbacks {
const ONINIT = 'Callbacks.OnInit';
const AFTERINIT = 'Callbacks.AfterInit';
const ONSHUTDOWN = 'Callbacks.OnShutdown';
const ONRESTART = 'Callbacks.OnRestart';
/** Script Callback: CallbackName, CallbackData */
const SCRIPTCALLBACK = 'Callbacks.ScriptCallback';
@ -49,6 +50,8 @@ interface Callbacks {
const ENDTURNSTOP = 'Callbacks.EndTurnStop';
/** EndRound Callback: RoundNumber */
const ENDROUND = 'Callbacks.EndRound';
/** EndRound Callback: RoundNumber */
const ENDROUNDSTOP = 'Callbacks.EndRoundStop';
/** EndSubmatch Callback: SubmatchNumber */
const ENDSUBMATCH = 'Callbacks.EndSubmatch';
/** EndMap Callback: Map */
@ -67,6 +70,12 @@ interface Callbacks {
/** EndWarmup Callback */
const ENDWARMUP = 'Callbacks.EndWarmUp';
/** Scores Callback (returned after LibXmlRpc_PlayerRanking): Scores */
const SCORESREADY = 'Callbacks.ScoresReady';
/** Scores Callback (returned after LibXmlRpc_PlayerRanking in SM, or LibXmlRpc_TeamsScores in Trackmania): Scores */
const SCORES = 'Callbacks.Scores';
/** PlayerRanking Callback, returned after LibXmlRpc_PlayerRanking
* try to avoid to use this, just use the Get function of the RankingsManager instead
* param1 Player $player
@ -80,8 +89,6 @@ interface Callbacks {
*/
/** RankingsUpdated Callback: SortedRankings */
const RANKINGSUPDATED = 'Callbacks.RankingsUpdated';
/** Scores Callback (returned after LibXmlRpc_PlayerRanking): Scores */
const SCORES = 'Callbacks.Scores';
/** Returns the AFKStatus of an Player, returned after param1 Scores */ //returned after TODO
const AFKSTATUS = 'Callbacks.AfkStatus';

View File

@ -0,0 +1,12 @@
<?php
namespace ManiaControl\Callbacks;
/**
* Interface for EchoListener
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
interface EchoListener {
}

View File

@ -0,0 +1,150 @@
<?php
namespace ManiaControl\Callbacks;
use ManiaControl\ManiaControl;
/**
* Class for managing Echo Callbacks
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class EchoManager implements CallbackListener, EchoListener {
/*
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
/** @var Listening[] $echoListenings */
private $echoListenings = array();
/**
* Create a new Echo Handler Instance
*
* @param ManiaControl $maniaControl
*/
public function __construct(ManiaControl $maniaControl) {
$this->maniaControl = $maniaControl;
$this->maniaControl->getCallbackManager()->registerCallbackListener(CallbackManager::CB_MP_ECHO, $this, 'handleEchos');
}
/**
* Sends an Echo Message
*
* @param string $name
* @param mixed $data (can be array, object or string)
* @return bool
* @throws \Maniaplanet\DedicatedServer\InvalidArgumentException
*/
public function sendEcho($name, $data) {
if (is_string($data)) {
$success = $this->maniaControl->getClient()->dedicatedEcho($data, $name);
} else {
$success = $this->maniaControl->getClient()->dedicatedEcho(json_encode($data), $name);
}
return $success;
}
/**
* Register a new Echo Listener
*
* @param string $callbackName
* @param EchoListener $listener
* @param string $method
* @return bool
*/
public function registerEchoListener($echoName, EchoListener $listener, $method) {
if (!Listening::checkValidCallback($listener, $method)) {
$listenerClass = get_class($listener);
trigger_error("Given Listener '{$listenerClass}' can't handle Callback '{$echoName}': No callable Method '{$method}'!");
return false;
}
if (!array_key_exists($echoName, $this->echoListenings)) {
$this->echoListenings[$echoName] = array();
}
$listening = new Listening($listener, $method);
array_push($this->echoListenings[$echoName], $listening);
return true;
}
/**
* Unregister a Echo Listener
*
* @param EchoListener $listener
* @return bool
*/
public function unregisterEchoListener(EchoListener $listener) {
return $this->removeEchoListener($this->echoListenings, $listener);
}
/**
* Remove the Echo Listener from the given Listeners Array
*
* @param Listening[] $listeningsArray
* @param EchoListener $listener
* @return bool
*/
private function removeEchoListener(array &$listeningsArray, EchoListener $listener) {
$removed = false;
foreach ($listeningsArray as &$listenings) {
foreach ($listenings as $key => &$listening) {
if ($listening->listener === $listener) {
unset($listenings[$key]);
$removed = true;
}
}
}
return $removed;
}
/**
* Trigger a specific Callback
*
* @param mixed $callback
*/
public function triggerEchoCallback($callbackName) {
if (!array_key_exists($callbackName, $this->echoListenings)) {
return;
}
$params = func_get_args();
$params = array_slice($params, 1, null, true);
//var_dump($params);
foreach ($this->echoListenings[$callbackName] as $listening) {
/** @var Listening $listening */
$listening->triggerCallbackWithParams($params);
}
}
/**
* Handle the given Callback
*
* @param array $callback
*/
public function handleEchos($param) {
$name = $param[1][0];
if (is_object($decode = json_decode($param[1][1]))) {
$message = $decode;
} else {
$message = $param[1][1];
}
switch ($name) {
case 'ManiaControl.Restart':
$this->maniaControl->restart($message);
break;
default:
$this->triggerEchoCallback($name, $message);
}
}
}

View File

@ -82,6 +82,9 @@ class LibXmlRpcCallbacks implements CallbackListener {
case 'LibXmlRpc_EndRound':
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::ENDROUND, $data[0]);
break;
case 'LibXmlRpc_EndRoundStop':
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::ENDROUNDSTOP, $data[0]);
break;
case 'LibXmlRpc_EndSubmatch':
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::ENDSUBMATCH, $data[0]);
break;
@ -146,6 +149,12 @@ class LibXmlRpcCallbacks implements CallbackListener {
$player = $this->maniaControl->getPlayerManager()->getPlayer($data[0]);
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::ONPLAYERREQUESTRESPAWN, $player);
break;
case 'LibXmlRpc_Scores':
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::SCORES, $data);
break;
case 'LibXmlRpc_ScoresReady':
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::SCORESREADY, $data);
break;
}
}

View File

@ -57,9 +57,10 @@ class Listening {
* Trigger the Listener's Method with the given Array of Params
*
* @param array $params
* @return mixed
*/
public function triggerCallbackWithParams(array $params) {
call_user_func_array($this->getUserFunction(), $params);
return call_user_func_array($this->getUserFunction(), $params);
}
/**

View File

@ -52,9 +52,6 @@ class ShootManiaCallbacks implements CallbackListener {
case 'LibXmlRpc_Rankings':
$this->maniaControl->getServer()->getRankingManager()->updateRankings($data[0]);
break;
case 'LibXmlRpc_Scores':
$this->maniaControl->getCallbackManager()->triggerCallback(Callbacks::SCORES, $data);
break;
case 'LibAFK_IsAFK':
$this->triggerAfkStatus($data[0]);
break;

View File

@ -4,7 +4,13 @@ namespace ManiaControl\Callbacks\Structures;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
/**
* Structure Class for the ArmorEmpty Callback
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class ArmorEmptyStructure {
/*
* Private properties

View File

@ -5,6 +5,13 @@ namespace ManiaControl\Callbacks\Structures;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
/**
* Structure Class for the Capture Callback
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class CaptureStructure {
/*
* Private properties
@ -42,7 +49,7 @@ class CaptureStructure {
$playerArray = array();
foreach ($this->playerArray as $login) {
$player = $this->maniaControl->getPlayerManager()->getPlayer($login);
if($player){
if ($player) {
$playerArray[$login] = $player;
}
}

View File

@ -5,6 +5,13 @@ namespace ManiaControl\Callbacks\Structures;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
/**
* Structure Class for the EliteBeginTurn Callback
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class EliteBeginTurnStructure {
/*
* Private properties
@ -22,8 +29,8 @@ class EliteBeginTurnStructure {
* @param array $data
*/
public function __construct(ManiaControl $maniaControl, array $data) {
$this->maniaControl = $maniaControl;
$this->attackerLogin = $data[0];
$this->maniaControl = $maniaControl;
$this->attackerLogin = $data[0];
$this->defenderLogins = $data[1];
}

View File

@ -5,6 +5,13 @@ namespace ManiaControl\Callbacks\Structures;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
/**
* Structure Class for the NearMiss Callback
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class NearMissStructure {
/*
* Private properties

View File

@ -1,16 +1,17 @@
<?php
/**
* Player Hit Structure
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
namespace ManiaControl\Callbacks\Structures;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
/**
* Structure Class for the Player Hit Callback
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class PlayerHitStructure {
/*
* Private properties
@ -20,7 +21,11 @@ class PlayerHitStructure {
private $damage;
private $shooterPoints;
private $weapon;
private $hitDistance = 0;
private $hitDistance;
private $shooterPosition = 0;
private $victimPosition = 0;
private $shooterAimDirection = 0;
private $victimAimDirection = 0;
/** @var ManiaControl $maniaControl */
private $maniaControl;
@ -38,10 +43,23 @@ class PlayerHitStructure {
$this->damage = $data[2];
$this->weapon = $data[3];
$this->shooterPoints = $data[4];
//TODO remove key check in some months (hitDistance got implemented 2014-10-16)
if (array_key_exists(5, $data)) {
$this->hitDistance = $data[5];
$this->hitDistance = $data[5];
//TODO remove key check in some months (got implemented 2015-05-03)
if (array_key_exists(6, $data)) {
$this->shooterPosition = $data[6];
}
if (array_key_exists(7, $data)) {
$this->victimPosition = $data[7];
}
if (array_key_exists(8, $data)) {
$this->shooterAimDirection = $data[8];
}
if (array_key_exists(9, $data)) {
$this->victimAimDirection = $data[9];
}
}

View File

@ -3,6 +3,11 @@
namespace ManiaControl;
use ManiaControl\Admin\AuthenticationManager;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\CallbackManager;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Players\Player;
use Maniaplanet\DedicatedServer\Xmlrpc\UnknownPlayerException;
@ -13,7 +18,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\UnknownPlayerException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class Chat {
class Chat implements CallbackListener, CommunicationListener {
/*
* Constants
*/
@ -22,12 +27,13 @@ class Chat {
const SETTING_FORMAT_SUCCESS = 'Success Format';
const SETTING_FORMAT_ERROR = 'Error Format';
const SETTING_FORMAT_USAGEINFO = 'UsageInfo Format';
const CHAT_BUFFER_SIZE = 200;
/*
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $chatBuffer = array();
/**
* Construct chat utility
@ -43,6 +49,15 @@ class Chat {
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_FORMAT_SUCCESS, '$0f0');
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_FORMAT_ERROR, '$f30');
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_FORMAT_USAGEINFO, '$f80');
//Callbacks
$this->maniaControl->getCallbackManager()->registerCallbackListener(CallbackManager::CB_MP_PLAYERCHAT, $this, 'onPlayerChat');
//Socket Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::SEND_CHAT_MESSAGE, $this, "communcationSendChat");
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_SERVER_CHAT, $this, function ($data) {
return new CommunicationAnswer($this->chatBuffer);
});
}
/**
@ -226,4 +241,92 @@ class Chat {
$format = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_FORMAT_USAGEINFO);
return $this->sendChat($format . $message, $login, $prefix);
}
/**
* Handles SendChat Communication Request
*
* @param $data
* @return array
*/
public function communcationSendChat($data) {
if (!is_object($data) || !property_exists($data, "message")) {
return new CommunicationAnswer("You have to provide a valid message", true);
}
$prefix = true;
if (property_exists($data, "prefix")) {
$prefix = $data->prefix;
}
$login = null;
if (property_exists($data, "login")) {
$login = $data->login;
}
$adminLevel = 0;
if (property_exists($data, "adminLevel")) {
$adminLevel = $data->adminLevel;
}
$type = "default";
if (property_exists($data, "type")) {
$type = $data->type;
}
switch ($type) {
case "information":
if ($adminLevel) {
$this->sendInformationToAdmins($data->message, $adminLevel, $prefix);
} else {
$this->sendInformation($data->message, $login, $prefix);
}
break;
case "success":
if ($adminLevel) {
$this->sendInformationToAdmins($data->message, $adminLevel, $prefix);
} else {
$this->sendSuccess($data->message, $login, $prefix);
}
break;
case "error":
if ($adminLevel) {
$this->sendErrorToAdmins($data->message, $adminLevel, $prefix);
} else {
$this->sendError($data->message, $login, $prefix);
}
break;
case "usage":
$this->sendUsageInfo($data->message, $login, $prefix);
break;
default:
if ($adminLevel) {
$this->sendMessageToAdmins($data->message, $adminLevel, $prefix);
} else {
$this->sendChat($data->message, $login, $prefix);
}
}
return new CommunicationAnswer();
}
/**
* Stores the ChatMessage in the Buffer
*
* @param $data
*/
public function onPlayerChat($data) {
$login = $data[1][1];
$player = $this->maniaControl->getPlayerManager()->getPlayer($login);
$nickname = "";
if ($player) {
$nickname = $player->nickname;
}
array_push($this->chatBuffer, array("user" => $login, "nickname" => $nickname, "message" => $data[1][2]));
if (count($this->chatBuffer) > self::CHAT_BUFFER_SIZE) {
array_shift($this->chatBuffer);
}
}
}

View File

@ -19,15 +19,14 @@ use ManiaControl\Players\Player;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class HelpManager implements CommandListener, CallbackListener {
/*
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $maniaControl = null;
private $playerCommands = array();
private $adminCommands = array();
private $adminCommands = array();
/**
* Construct a new Commands Manager
@ -58,14 +57,14 @@ class HelpManager implements CommandListener, CallbackListener {
* @param Player $player
*/
public function command_adminHelp(array $chatCallback, Player $player) {
// Parse list from array
$message = $this->parseHelpList($this->adminCommands);
// Show message when it's not empty
if($message != NULL){
$message = 'Supported Admin Commands: ' . $message;
$this->maniaControl->getChat()->sendChat($message, $player);
}
// Parse list from array
$message = $this->parseHelpList($this->adminCommands);
// Show message when it's not empty
if ($message != null) {
$message = 'Supported Admin Commands: ' . $message;
$this->maniaControl->getChat()->sendChat($message, $player);
}
}
/**
@ -75,14 +74,14 @@ class HelpManager implements CommandListener, CallbackListener {
* @param Player $player
*/
public function command_playerHelp(array $chatCallback, Player $player) {
// Parse list from array
// Parse list from array
$message = $this->parseHelpList($this->playerCommands);
// Show message when it's not empty
if($message != NULL){
$message = 'Supported Player Commands: ' . $message;
$this->maniaControl->getChat()->sendChat($message, $player);
}
// Show message when it's not empty
if ($message != null) {
$message = 'Supported Player Commands: ' . $message;
$this->maniaControl->getChat()->sendChat($message, $player);
}
}
/**
@ -92,23 +91,24 @@ class HelpManager implements CommandListener, CallbackListener {
* @param Player $player
*/
public function command_playerHelpAll(array $chatCallback, Player $player) {
$this->parseHelpList($this->playerCommands, true, $player);
$this->parseHelpList($this->playerCommands, true, $player);
}
/**
* Parse list with commands from array
* @param array $commands
* @param bool $isHelpAll
* @param Player $player
* @return string|void
*/
* Parse list with commands from array
*
* @param array $commands
* @param bool $isHelpAll
* @param Player $player
* @return string|void
*/
private function parseHelpList(array $commands, $isHelpAll = false, Player $player = null) {
$showCommands = array();
$registeredMethods = array();
$message = '';
$message = '';
foreach (array_reverse($commands) as $command) {
if (array_key_exists($command['Method'], $registeredMethods)) {
if($showCommands[$registeredMethods[$command['Method']]]['Description'] === $command['Description']) {
if ($showCommands[$registeredMethods[$command['Method']]]['Description'] === $command['Description']) {
$name = $registeredMethods[$command['Method']];
$showCommands[$name]['Name'] .= '|' . $command['Name'];
} else {
@ -125,20 +125,17 @@ class HelpManager implements CommandListener, CallbackListener {
return strcmp($commandA['Name'], $commandB['Name']);
});
if(!$isHelpAll){
if (!$isHelpAll) {
foreach ($showCommands as $command) {
$message .= $command['Name'] . ',';
}
$message = substr($message, 0, -1);
return $message;
}else{
if($player != NULL){
} else {
if ($player != null) {
$this->showHelpAllList($showCommands, $player);
}
}
return;
return $message;
}
/**
@ -221,7 +218,7 @@ class HelpManager implements CommandListener, CallbackListener {
* @param Player $player
*/
public function command_adminHelpAll(array $chatCallback, Player $player) {
$this->parseHelpList($this->adminCommands, true, $player);
$this->parseHelpList($this->adminCommands, true, $player);
}
/**

View File

@ -0,0 +1,107 @@
<?php
namespace ManiaControl\Communication;
/**
* Class for Communicating with other ManiaControls
* to call @see ManiaControl\Communication\CommunicationManager\createCommunication
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class Communication {
private $socket;
private $ip;
private $port;
private $encryptionPassword;
private $buffer = "";
private $messageQueue = array();
public function __construct($ip, $port, $encryptionPassword) {
$this->ip = $ip;
$this->port = $port;
$this->encryptionPassword = $encryptionPassword;
}
/** Create an Connection */
public function createConnection() {
$errno = null;
$errstr = null;
$this->socket = @fsockopen($this->ip, $this->port, $errno, $errstr, 2);
//socket_set_nonblock($this->socket);
stream_set_blocking($this->socket, 0);
if ($errno != 0 || !$this->socket) {
var_dump($errstr);
return false;
}
return true;
}
/**
* Call an Method Asynchronously
*
* @param callable $function
* @param $method
* @param string $data
*/
public function call(callable $function, $method, $data = "") {
if (!$this->socket) {
call_user_func($function, true, "You need to create an Communication before using it");
return null;
}
$data = json_encode(array("method" => $method, "data" => $data));
$data = openssl_encrypt($data, CommunicationManager::ENCRYPTION_METHOD, $this->encryptionPassword, OPENSSL_RAW_DATA, CommunicationManager::ENCRYPTION_IV);
array_push($this->messageQueue, $function);
// Write Request on Socket
fwrite($this->socket, strlen($data) . "\n" . $data);
}
/**
* Process data on every Tick
*/
public function tick() {
//Check if the connection is enabled
if (!$this->socket) {
return;
}
$data = fgets($this->socket, 1024); // reads as much as possible OR nothing at all
if (strlen($data) > 0) { // got new data
$this->buffer .= $data; // append new data to buffer
// handle the data the exact same way
$arr = explode("\n", $this->buffer, 2);
while (count($arr) == 2 && strlen($arr[1]) >= (int) $arr[0]) {
// received full message
$len = (int) $arr[0];
$msg = substr($arr[1], 0, $len); // clip msg
$this->buffer = substr($this->buffer, strlen((string) $len) + 1 /* newline */ + $len); // clip buffer
// Decode Message
$data = openssl_decrypt($msg, CommunicationManager::ENCRYPTION_METHOD, $this->encryptionPassword, OPENSSL_RAW_DATA, CommunicationManager::ENCRYPTION_IV);
$data = json_decode($data);
// Received something!
//Call Function with Data
call_user_func(array_shift($this->messageQueue), $data->error, $data->data);
// next msg
$arr = explode("\n", $this->buffer, 2);
}
}
}
/** Closes the connection, don't call yourself, let it do the Communication Manager */
public function closeConnection() {
if ($this->socket) {
fclose($this->socket);
}
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace ManiaControl\Communication;
/**
* Class for Answer of Communication Request
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class CommunicationAnswer {
/** Properties are Public for serialization */
public $error;
public $data;
/**
* @param string $data
* @param bool $error
*/
public function __construct($data = "", $error = false) {
$this->data = $data;
$this->error = $error;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace ManiaControl\Communication;
/**
* Interface for SocketListener
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
interface CommunicationListener {
}

View File

@ -0,0 +1,311 @@
<?php
namespace ManiaControl\Communication;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Callbacks\Listening;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use ManiaControl\Settings\Setting;
use ManiaControl\Settings\SettingManager;
use React\EventLoop\Factory;
use React\EventLoop\LoopInterface;
use React\Socket\Connection;
use React\Socket\ConnectionException;
use React\Socket\Server;
/**
* Class for managing Socket Callbacks
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class CommunicationManager implements CallbackListener {
/** Constants */
const SETTING_SOCKET_ENABLED = "Activate Socket";
const SETTING_SOCKET_PASSWORD = "Password for the Socket Connection";
const SETTING_SOCKET_PORT = "Socket Port for Server ";
const ENCRYPTION_IV = "kZ2Kt0CzKUjN2MJX";
const ENCRYPTION_METHOD = "aes-192-cbc";
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
/** @var LoopInterface $loop */
private $loop = null;
/** @var Listening[] $communicationListenings */
private $communicationListenings = array();
/** @var Server $socket */
private $socket = null;
/** @var Communication[] $communcations */
private $communications = array();
/**
* Create a new Communication Handler Instance
*
* @param ManiaControl $maniaControl
*/
public function __construct(ManiaControl $maniaControl) {
$this->maniaControl = $maniaControl;
$this->maniaControl->getCallbackManager()->registerCallbackListener(SettingManager::CB_SETTING_CHANGED, $this, 'updateSettings');
$this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::AFTERINIT, $this, 'initCommunicationManager');
$this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::ONRESTART, $this, 'onShutDown');
$this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::ONSHUTDOWN, $this, 'onShutDown');
}
/**
* Creates a Communication to another ManiaControl
*
* @param $ip
* @param $port
* @return \ManiaControl\Communication\Communication
*/
public function createCommunication($ip, $port, $encryptionKey) {
$communication = new Communication($ip, $port, $encryptionKey);
$communication->createConnection();
$this->communications[] = $communication;
return $communication;
}
/**
* Closes a opened Communication
* Does not necessarily need be called, all connections get destroyed on ManiaControl Shutdown
*
* @param Communication $communication
* @return bool
*/
public function closeCommunication($communication) {
$key = array_search($communication, $this->communications);
if (isset($this->communications[$key])) {
$this->communications[$key]->closeConnection();
unset($this->communications[$key]);
return true;
}
return false;
}
/** Close all Sockets on maniaControl Shutdown */
public function onShutDown() {
if ($this->socket && $this->socket->master) {
//Stop the Socket Listening
$this->socket->shutdown();
$this->socket = null;
}
foreach ($this->communications as $communication) {
$this->closeCommunication($communication);
}
}
/**
* Register a new Communication Listener
*
* @param string $callbackName
* @param CommunicationListener $listener
* @param string $method
* @return bool
*/
public function registerCommunicationListener($echoName, CommunicationListener $listener, $method) {
if (!Listening::checkValidCallback($listener, $method)) {
$listenerClass = get_class($listener);
trigger_error("Given Listener '{$listenerClass}' can't handle Callback '{$echoName}': No callable Method '{$method}'!");
return false;
}
if (!array_key_exists($echoName, $this->communicationListenings)) {
$this->communicationListenings[$echoName] = new Listening($listener, $method);
} else {
//TODO say which is already listening and other stuff
trigger_error("Only one Listener can listen on a specific Communication Message");
}
return true;
}
/**
* Trigger a specific Callback
*
* @param mixed $callback
*/
public function triggerCommuncationCallback($callbackName) {
if (!array_key_exists($callbackName, $this->communicationListenings)) {
return null;
}
$params = func_get_args();
$params = array_slice($params, 1, null, true);
$listening = $this->communicationListenings[$callbackName];
/** @var Listening $listening */
return $listening->triggerCallbackWithParams($params);
}
/**
* Unregister a Communication Listener
*
* @param CommunicationListener $listener
* @return bool
*/
public function unregisterCommunicationListener(CommunicationListener $listener) {
return $this->removeCommunicationListener($this->communicationListenings, $listener);
}
/**
* Remove the Communication Listener from the given Listeners Array
*
* @param Listening[] $listeningsArray
* @param CommunicationListener $listener
* @return bool
*/
private function removeCommunicationListener(array &$listeningsArray, CommunicationListener $listener) {
$removed = false;
foreach ($listeningsArray as &$listening) {
if ($listening->listener === $listener) {
unset($listening);
$removed = true;
}
}
return $removed;
}
/**
* Inits the Communication Manager after ManiaControl Startup
*/
public function initCommunicationManager() {
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_SOCKET_ENABLED, false);
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_SOCKET_PASSWORD, "");
$servers = $this->maniaControl->getServer()->getAllServers();
foreach ($servers as $server) {
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_SOCKET_PORT . $server->login, 31500 + $server->index);
}
$this->createListeningSocket();
}
/**
* Update Setting
*
* @param Setting $setting
*/
public function updateSettings(Setting $setting) {
if (!$setting->belongsToClass($this)) {
return;
}
$socketEnabled = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_ENABLED);
if ($socketEnabled && !$this->socket) {
$this->createListeningSocket();
}
if (!$socketEnabled) {
$this->socket = null;
}
}
/**
* Creates The Socket
*/
private function createListeningSocket() {
$socketEnabled = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_ENABLED);
if ($socketEnabled) {
Logger::log("[CommunicationManager] Trying to create Socket");
// Check for MySQLi
$message = '[CommunicationManager] Checking for installed openssl ... ';
if (!extension_loaded('openssl')) {
Logger::log($message . 'NOT FOUND!');
Logger::log(" -- You don't have openssl installed! Check: http://www.php.net/manual/en/openssl.installation.php");
return;
} else {
Logger::log($message . 'FOUND!');
}
$serverLogin = $this->maniaControl->getServer()->login;
$socketPort = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_PORT . $serverLogin);
$password = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_PASSWORD);
try {
$this->loop = Factory::create();
$this->socket = new Server($this->loop);
$this->socket->on('error', function ($e) {
Logger::log("[CommunicationManager] Socket Error" . $e);
});
$this->socket->on('connection', function (Connection $connection) use ($password) {
$buffer = '';
$connection->on('data', function ($data) use (&$buffer, &$connection, $password) {
$buffer .= $data;
$arr = explode("\n", $buffer, 2);
while (count($arr) == 2 && strlen($arr[1]) >= (int) $arr[0]) {
// received full message
$len = (int) $arr[0];
$msg = substr($arr[1], 0, $len); // clip msg
$buffer = substr($buffer, strlen((string) $len) + 1 /* newline */ + $len); // clip buffer
// Decode Message
$data = openssl_decrypt($msg, self::ENCRYPTION_METHOD, $password, OPENSSL_RAW_DATA, self::ENCRYPTION_IV);
$data = json_decode($data);
if ($data == null) {
$data = array("error" => true, "data" => "Data is not provided as an valid AES-196-encrypted encrypted JSON");
} else if (!property_exists($data, "method") || !property_exists($data, "data")) {
$data = array("error" => true, "data" => "Invalid Message");
} else {
$answer = $this->triggerCommuncationCallback($data->method, $data->data);
//Prepare Response
if (!$answer) {
$data = new CommunicationAnswer("No listener or response on the given Message", true);
} else {
$data = $answer;
}
}
//Encode, Encrypt and Send Response
$data = json_encode($data);
$data = openssl_encrypt($data, self::ENCRYPTION_METHOD, $password, OPENSSL_RAW_DATA, self::ENCRYPTION_IV);
$connection->write(strlen($data) . "\n" . $data);
// next msg
$arr = explode("\n", $buffer, 2);
}
});
});
//TODO check if port is closed
$this->socket->listen($socketPort, $this->maniaControl->getServer()->ip);
Logger::log("[CommunicationManager] Socket " . $this->maniaControl->getServer()->ip . ":" . $this->socket->getPort() . " Successfully created!");
} catch (ConnectionException $e) {
Logger::log("[CommunicationManager] Exception: " . $e->getMessage());
}
}
}
/**
* Processes Data on every ManiaControl Tick, don't call this Method
*/
public function tick() {
if ($this->loop) {
$this->loop->tick();
}
foreach ($this->communications as $communication) {
$communication->tick();
}
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace ManiaControl\Communication;
/**
* Communication Methods Interface
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
interface CommunicationMethods {
/** Restarts Mania Control
* Optional Params
* - message
*/
const RESTART_MANIA_CONTROL = "ManiaControl.Restart";
/** Grands an Authentication Level on a Player
* Required Parameters
* - login (login of the player)
* - level (integer, 0-3 possible, @see AuthenticationManager)
*/
const GRANT_AUTH_LEVEL = "AuthenticationManager.GrandLevel";
/** Revokes an Authentication Level on a Player
* Required Parameters
* - login (login of the player)
*/
const REVOKE_AUTH_LEVEL = "AuthenticationManager.RevokeLevel";
/** Provides the Server Options
* no Parameters
*/
const GET_SERVER_OPTIONS = "ServerOptions.GetServerOptions";
/** Set Server Options
* Required Parameter
* - scriptSettings (array(optionName1 => value1, optionName2 => value2...))
*/
const SET_SERVER_OPTIONS = "ServerOptions.SetServerOptions";
/** Provides the ModeScriptSettings
* no Parameters
*/
const GET_SCRIPT_SETTINGS = "ScriptSettings.GetScriptSettings";
/** Set ModeScriptSettings
* Required Parameter
* - scriptSettings (array(settingName1 => value1, settingName2 => value2...))
*/
const SET_SCRIPT_SETTINGS = "ScriptSettings.SetScriptSettings";
/** Restarts the Current Map
* no Parameters
*/
const RESTART_MAP = "MapActions.RestartMap";
/** Skips the Current Map
* no Parameters
*/
const SKIP_MAP = "MapActions.SkipMap";
/** Skips to a Specific Map by MxId or MapUid
* Required Parameters
* - mxId (integer)
* OR
* - mapUid (string)
*/
const SKIP_TO_MAP = "MapActions.SkipToMap";
/** Adds a Map from Mania Exchange to the Server
* Required Parameters
* - mxId (integer)
* (no success returning yet because of asynchronously of adding)
*/
const ADD_MAP = "MapManager.AddMap";
/** Removes a Map from the Server
* Required Parameters
* - mapUid (string)
* Optional Parameters
* - displayMessage (default true)
* - eraseMapFile (default false)
*/
const REMOVE_MAP = "MapManager.RemoveMap";
/** Updates a Map over Mania Exchange
* Required Parameters
* - mapUid
* (no success returning yet because of asynchronously of adding)
*/
const UPDATE_MAP = "MapManager.UpdateMap";
/** Gets the current Map
* Required Parameters
* - mxId (integer)
* OR
* - mapUid (string)
*/
const GET_CURRENT_MAP = "MapManager.GetCurrentMap";
/** Gets the specific Map
* no Parameters
*/
const GET_MAP = "MapManager.GetMap";
/** Gets the current Map List
* no Parameters
*/
const GET_MAP_LIST = "MapManager.GetMapList";
/** Gets Mania Control PlayerList
* no Parameters
*/
const GET_PLAYER_LIST = "PlayerManager.GetPlayerList";
/** Warns a Player
* Required Params
* - login
*/
const WARN_PLAYER = "PlayerActions.WarnPlayer";
/** Mutes a Player
* Required Params
* - login
*/
const MUTE_PLAYER = "PlayerActions.MutePlayer";
/** UnMutes a Player
* Required Params
* - login
*/
const UNMUTE_PLAYER = "PlayerActions.UnMutePlayer";
/** UnMutes a Player
* Required Params
* - login
* Optional Params
* - message
*/
const KICK_PLAYER = "PlayerActions.KickPlayer";
/** Forces a player to Spectator
* Required Params
* - login
*/
const FORCE_PLAYER_TO_SPEC = "PlayerActions.ForcePlayerToSpec";
/** Forces a player to Spectator
* Required Params
* - login
* Optional Params
* - teamId (integer, id of the team the player should get forced into it)
*/
const FORCE_PLAYER_TO_PLAY = "PlayerActions.ForcePlayerToPlay";
/** Returns the last 200 lines of the chat (inclusive player logins and nicknames)
* No Params
*/
const GET_SERVER_CHAT = "Chat.GetServerChat";
/** Sends a ChatMessage to the Server
* Required Params:
* - message
* Optional Params
* - prefix (use custom prefix or false for no prefix)
* - login (login of a receiver if the message don't get sent to all)
* - adminLevel (minimum Admin Level if the Message should get sent to an Admin)
* - type (type of the message (information, error, success or usage)
*/
const SEND_CHAT_MESSAGE = "Chat.SendChatMessage";
}

View File

@ -0,0 +1,53 @@
The CommuncationListening of the Communcation Manager can be enabled in the ingame Settings.
There the following settings are existing:
-- Enable Socket Listening (Let the CommunicationManager listen for incoming calls)
-- Passsword (Password which get used to encrypt and decrypt the messages for the openssl connection)
-- Listening port for every server (this is the port the CommunicationManager listens at)
For the description of the available implemented communcation Methods check the CommunicationMethods interface.
If you need methods which are not implemented, or additional Parameters, feel free to contact us.
Sample ManiaControl Implementation (for ManiaControl to ManiaControl connections)
##php code begin
$communication = $this->maniaControl->getCommunicationManager()->createCommunication(IP/Domain, PORT, 'YOUR_PASSWORD');
$communication->call(function($data){
var_dump($data);
}, CommunicationMethods::GET_SERVER_CHAT);
##php code end
Sample Web Implementation (to call ManiaControl from a website)
##php code begin
$errno = null;
$errstr = null;
$socket = fsockopen("xx.xxx.xx.xx", xxxxx, $errno, $errstr, 2);
echo "ok?" . $errno . " - " . $errstr . "\n";
$data = array("method" => "getServerChat", "data" => "");
// Encode and Encrypt the Data
$data = json_encode(array("method" => "getServerChat", "data" => ""));
$data = openssl_encrypt($data, 'aes-192-cbc', 'YOUR_PASSWORD', OPENSSL_RAW_DATA, 'kZ2Kt0CzKUjN2MJX');
// Write the Data on the Socket
fwrite($socket, strlen($data) . "\n" . $data);
// Read Answer Data
$len = (int)fgets($socket);
echo $len;
$buff = '';
while (!feof($socket) && strlen($buff) < $len) {
$buff .= fgets($socket, $len - strlen($buff) + 1);
}
// Decrypt and Decode the Response Data
$data = openssl_decrypt($buff, 'aes-192-cbc', 'YOUR_PASSWORD', OPENSSL_RAW_DATA, 'kZ2Kt0CzKUjN2MJX');
echo json_decode($data);
//Close the Socket
fclose($socket);
##php code end

View File

@ -199,11 +199,11 @@ class Configurator implements CallbackListener, CommandListener, ManialinkPageAn
$frame = new Frame();
$manialink->add($frame);
$frame->setPosition($menuPosX, $menuPosY, 10);
$frame->setPosition($menuPosX, $menuPosY, 34);
$backgroundQuad = new Quad();
$frame->add($backgroundQuad);
$backgroundQuad->setZ(-10)->setSize($menuWidth, $menuHeight)->setStyles($quadStyle, $quadSubstyle);
$backgroundQuad->setZ(-2)->setSize($menuWidth, $menuHeight)->setStyles($quadStyle, $quadSubstyle);
$menuItemsFrame = new Frame();
$frame->add($menuItemsFrame);
@ -211,7 +211,7 @@ class Configurator implements CallbackListener, CommandListener, ManialinkPageAn
$itemsBackgroundQuad = new Quad();
$menuItemsFrame->add($itemsBackgroundQuad);
$backgroundQuad->setZ(-9);
$backgroundQuad->setZ(-1);
$itemsBackgroundQuad->setSize($menuListWidth, $menuHeight)->setStyles($quadStyle, $quadSubstyle);
$menusFrame = new Frame();

View File

@ -14,6 +14,9 @@ use FML\Script\Script;
use ManiaControl\Admin\AuthenticationManager;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
@ -26,7 +29,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\GameModeException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class ScriptSettings implements ConfiguratorMenu, CallbackListener {
class ScriptSettings implements ConfiguratorMenu, CallbackListener, CommunicationListener {
/*
* Constants
*/
@ -61,6 +64,10 @@ class ScriptSettings implements ConfiguratorMenu, CallbackListener {
// Permissions
$this->maniaControl->getAuthenticationManager()->definePermissionLevel(self::SETTING_PERMISSION_CHANGE_SCRIPT_SETTINGS, AuthenticationManager::AUTH_LEVEL_ADMIN);
//TODO remove to somewhere cleaner
//Communication Listenings
$this->initalizeCommunicationListenings();
}
/**
@ -149,8 +156,7 @@ class ScriptSettings implements ConfiguratorMenu, CallbackListener {
* Handle Begin Map Callback
*/
public function onBeginMap() {
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_LOAD_DEFAULT_SETTINGS_MAP_BEGIN)
) {
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_LOAD_DEFAULT_SETTINGS_MAP_BEGIN)) {
$this->loadSettingsFromDatabase();
}
}
@ -282,8 +288,7 @@ class ScriptSettings implements ConfiguratorMenu, CallbackListener {
* @see \ManiaControl\Configurators\ConfiguratorMenu::saveConfigData()
*/
public function saveConfigData(array $configData, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SCRIPT_SETTINGS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SCRIPT_SETTINGS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -327,6 +332,7 @@ class ScriptSettings implements ConfiguratorMenu, CallbackListener {
$this->maniaControl->getConfigurator()->showMenu($player, $this);
}
/**
* Apply the Array of new Script Settings
*
@ -406,6 +412,63 @@ class ScriptSettings implements ConfiguratorMenu, CallbackListener {
if (is_bool($value)) {
return ($value ? 'True' : 'False');
}
return (string)$value;
return (string) $value;
}
/**
* Initializes the communication Listenings
*/
private function initalizeCommunicationListenings() {
//Communication Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_SCRIPT_SETTINGS, $this, function ($data) {
try {
$scriptSettings = $this->maniaControl->getClient()->getModeScriptSettings();
} catch (GameModeException $e) {
return new CommunicationAnswer($e->getMessage(), true);
}
return new CommunicationAnswer($scriptSettings);
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::SET_SCRIPT_SETTINGS, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "scriptSettings")) {
return new CommunicationAnswer("No valid ScriptSettings provided!", true);
}
try {
$scriptSettings = $this->maniaControl->getClient()->getModeScriptSettings();
} catch (GameModeException $e) {
return new CommunicationAnswer($e->getMessage(), true);
}
$newSettings = array();
foreach ($data->scriptSettings as $name => $value) {
if (!isset($scriptSettings[$name])) {
var_dump('no setting ' . $name);
continue;
}
if ($value == $scriptSettings[$name]) {
// Not changed
continue;
}
$newSettings[$name] = $value;
settype($newSettings[$name], gettype($scriptSettings[$name]));
}
//No new Settings
if (empty($newSettings)) {
return new CommunicationAnswer(array("success" => true));
}
//Trigger Scriptsettings Changed Callback
$this->maniaControl->getCallbackManager()->triggerCallback(self::CB_SCRIPTSETTINGS_CHANGED);
//Set the Settings
$success = $this->maniaControl->getClient()->setModeScriptSettings($newSettings);
return new CommunicationAnswer(array("success" => $success));
});
}
}

View File

@ -0,0 +1,207 @@
<?php
namespace ManiaControl\Files;
use cURL\Event;
use cURL\Request;
use ManiaControl\ManiaControl;
/**
* Asynchronous Http Request Class
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class AsyncHttpRequest {
/*
* Constants
*/
const CONTENT_TYPE_JSON = 'application/json';
/*
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl;
private $url;
private $function;
private $content;
private $compression = false;
private $contentType = 'text/xml; charset=UTF-8;';
private $headers = array();
public function __construct($maniaControl, $url) {
$this->maniaControl = $maniaControl;
$this->url = $url;
}
/**
* Create a new cURL Request for the given URL
*
* @param string $url
* @return Request
*/
private function newRequest($url) {
$request = new Request($url);
$request->getOptions()->set(CURLOPT_TIMEOUT, 60)->set(CURLOPT_HEADER, false)// don't display response header
->set(CURLOPT_CRLF, true)// linux line feed
->set(CURLOPT_ENCODING, '')// accept encoding
->set(CURLOPT_USERAGENT, 'ManiaControl v' . ManiaControl::VERSION)// user-agent
->set(CURLOPT_RETURNTRANSFER, true)//
->set(CURLOPT_FOLLOWLOCATION, true)// support redirect
->set(CURLOPT_SSL_VERIFYPEER, false);
return $request;
}
/**
* Carry out a GetData Request
*
* @param int $keepAlive
*/
public function getData($keepAlive = 0) {
array_push($this->headers, 'Content-Type: ' . $this->contentType);
if ($keepAlive) {
array_push($this->headers, 'Keep-Alive: ' . $keepAlive);
array_push($this->headers, 'Connection: Keep-Alive');
}
$request = $this->newRequest($this->url);
$request->getOptions()->set(CURLOPT_AUTOREFERER, true)// accept link reference
->set(CURLOPT_HTTPHEADER, $this->headers); // headers
$this->processRequest($request);
}
/**
* Carry out a PostData Request
*/
public function postData() {
array_push($this->headers, 'Content-Type: ' . $this->contentType);
array_push($this->headers, 'Keep-Alive: timeout=600, max=2000');
array_push($this->headers, 'Connection: Keep-Alive');
$content = str_replace(array("\r", "\n"), '', $this->content);
if ($this->compression) {
$content = zlib_encode($content, 31);
array_push($this->headers, 'Content-Encoding: gzip');
}
$request = $this->newRequest($this->url);
$request->getOptions()->set(CURLOPT_POST, true)// post method
->set(CURLOPT_POSTFIELDS, $content)// post content field
->set(CURLOPT_HTTPHEADER, $this->headers) // headers
;
$this->processRequest($request);
}
/**
* Processes the Request
*
* @param Request $request
*/
private function processRequest(Request $request) {
$request->addListener('complete', function (Event $event) {
$error = null;
$content = null;
if ($event->response->hasError()) {
$error = $event->response->getError()->getMessage();
} else {
$content = $event->response->getContent();
}
call_user_func($this->function, $content, $error);
});
$fileReader = $this->maniaControl->getFileReader();
$fileReader->addRequest($request);
}
/**
* @param $url
* @return $this
*/
public function setURL($url) {
$this->url = $url;
return $this;
}
/**
* @param callable $function
* @return $this
*/
public function setCallable($function) {
$this->function = $function;
return $this;
}
/**
* @return mixed
*/
public function getContent() {
return $this->content;
}
/**
* @param mixed $content
* @return $this
*/
public function setContent($content) {
$this->content = $content;
return $this;
}
/**
* @return boolean
*/
public function getCompression() {
return $this->compression;
}
/**
* @param boolean $compression
* @return $this
*/
public function setCompression($compression) {
$this->compression = $compression;
return $this;
}
/**
* @return array
*/
public function getHeaders() {
return $this->headers;
}
/**
* @param array $headers
* @return $this
*/
public function setHeaders($headers) {
if(is_array($headers)){
$this->headers = $headers;
}
return $this;
}
/**
* @return string
*/
public function getContentType() {
return $this->contentType;
}
/**
* @param string $contentType
* @return $this
*/
public function setContentType($contentType) {
$this->contentType = $contentType;
return $this;
}
}

View File

@ -2,7 +2,6 @@
namespace ManiaControl\Files;
use cURL\Event;
use cURL\Request;
use ManiaControl\ManiaControl;
@ -38,10 +37,10 @@ class AsynchronousFileReader {
public static function newRequestTest($url) {
$request = new Request($url);
$request->getOptions()->set(CURLOPT_TIMEOUT, 60)->set(CURLOPT_HEADER, false) // don't display response header
->set(CURLOPT_CRLF, true) // linux line feed
->set(CURLOPT_ENCODING, '') // accept encoding
->set(CURLOPT_USERAGENT, 'ManiaControl v' . ManiaControl::VERSION) // user-agent
$request->getOptions()->set(CURLOPT_TIMEOUT, 60)->set(CURLOPT_HEADER, false)// don't display response header
->set(CURLOPT_CRLF, true)// linux line feed
->set(CURLOPT_ENCODING, '')// accept encoding
->set(CURLOPT_USERAGENT, 'ManiaControl v' . ManiaControl::VERSION)// user-agent
->set(CURLOPT_RETURNTRANSFER, true); // return instead of output content
return $request;
}
@ -66,93 +65,15 @@ class AsynchronousFileReader {
* @param callable $function
* @param string $contentType
* @param int $keepAlive
* @param array $headers Additional Headers
* @deprecated @see ManiaControl\Files\AsyncHttpRequest
*/
public function loadFile($url, callable $function, $contentType = 'UTF-8', $keepAlive = 0) {
$headers = array();
array_push($headers, 'Content-Type: ' . $contentType);
if ($keepAlive) {
array_push($headers, 'Keep-Alive: ' . $keepAlive);
array_push($headers, 'Connection: Keep-Alive');
}
$request = $this->newRequest($url);
$request->getOptions()->set(CURLOPT_AUTOREFERER, true) // accept link reference
->set(CURLOPT_HTTPHEADER, $headers); // headers
$request->addListener('complete', function (Event $event) use (&$function) {
$error = null;
$content = null;
if ($event->response->hasError()) {
$error = $event->response->getError()->getMessage();
} else {
$content = $event->response->getContent();
}
call_user_func($function, $content, $error);
});
$this->addRequest($request);
public function loadFile($url, callable $function, $contentType = 'UTF-8', $keepAlive = 0, $headers = array()) {
$httpRequest = new AsyncHttpRequest($this->maniaControl, $url);
$httpRequest->setCallable($function)->setContentType($contentType)->setHeaders($headers);
$httpRequest->getData($keepAlive);
}
/**
* Create a new cURL Request for the given URL
*
* @param string $url
* @return Request
*/
protected function newRequest($url) {
$request = new Request($url);
$request->getOptions()->set(CURLOPT_TIMEOUT, 60)->set(CURLOPT_HEADER, false) // don't display response header
->set(CURLOPT_CRLF, true) // linux line feed
->set(CURLOPT_ENCODING, '') // accept encoding
->set(CURLOPT_USERAGENT, 'ManiaControl v' . ManiaControl::VERSION) // user-agent
->set(CURLOPT_RETURNTRANSFER, true); // return instead of output content
return $request;
}
//TODO remove, they are just for testing dedimania
/**
* Add a Request to the queue
*
* @param Request $request
*/
protected function addRequest(Request $request) {
array_push($this->requests, $request);
}
public function postDataTest(Request $request, $url, callable $function, $content, $compression = false, $contentType = 'text/xml; charset=UTF-8;') {
$headers = array();
array_push($headers, 'Content-Type: ' . $contentType);
array_push($headers, 'Keep-Alive: timeout=600, max=2000');
array_push($headers, 'Connection: Keep-Alive');
$content = str_replace(array("\r", "\n"), '', $content);
if ($compression) {
$content = zlib_encode($content, 31);
array_push($headers, 'Content-Encoding: gzip');
}
$request->getOptions()->set(CURLOPT_POST, true) // post method
->set(CURLOPT_POSTFIELDS, $content) // post content field
->set(CURLOPT_HTTPHEADER, $headers) // headers
;
$request->addListener('complete', function (Event $event) use (&$function) {
$error = null;
$content = null;
if ($event->response->hasError()) {
$error = $event->response->getError()->getMessage();
} else {
$content = $event->response->getContent();
}
call_user_func($function, $content, $error);
});
$this->addRequest($request);
}
/**
* Send Data via POST Method
*
@ -161,36 +82,21 @@ class AsynchronousFileReader {
* @param string $content
* @param bool $compression
* @param string $contentType
* @param array $headers Additional Headers
* @deprecated @see ManiaControl\Files\AsyncHttpRequest
*/
public function postData($url, callable $function, $content, $compression = false, $contentType = 'text/xml; charset=UTF-8;') {
public function postData($url, callable $function, $content, $compression = false, $contentType = 'text/xml; charset=UTF-8;', $headers = array()) {
$httpRequest = new AsyncHttpRequest($this->maniaControl, $url);
$httpRequest->setCallable($function)->setContent($content)->setCompression($compression)->setContentType($contentType)->setHeaders($headers);
$httpRequest->postData();
}
$headers = array();
array_push($headers, 'Content-Type: ' . $contentType);
array_push($headers, 'Keep-Alive: timeout=600, max=2000');
array_push($headers, 'Connection: Keep-Alive');
$content = str_replace(array("\r", "\n"), '', $content);
if ($compression) {
$content = zlib_encode($content, 31);
array_push($headers, 'Content-Encoding: gzip');
}
$request = $this->newRequest($url);
$request->getOptions()->set(CURLOPT_POST, true) // post method
->set(CURLOPT_POSTFIELDS, $content) // post content field
->set(CURLOPT_HTTPHEADER, $headers) // headers
;
$request->addListener('complete', function (Event $event) use (&$function) {
$error = null;
$content = null;
if ($event->response->hasError()) {
$error = $event->response->getError()->getMessage();
} else {
$content = $event->response->getContent();
}
call_user_func($function, $content, $error);
});
$this->addRequest($request);
/**
* Add a Request to the queue, DO NOT CALL MANUALLY!
*
* @param Request $request
*/
public function addRequest(Request $request) {
array_push($this->requests, $request);
}
}

View File

@ -8,10 +8,15 @@ use ManiaControl\Bills\BillManager;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\CallbackManager;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Callbacks\EchoManager;
use ManiaControl\Callbacks\TimerListener;
use ManiaControl\Callbacks\TimerManager;
use ManiaControl\Commands\CommandListener;
use ManiaControl\Commands\CommandManager;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationManager;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Configurator\Configurator;
use ManiaControl\Database\Database;
use ManiaControl\Files\AsynchronousFileReader;
@ -38,11 +43,11 @@ use Maniaplanet\DedicatedServer\Xmlrpc\TransportException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class ManiaControl implements CallbackListener, CommandListener, TimerListener {
class ManiaControl implements CallbackListener, CommandListener, TimerListener, CommunicationListener {
/*
* Constants
*/
const VERSION = '0.155';
const VERSION = '0.163';
const API_VERSION = '2013-04-16';
const MIN_DEDIVERSION = '2014-04-02_18_00';
const SCRIPT_TIMEOUT = 10;
@ -164,6 +169,10 @@ class ManiaControl implements CallbackListener, CommandListener, TimerListener {
*/
private $requestQuitMessage = null;
/** @var EchoManager $echoManager */
private $echoManager = null;
private $communicationManager = null;
/**
* Construct a new ManiaControl instance
*/
@ -176,6 +185,8 @@ class ManiaControl implements CallbackListener, CommandListener, TimerListener {
// Load ManiaControl Modules
$this->callbackManager = new CallbackManager($this);
$this->echoManager = new EchoManager($this);
$this->communicationManager = new CommunicationManager($this);
$this->timerManager = new TimerManager($this);
$this->database = new Database($this);
$this->fileReader = new AsynchronousFileReader($this);
@ -194,6 +205,7 @@ class ManiaControl implements CallbackListener, CommandListener, TimerListener {
$this->pluginManager = new PluginManager($this);
$this->updateManager = new UpdateManager($this);
$this->getErrorHandler()->init();
// Permissions
@ -207,6 +219,18 @@ class ManiaControl implements CallbackListener, CommandListener, TimerListener {
// Check connection every 30 seconds
$this->getTimerManager()->registerTimerListening($this, 'checkConnection', 1000 * 30);
// Communication Methods
$this->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::RESTART_MANIA_CONTROL, $this, function ($data) {
//Delay Shutdown to send answer first
$this->getTimerManager()->registerOneTimeListening($this, function () use ($data) {
if (is_object($data) && property_exists($data, "message")) {
$this->restart($data->message);
}
$this->restart();
}, 3000);
return new CommunicationAnswer();
});
}
/**
@ -278,6 +302,24 @@ class ManiaControl implements CallbackListener, CommandListener, TimerListener {
return $this->callbackManager;
}
/**
* Return the echo manager
*
* @return EchoManager
*/
public function getEchoManager() {
return $this->echoManager;
}
/**
* Return the socket manager
*
* @return CommunicationManager
*/
public function getCommunicationManager() {
return $this->communicationManager;
}
/**
* Return the chat
*
@ -465,6 +507,9 @@ class ManiaControl implements CallbackListener, CommandListener, TimerListener {
* @param string $message
*/
public function restart($message = null) {
// Trigger callback on Restart
$this->getCallbackManager()->triggerCallback(Callbacks::ONRESTART);
// Announce restart
try {
$this->getChat()->sendInformation('Restarting ManiaControl...');

View File

@ -130,14 +130,18 @@ class ManiaExchangeList implements CallbackListener, ManialinkPageAnswerListener
}
}
// search for matching maps
$this->maniaControl->getMapManager()->getMXManager()->fetchMapsAsync(function (array $maps) use (&$player) {
//Search the Maps
$mxSearch = new ManiaExchangeMapSearch($this->maniaControl);
$mxSearch->setAuthorName($author);
$mxSearch->setEnvironments($environment);
$mxSearch->setMapName($searchString);
$mxSearch->fetchMapsAsync(function (array $maps) use (&$player) {
if (!$maps) {
$this->maniaControl->getChat()->sendError('No maps found, or MX is down!', $player->login);
return;
}
$this->showManiaExchangeList($maps, $player);
}, $searchString, $author, $environment);
});
}
/**

View File

@ -9,7 +9,7 @@ use ManiaControl\Maps\MapManager;
use Maniaplanet\DedicatedServer\Xmlrpc\GameModeException;
/**
* Mania Exchange Info Searcher Class
* Mania Exchange Manager Class
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
@ -18,8 +18,9 @@ use Maniaplanet\DedicatedServer\Xmlrpc\GameModeException;
class ManiaExchangeManager {
/*
* Constants
* @deprecated SEARCH Constants
*/
//Search others
//Search orders (prior parameter) http://api.mania-exchange.com/documents/enums#orderings
const SEARCH_ORDER_NONE = -1;
const SEARCH_ORDER_TRACK_NAME = 0;
const SEARCH_ORDER_AUTHOR = 1;
@ -47,7 +48,7 @@ class ManiaExchangeManager {
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $maniaControl = null;
private $mxIdUidVector = array();
/**
@ -269,9 +270,9 @@ class ManiaExchangeManager {
/**
* @deprecated
* @see \ManiaControl\ManiaExchange\ManiaExchangeManager::fetchMapsAsync()
* @see \ManiaControl\ManiaExchange\ManiaExchangeMapSearch
*/
public function getMapsAsync(callable $function, $name = '', $author = '', $env = '', $maxMapsReturned = 100, $searchOrder = self::SEARCH_ORDER_UPDATED_NEWEST) {
public function getMapsAsync(callable $function, $name = '', $author = '', $env = '', $maxMapsReturned = 100, $searchOrder = ManiaExchangeMapSearch::SEARCH_ORDER_UPDATED_NEWEST) {
$this->fetchMapsAsync($function, $name, $author, $env, $maxMapsReturned, $searchOrder);
return true;
}
@ -285,73 +286,22 @@ class ManiaExchangeManager {
* @param string $env
* @param int $maxMapsReturned
* @param int $searchOrder
*
* @deprecated
* @see \ManiaControl\ManiaExchange\ManiaExchangeMapSearch
*/
public function fetchMapsAsync(callable $function, $name = '', $author = '', $env = '', $maxMapsReturned = 100, $searchOrder = self::SEARCH_ORDER_UPDATED_NEWEST) {
// TODO: remove $env because it's not really used?
public function fetchMapsAsync(callable $function, $name = '', $author = '', $env = '', $maxMapsReturned = 100, $sortOrder = ManiaExchangeMapSearch::SEARCH_ORDER_UPDATED_NEWEST) {
$mapSearch = new ManiaExchangeMapSearch($this->maniaControl);
$mapSearch->setMapName($name);
$mapSearch->setAuthorName($author);
$mapSearch->setMapLimit($maxMapsReturned);
$mapSearch->setPrioritySortOrder($sortOrder);
// Get Title Id
$titleId = $this->maniaControl->getServer()->titleId;
$titlePrefix = $this->maniaControl->getMapManager()->getCurrentMap()->getGame();
// compile search URL
$url = 'http://' . $titlePrefix . '.mania-exchange.com/tracksearch2/search?api=on';
$game = explode('@', $titleId);
$envNumber = $this->getEnvironment($game[0]);
if ($env || $envNumber > -1) {
$url .= '&environments=' . $envNumber;
}
if ($name) {
$url .= '&trackname=' . str_replace(" ", "%20", $name);
}
if ($author) {
$url .= '&author=' . $author;
if($env){
$mapSearch->setEnvironments($env);
}
$url .= '&priord=' . $searchOrder;
$url .= '&limit=' . $maxMapsReturned;
if ($titlePrefix !== "tm") {
$url .= '&minexebuild=' . self::MIN_EXE_BUILD;
}
// Get MapTypes
try {
$scriptInfos = $this->maniaControl->getClient()->getModeScriptInfo();
$mapTypes = $scriptInfos->compatibleMapTypes;
$url .= '&mtype=' . $mapTypes;
} catch (GameModeException $e) {
}
$this->maniaControl->getFileReader()->loadFile($url, function ($mapInfo, $error) use (&$function, $titlePrefix) {
if ($error) {
trigger_error($error);
return;
}
$mxMapList = json_decode($mapInfo);
if (!isset($mxMapList->results)) {
trigger_error('Cannot decode searched JSON data');
return;
}
$mxMapList = $mxMapList->results;
if ($mxMapList === null) {
trigger_error('Cannot decode searched JSON data');
return;
}
$maps = array();
foreach ($mxMapList as $map) {
if (!empty($map)) {
array_push($maps, new MXMapInfo($titlePrefix, $map));
}
}
call_user_func($function, $maps);
}, AsynchronousFileReader::CONTENT_TYPE_JSON);
$mapSearch->fetchMapsAsync($function);
}
/**

View File

@ -0,0 +1,437 @@
<?php
namespace ManiaControl\ManiaExchange;
use ManiaControl\Files\AsynchronousFileReader;
use ManiaControl\ManiaControl;
use Maniaplanet\DedicatedServer\Xmlrpc\GameModeException;
/**
* Mania Exchange Map Searching Class
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class ManiaExchangeMapSearch {
//Search orders (prior parameter) http://api.mania-exchange.com/documents/enums#orderings
const SEARCH_ORDER_NONE = -1;
const SEARCH_ORDER_TRACK_NAME = 0;
const SEARCH_ORDER_AUTHOR = 1;
const SEARCH_ORDER_UPLOADED_NEWEST = 2;
const SEARCH_ORDER_UPLOADED_OLDEST = 3;
const SEARCH_ORDER_UPDATED_NEWEST = 4;
const SEARCH_ORDER_UPDATED_OLDEST = 5;
const SEARCH_ORDER_ACTIVITY_LATEST = 6;
const SEARCH_ORDER_ACTIVITY_OLDEST = 7;
const SEARCH_ORDER_AWARDS_MOST = 8;
const SEARCH_ORDER_AWARDS_LEAST = 9;
const SEARCH_ORDER_COMMENTS_MOST = 10;
const SEARCH_ORDER_COMMENTS_LEAST = 11;
const SEARCH_ORDER_DIFFICULTY_EASIEST = 12;
const SEARCH_ORDER_DIFFICULTY_HARDEST = 13;
const SEARCH_ORDER_LENGTH_SHORTEST = 14;
const SEARCH_ORDER_LENGTH_LONGEST = 15;
const SEARCH_ORDER_TRACK_VALUE_LTH = 24;
const SEARCH_ORDER_TRACK_VALUE_HTL = 25;
const SEARCH_ORDER_ONLINE_RATING_LTH = 26;
const SEARCH_ORDER_ONLINE_RATING_HTL = 27;
//Special Search Orders (mode parameter): http://api.mania-exchange.com/documents/enums#modes
const SEARCH_ORDER_SPECIAL_DEFAULT = 0;
const SEARCH_ORDER_SPECIAL_USER_TRACKS = 1;
const SEARCH_ORDER_SPECIAL_LATEST_TRACKS = 2;
const SEARCH_ORDER_SPECIAL_RECENTLY_AWARDED = 3;
const SEARCH_ORDER_SPECIAL_BEST_OF_WEEK_AWARDS = 4;
const SEARCH_ORDER_SPECIAL_BEST_OF_MONTH_AWARDS = 5;
const SEARCH_ORDER_SPECIAL_MX_SUPPORTER_TRACKS = 10;
const SEARCH_ORDER_SPECIAL_DUO_ACCOUNT_TRACKS = 11;
const SEARCH_ORDER_SPECIAL_MOST_COMPETITIVE_WEEK = 19;
const SEARCH_ORDER_SPECIAL_MOST_COMPETITIVE_MONTH = 20;
const SEARCH_ORDER_SPECIAL_BEST_ONLINE_RATING_WEEK = 21;
const SEARCH_ORDER_SPECIAL_BEST_ONLINE_RATING_MONTH = 22;
//Private Properties
private $url = "";
private $titlePrefix = "";
private $mode = null;
private $mapName = null;
private $authorName = null;
private $mod = null;
private $authorId = null;
private $maniaScriptType = null;
private $titlePack = null;
private $replayType = null;
private $style = null;
private $length = null;
private $lengthOperator = null;
private $priorityOrder = null;
private $secondaryOrder = null;
private $environments = null;
private $vehicles = null;
private $page = null;
private $mapLimit = null;
private $unreleased = null;
private $mapGroup = null;
private $commentsMinLength = null;
private $customScreenshot = null;
private $minExeBuild = null;
private $envMix = null;
private $ghostBlocks = null;
private $embeddedObjects = null;
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
//TODO use class by mxlist
/**
* Construct map manager
*
* @param \ManiaControl\ManiaControl $maniaControl
*/
public function __construct(ManiaControl $maniaControl) {
$this->maniaControl = $maniaControl;
$titleId = $this->maniaControl->getServer()->titleId;
$this->titlePrefix = $this->maniaControl->getMapManager()->getCurrentMap()->getGame();
$this->url = 'https://' . $this->titlePrefix . '.mania-exchange.com/tracksearch2/search?api=on';
//Set some defaults:
$this->mapLimit = 100;
$this->priorityOrder = self::SEARCH_ORDER_UPDATED_NEWEST;
//Set Min Exe Build Default for games which are not Trackmania
if ($this->titlePrefix !== "tm") {
$this->minExeBuild = ManiaExchangeManager::MIN_EXE_BUILD;
}
//Set MapTypes
try {
$scriptInfos = $this->maniaControl->getClient()->getModeScriptInfo();
$mapTypes = $scriptInfos->compatibleMapTypes;
$this->maniaScriptType = $mapTypes;
} catch (GameModeException $e) {
}
//Set Environments on Trackmania
$game = explode('@', $titleId);
$envNumber = $this->getEnvironment($game[0]); //TODO enviroment as constant
if ($envNumber > -1) {
$this->environments = $envNumber;
}
}
/**
* Fetch a MapList Asynchronously
*
* @param callable $function
*/
public function fetchMapsAsync(callable $function) {
// compile search URL
$parameters = "";
if ($this->mode) {
$parameters .= "&mode=" . $this->mode;
}
if ($this->mapName) {
$parameters .= "&trackname=" . urlencode($this->mapName);
}
if ($this->authorName) {
$parameters .= "&author=" . urlencode($this->authorName);
}
if ($this->mod) {
$parameters .= "&mod=" . urlencode($this->mod);
}
if ($this->authorId) {
$parameters .= "&authorid= " . $this->authorId;
}
if ($this->maniaScriptType) {
$parameters .= "&mtype=" . urlencode($this->maniaScriptType);
}
if ($this->titlePack) {
$parameters .= "&tpack=" . urlencode($this->titlePack);
}
if ($this->replayType) {
$parameters .= "&rytpe=" . $this->replayType;
}
if ($this->style) {
$parameters .= "&style=" . $this->style;
}
if ($this->length) {
$parameters .= "&length=" . $this->length;
}
if ($this->lengthOperator) {
$parameters .= "&lengthop=" . $this->lengthOperator;
}
if ($this->priorityOrder) {
$parameters .= "&priord=" . $this->priorityOrder;
}
if ($this->secondaryOrder) {
$parameters .= "&secord=" . $this->secondaryOrder;
}
if ($this->environments) {
$parameters .= "&environemtns=" . $this->environments;
}
if ($this->vehicles) {
$parameters .= "&vehicles=" . $this->vehicles;
}
if ($this->page) {
$parameters .= "&page=" . $this->page;
}
if ($this->mapLimit) {
$parameters .= "&limit=" . $this->mapLimit;
}
if (isset($this->unreleased)) {
$parameters .= "&unreleased=" . (int) $this->unreleased;
}
if ($this->mapGroup) {
$parameters .= "&mapgroup=" . $this->mapGroup;
}
if ($this->commentsMinLength) {
$parameters .= "&commentsminlength=" . $this->commentsMinLength;
}
if (isset($this->customScreenshot)) {
$parameters .= "&customscreenshot=" . $this->customScreenshot;
}
if ($this->minExeBuild) {
$parameters .= "&minexebuild=" . urlencode($this->minExeBuild);
}
if (isset($this->envMix)) {
$parameters .= "&envmix=" . (int) $this->envMix;
}
if (isset($this->ghostBlocks)) {
$parameters .= "&ghostblocks=" . (int) $this->ghostBlocks;
}
if (isset($this->embeddedObjects)) {
$parameters .= "&embeddedobjects=" . (int) $this->embeddedObjects;
}
$this->maniaControl->getFileReader()->loadFile($this->url . $parameters, function ($mapInfo, $error) use (&$function) {
if ($error) {
trigger_error($error);
return;
}
$mxMapList = json_decode($mapInfo);
if (!isset($mxMapList->results)) {
trigger_error('Cannot decode searched JSON data');
return;
}
$mxMapList = $mxMapList->results;
if ($mxMapList === null) {
trigger_error('Cannot decode searched JSON data');
return;
}
$maps = array();
foreach ($mxMapList as $map) {
if (!empty($map)) {
array_push($maps, new MXMapInfo($this->titlePrefix, $map));
}
}
call_user_func($function, $maps);
}, AsynchronousFileReader::CONTENT_TYPE_JSON);
}
/**
* Get the Current Environment by String
*
* @param string $env
* @return int
*/
private function getEnvironment($env) {
switch ($env) {
case 'TMCanyon':
return 1;
case 'TMStadium':
return 2;
case 'TMValley':
return 3;
default:
return -1;
}
}
/**
* @param int $mode
*/
public function setMode($mode) {
$this->mode = $mode;
}
/**
* @param string $mapName
*/
public function setMapName($mapName) {
$this->mapName = $mapName;
}
/**
* @param string $authorName
*/
public function setAuthorName($authorName) {
$this->authorName = $authorName;
}
/**
* @param int $authorId
*/
public function setAuthorId($authorId) {
$this->authorId = $authorId;
}
/**
* @param string $maniaScriptType
*/
public function setManiaScriptType($maniaScriptType) {
$this->maniaScriptType = $maniaScriptType;
}
/**
* @param string $mod
*/
public function setMod($mod) {
$this->mod = $mod;
}
/**
* @param string $titlePack
*/
public function setTitlePack($titlePack) {
$this->titlePack = $titlePack;
}
/**
* @param int $replayType
*/
public function setReplayType($replayType) {
$this->replayType = $replayType;
}
/**
* @param int $length
*/
public function setLength($length) {
$this->length = $length;
}
/**
* @param int $style
*/
public function setStyle($style) {
$this->style = $style;
}
/**
* @param int $lengthOperator
*/
public function setLengthOperator($lengthOperator) {
$this->lengthOperator = $lengthOperator;
}
/**
* @param int $secondaryOrder
*/
public function setSecondarySortOrder($secondaryOrder) {
$this->secondaryOrder = $secondaryOrder;
}
/**
* @param int $priorityOrder
*/
public function setPrioritySortOrder($priorityOrder) {
$this->priorityOrder = $priorityOrder;
}
/**
* @param string $environments
*/
public function setEnvironments($environments) {
$this->environments = $environments;
}
/**
* @param int $page
*/
public function setPage($page) {
$this->page = $page;
}
/**
* @param string $vehicles
*/
public function setVehicles($vehicles) {
$this->vehicles = $vehicles;
}
/**
* @param bool $unreleased
*/
public function setUnreleased($unreleased) {
$this->unreleased = $unreleased;
}
/**
* @param int $mapGroup
*/
public function setMapGroup($mapGroup) {
$this->mapGroup = $mapGroup;
}
/**
* @param int $commentsMinLength
*/
public function setCommentsMinLength($commentsMinLength) {
$this->commentsMinLength = $commentsMinLength;
}
/**
* @param bool $customScreenshot
*/
public function setCustomScreenshot($customScreenshot) {
$this->customScreenshot = $customScreenshot;
}
/**
* @param bool $envMix
*/
public function setEnvMix($envMix) {
$this->envMix = $envMix;
}
/**
* @param string $minExeBuild
*/
public function setMinExeBuild($minExeBuild) {
$this->minExeBuild = $minExeBuild;
}
/**
* @param bool $ghostBlocks
*/
public function setGhostBlocks($ghostBlocks) {
$this->ghostBlocks = $ghostBlocks;
}
/**
* @param bool $embeddedObjects
*/
public function setEmbeddedObjects($embeddedObjects) {
$this->embeddedObjects = $embeddedObjects;
}
/**
* @param null $mapLimit
*/
public function setMapLimit($mapLimit) {
$this->mapLimit = $mapLimit;
}
}

View File

@ -10,7 +10,7 @@ use ManiaControl\Players\Player;
use ManiaControl\Players\PlayerManager;
/**
* Class managing the Custom UI in TrackMania
* Class managing the Custom UI in ManiaPlanet
*
* @author ManiaControl Team <mail@maniacontrol.com>
* @copyright 2014-2015 ManiaControl Team
@ -28,7 +28,7 @@ class CustomUIManager implements CallbackListener, TimerListener {
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
/** @var customUI $customUI */
private $customUI = null;
private $customUI = null;
private $updateManialink = false;
/**

View File

@ -262,7 +262,7 @@ class ManialinkManager implements ManialinkPageAnswerListener, CallbackListener
}
} catch (UnknownPlayerException $e) {
return false;
} catch (FaultException $e){
} catch (FaultException $e) {
//TODO added 17.01.2015, remove later:
$this->maniaControl->getErrorHandler()->triggerDebugNotice("Fault Exception: ManiaLink Manager, Message: " . $e->getMessage());
return false;
@ -349,8 +349,11 @@ class ManialinkManager implements ManialinkPageAnswerListener, CallbackListener
return $success;
}
/**
* Adds a line of labels
* LabelLine should be an array with the following structure: array(array(positions), array(texts))
* or array($text1 => $pos1, $text2 => $pos2 ...)
*
* @param Frame $frame
* @param array $labelStrings
@ -366,21 +369,49 @@ class ManialinkManager implements ManialinkPageAnswerListener, CallbackListener
$profile = (isset($properties['profile']) ? $properties['profile'] : false);
$labels = array();
foreach ($labelStrings as $text => $x) {
$label = new Label_Text();
$frame->add($label);
$label->setHAlign($hAlign);
$label->setX($x);
$label->setStyle($style);
$label->setTextSize($textSize);
$label->setText($text);
$label->setTextColor($textColor);
if ($profile) {
$label->addPlayerProfileFeature($profile);
//If you call LabelLine with array(array(positions), array(texts))
if (count($labelStrings) == 2 && array_key_exists(0, $labelStrings) && array_key_exists(1, $labelStrings) && array_key_exists(0, $labelStrings[0]) && array_key_exists(0, $labelStrings[1])) {
$positions = $labelStrings[0];
$texts = $labelStrings[1];
if (count($positions) != count($texts)) {
trigger_error("LabelLine Position length is not equal to Text Length", E_USER_ERROR);
}
array_push($labels, $label);
foreach ($positions as $key => $x) {
$label = new Label_Text();
$frame->add($label);
$label->setHAlign($hAlign);
$label->setX($x);
$label->setStyle($style);
$label->setTextSize($textSize);
$label->setText($texts[$key]);
$label->setTextColor($textColor);
if ($profile) {
$label->addPlayerProfileFeature($profile);
}
array_push($labels, $label);
}
} else {
foreach ($labelStrings as $text => $x) {
$label = new Label_Text();
$frame->add($label);
$label->setHAlign($hAlign);
$label->setX($x);
$label->setStyle($style);
$label->setTextSize($textSize);
$label->setText($text);
$label->setTextColor($textColor);
if ($profile) {
$label->addPlayerProfileFeature($profile);
}
array_push($labels, $label);
}
}
return $labels;

View File

@ -161,6 +161,13 @@ class StyleManager {
$frame = new Frame();
$frame->setSize($width, $height)->setZ(45); //TODO place before scoreboards
//TODO remove: (just temporary fix for tm bug)
if ($this->maniaControl->getMapManager()->getCurrentMap()->getGame() === 'tm'
) {
$frame->setSize($width, $height)->setZ(32);
}
// Background Quad
$backgroundQuad = new Quad();
$frame->add($backgroundQuad);

View File

@ -2,6 +2,9 @@
namespace ManiaControl\Maps;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\ManiaControl;
use Maniaplanet\DedicatedServer\Xmlrpc\ChangeInProgressException;
@ -12,7 +15,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\ChangeInProgressException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class MapActions {
class MapActions implements CommunicationListener {
/*
* Private properties
*/
@ -26,12 +29,79 @@ class MapActions {
*/
public function __construct(ManiaControl $maniaControl) {
$this->maniaControl = $maniaControl;
//Communication Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::SKIP_MAP, $this, function ($data) {
$success = $this->skipMap();
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::RESTART_MAP, $this, function ($data) {
$success = $this->restartMap();
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::SKIP_TO_MAP, $this, function ($data) {
if (!is_object($data)) {
return new CommunicationAnswer("Error in provided Data", true);
}
if (property_exists($data, "mxId")) {
$success = $this->skipToMapByMxId($data->mxId);
} else if (property_exists($data, "mapUid")) {
$success = $this->skipToMapByUid($data->mapUid);
} else {
return new CommunicationAnswer("No mxId or mapUid provided.", true);
}
return new CommunicationAnswer(array("success" => $success));
});
}
/**
* Skips to a Map by its given UID
*
* @param String $uid
* @return bool
*/
public function skipToMapByUid($uid) {
//TODO message
//Check if Map exists
$map = $this->maniaControl->getMapManager()->getMapByUid($uid);
if (!$map) {
return false;
}
try {
$this->maniaControl->getClient()->jumpToMapIdent($uid);
} catch (ChangeInProgressException $e) {
return false;
}
return true;
}
/**
* Skips to a Map by its given MxId
*
* @param int $mxId
* @return bool
*/
public function skipToMapByMxId($mxId) {
$map = $this->maniaControl->getMapManager()->getMapByMxId($mxId);
if (!$map) {
return false;
}
return $this->skipToMapByUid($map->uid);
}
/**
* Skip the current Map
*
* @return bool
*/
public function skipMap() {
//TODO message
// Force an EndMap on the MapQueue to set the next Map
$this->maniaControl->getMapManager()->getMapQueue()->endMap(null);
@ -42,6 +112,27 @@ class MapActions {
try {
$this->maniaControl->getClient()->nextMap();
} catch (ChangeInProgressException $e) {
return false;
}
return true;
}
/**
* Restarts the Current Map
*
* @return bool
*/
public function restartMap() {
//TODO message
//Restarts the Current Map
try {
$this->maniaControl->getClient()->restartMap();
} catch (ChangeInProgressException $e) {
return false;
}
return true;
}
}

View File

@ -436,8 +436,18 @@ class MapCommands implements CommandListener, ManialinkPageAnswerListener, Callb
*/
private function showMapListKarma($best, Player $player) {
/** @var \MCTeam\KarmaPlugin $karmaPlugin */
$karmaPlugin = $this->maniaControl->getPluginManager()->getPlugin(MapList::DEFAULT_KARMA_PLUGIN);
$karmaPlugin = $this->maniaControl->getPluginManager()->getPlugin(MapList::DEFAULT_KARMA_PLUGIN);
$displayMxKarma = $this->maniaControl->getSettingManager()->getSettingValue($karmaPlugin, $karmaPlugin::SETTING_WIDGET_DISPLAY_MX);
if ($karmaPlugin) {
//Sort by Mx Karma in Maplist
if ($displayMxKarma) { //TODO
//Sort by Local Karma in Maplist
} else {
}
$maps = $this->maniaControl->getMapManager()->getMaps();
$mapList = array();
foreach ($maps as $map) {

View File

@ -122,7 +122,7 @@ class MapList implements ManialinkPageAnswerListener, CallbackListener {
$height = $this->maniaControl->getManialinkManager()->getStyleManager()->getListWidgetsHeight();
if ($pageIndex < 0) {
$pageIndex = (int)$player->getCache($this, self::CACHE_CURRENT_PAGE);
$pageIndex = (int) $player->getCache($this, self::CACHE_CURRENT_PAGE);
}
$player->setCache($this, self::CACHE_CURRENT_PAGE, $pageIndex);
$queueBuffer = $this->maniaControl->getMapManager()->getMapQueue()->getQueueBuffer();
@ -441,9 +441,20 @@ class MapList implements ManialinkPageAnswerListener, CallbackListener {
// Display Karma bar
if ($karmaPlugin) {
$karma = $karmaPlugin->getMapKarma($map);
$votes = $karmaPlugin->getMapVotes($map);
if (is_numeric($karma)) {
$displayMxKarma = $this->maniaControl->getSettingManager()->getSettingValue($karmaPlugin, $karmaPlugin::SETTING_WIDGET_DISPLAY_MX);
//Display Mx Karma
if ($displayMxKarma && $map->mx) {
$karma = $map->mx->ratingVoteAverage / 100;
$votes = array("count" => $map->mx->ratingVoteCount);
//Display Local Karma
} else {
$karma = $karmaPlugin->getMapKarma($map);
$votes = $karmaPlugin->getMapVotes($map);
}
if (is_numeric($karma) && $votes['count'] > 0) {
if ($this->maniaControl->getSettingManager()->getSettingValue($karmaPlugin, $karmaPlugin::SETTING_NEWKARMA)
) {
$karmaText = ' ' . round($karma * 100.) . '% (' . $votes['count'] . ')';
@ -692,7 +703,7 @@ class MapList implements ManialinkPageAnswerListener, CallbackListener {
default:
if (substr($actionId, 0, strlen(self::ACTION_PAGING_CHUNKS)) === self::ACTION_PAGING_CHUNKS) {
// Paging chunks
$neededPage = (int)substr($actionId, strlen(self::ACTION_PAGING_CHUNKS));
$neededPage = (int) substr($actionId, strlen(self::ACTION_PAGING_CHUNKS));
$this->showMapList($player, null, $neededPage - 1);
}
break;

View File

@ -6,6 +6,9 @@ use ManiaControl\Admin\AuthenticationManager;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\CallbackManager;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Files\FileUtil;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
@ -30,7 +33,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\UnavailableFeatureException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class MapManager implements CallbackListener {
class MapManager implements CallbackListener, CommunicationListener {
/*
* Constants
*/
@ -135,34 +138,9 @@ class MapManager implements CallbackListener {
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_AUTOSAVE_MAPLIST, true);
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_MAPLIST_FILE, "MatchSettings/tracklist.txt");
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_WRITE_OWN_MAPLIST_FILE, false);
}
/**
* Initialize necessary database tables
*
* @return bool
*/
private function initTables() {
$mysqli = $this->maniaControl->getDatabase()->getMysqli();
$query = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_MAPS . "` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`mxid` int(11),
`uid` varchar(50) NOT NULL,
`name` varchar(150) NOT NULL,
`authorLogin` varchar(100) NOT NULL,
`fileName` varchar(100) NOT NULL,
`environment` varchar(50) NOT NULL,
`mapType` varchar(50) NOT NULL,
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `uid` (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Map Data' AUTO_INCREMENT=1;";
$result = $mysqli->query($query);
if ($mysqli->error) {
trigger_error($mysqli->error, E_USER_ERROR);
return false;
}
return $result;
//Initlaize Communication Listenings
$this->initalizeCommunicationListenings();
}
/**
@ -213,14 +191,16 @@ class MapManager implements CallbackListener {
/**
* Update a Map from Mania Exchange
*
* @param Player $admin
* @param string $uid
* @param Player|null $admin
* @param string $uid
*/
public function updateMap(Player $admin, $uid) {
public function updateMap($admin, $uid) {
$this->updateMapTimestamp($uid);
if (!isset($uid) || !isset($this->maps[$uid])) {
$this->maniaControl->getChat()->sendError("Error updating Map: Unknown UID '{$uid}'!", $admin);
if ($admin) {
$this->maniaControl->getChat()->sendError("Error updating Map: Unknown UID '{$uid}'!", $admin);
}
return;
}
@ -229,7 +209,11 @@ class MapManager implements CallbackListener {
$mxId = $map->mx->id;
$this->removeMap($admin, $uid, true, false);
$this->addMapFromMx($mxId, $admin->login, true);
if ($admin) {
$this->addMapFromMx($mxId, $admin->login, true);
} else {
$this->addMapFromMx($mxId, null, true);
}
}
/**
@ -263,15 +247,18 @@ class MapManager implements CallbackListener {
/**
* Remove a Map
*
* @param Player $admin
* @param string $uid
* @param bool $eraseFile
* @param bool $message
* @param Player|null $admin
* @param string $uid
* @param bool $eraseFile
* @param bool $message
* @return bool
*/
public function removeMap(Player $admin, $uid, $eraseFile = false, $message = true) {
public function removeMap($admin, $uid, $eraseFile = false, $message = true) {
if (!isset($this->maps[$uid])) {
$this->maniaControl->getChat()->sendError('Map does not exist!', $admin);
return;
if ($admin) {
$this->maniaControl->getChat()->sendError('Map does not exist!', $admin);
}
return false;
}
/** @var Map $map */
@ -296,26 +283,35 @@ class MapManager implements CallbackListener {
if ($eraseFile) {
// Check if ManiaControl can even write to the maps dir
$mapDir = $this->maniaControl->getClient()->getMapsDirectory();
if ($this->maniaControl->getServer()->checkAccess($mapDir)
) {
if ($this->maniaControl->getServer()->checkAccess($mapDir)) {
// Delete map file
if (!@unlink($mapDir . $map->fileName)) {
$this->maniaControl->getChat()->sendError("Couldn't erase the map file.", $admin);
if ($admin) {
$this->maniaControl->getChat()->sendError("Couldn't erase the map file.", $admin);
}
$eraseFile = false;
}
} else {
$this->maniaControl->getChat()->sendError("Couldn't erase the map file (no access).", $admin);
if ($admin) {
$this->maniaControl->getChat()->sendError("Couldn't erase the map file (no access).", $admin);
}
$eraseFile = false;
}
}
// Show Message
if ($message) {
$action = ($eraseFile ? 'erased' : 'removed');
$message = $admin->getEscapedNickname() . ' ' . $action . ' ' . $map->getEscapedName() . '!';
$action = ($eraseFile ? 'erased' : 'removed');
if ($admin) {
$message = $admin->getEscapedNickname() . ' ' . $action . ' ' . $map->getEscapedName() . '!';
} else {
$message = $map->getEscapedName() . ' got ' . $action . '!';
}
$this->maniaControl->getChat()->sendSuccess($message);
Logger::logInfo($message, true);
}
return true;
}
/**
@ -342,6 +338,7 @@ class MapManager implements CallbackListener {
* @param int $mapId
* @param string $login
* @param bool $update
* @param bool $displayMessage
*/
public function addMapFromMx($mapId, $login, $update = false) {
if (is_numeric($mapId)) {
@ -350,8 +347,10 @@ class MapManager implements CallbackListener {
&$login, &$update
) {
if (!$mapInfo || !isset($mapInfo->uploaded)) {
// Invalid id
$this->maniaControl->getChat()->sendError('Invalid MX-Id!', $login);
if ($login) {
// Invalid id
$this->maniaControl->getChat()->sendError('Invalid MX-Id!', $login);
}
return;
}
@ -360,14 +359,17 @@ class MapManager implements CallbackListener {
&$login, &$mapInfo, &$update
) {
if (!$file || $error) {
// Download error
$this->maniaControl->getChat()->sendError("Download failed: '{$error}'!", $login);
if ($login) {
// Download error
$this->maniaControl->getChat()->sendError("Download failed: '{$error}'!", $login);
}
return;
}
$this->processMapFile($file, $mapInfo, $login, $update);
});
}, 'UTF-8', 0, array("X-ManiaPlanet-ServerLogin: " . $this->maniaControl->getServer()->login));
});
}
return;
}
/**
@ -459,19 +461,21 @@ class MapManager implements CallbackListener {
}
$map->lastUpdate = time();
$player = $this->maniaControl->getPlayerManager()->getPlayer($login);
if (!$update) {
// Message
$message = $player->getEscapedNickname() . ' added $<' . $mapInfo->name . '$>!';
$this->maniaControl->getChat()->sendSuccess($message);
Logger::logInfo($message, true);
// Queue requested Map
$this->maniaControl->getMapManager()->getMapQueue()->addMapToMapQueue($login, $mapInfo->uid);
} else {
$message = $player->getEscapedNickname() . ' updated $<' . $mapInfo->name . '$>!';
$this->maniaControl->getChat()->sendSuccess($message);
Logger::logInfo($message, true);
//TODO messages for communication
if ($login) {
$player = $this->maniaControl->getPlayerManager()->getPlayer($login);
if (!$update) {
// Message
$message = $player->getEscapedNickname() . ' added $<' . $mapInfo->name . '$>!';
$this->maniaControl->getChat()->sendSuccess($message);
Logger::logInfo($message, true);
// Queue requested Map
$this->maniaControl->getMapManager()->getMapQueue()->addMapToMapQueue($login, $mapInfo->uid);
} else {
$message = $player->getEscapedNickname() . ' updated $<' . $mapInfo->name . '$>!';
$this->maniaControl->getChat()->sendSuccess($message);
Logger::logInfo($message, true);
}
}
}
@ -522,10 +526,8 @@ class MapManager implements CallbackListener {
$this->maniaControl->getCallbackManager()->triggerCallback(self::CB_MAPS_UPDATED);
// Write MapList
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_AUTOSAVE_MAPLIST)
) {
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_WRITE_OWN_MAPLIST_FILE)
) {
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_AUTOSAVE_MAPLIST)) {
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_WRITE_OWN_MAPLIST_FILE)) {
$serverLogin = $this->maniaControl->getServer()->login;
$matchSettingsFileName = "MatchSettings/{$serverLogin}.txt";
} else {
@ -884,4 +886,102 @@ class MapManager implements CallbackListener {
public function getMapsCount() {
return count($this->maps);
}
/**
* Initializes the Communication Listenings
*/
private function initalizeCommunicationListenings() {
// Communication Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_CURRENT_MAP, $this, function ($data) {
return new CommunicationAnswer($this->getCurrentMap());
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_MAP_LIST, $this, function ($data) {
return new CommunicationAnswer($this->getMaps());
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_MAP, $this, function ($data) {
if (!is_object($data)) {
return new CommunicationAnswer("Error in provided Data", true);
}
if (property_exists($data, "mxId")) {
return new CommunicationAnswer($this->getMapByMxId($data->mxId));
} else if (property_exists($data, "mapUid")) {
return new CommunicationAnswer($this->getMapByUid($data->mapUid));
} else {
return new CommunicationAnswer("No mxId or mapUid provided.", true);
}
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::ADD_MAP, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "mxId")) {
return new CommunicationAnswer("No valid mxId provided.", true);
}
$this->addMapFromMx($data->mxId, null);
return new CommunicationAnswer();
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::REMOVE_MAP, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "mapUid")) {
return new CommunicationAnswer("No valid mapUid provided.", true);
}
if (!$this->getMapByUid($data->mapUid)) {
return new CommunicationAnswer("Map not found.", true);
}
$erase = false;
if (property_exists($data, "eraseMapFile")) {
$erase = $data->eraseMapFile;
}
$showMessage = true;
if (property_exists($data, "showChatMessage")) {
$showMessage = $data->showChatMessage;
}
$success = $this->removeMap(null, $data->mapUid, $erase, $showMessage);
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::UPDATE_MAP, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "mapUid")) {
return new CommunicationAnswer("No valid mapUid provided.", true);
}
$this->updateMap(null, $data->mapUid);
return new CommunicationAnswer();
});
}
/**
* Initialize necessary database tables
*
* @return bool
*/
private function initTables() {
$mysqli = $this->maniaControl->getDatabase()->getMysqli();
$query = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_MAPS . "` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`mxid` int(11),
`uid` varchar(50) NOT NULL,
`name` varchar(150) NOT NULL,
`authorLogin` varchar(100) NOT NULL,
`fileName` varchar(100) NOT NULL,
`environment` varchar(50) NOT NULL,
`mapType` varchar(50) NOT NULL,
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `uid` (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Map Data' AUTO_INCREMENT=1;";
$result = $mysqli->query($query);
if ($mysqli->error) {
trigger_error($mysqli->error, E_USER_ERROR);
return false;
}
return $result;
}
}

View File

@ -42,10 +42,10 @@ class MapQueue implements CallbackListener, CommandListener {
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $queuedMaps = array();
private $nextMap = null;
private $buffer = array();
private $nextNoQueue = false;
private $queuedMaps = array();
private $nextMap = null;
private $buffer = array();
private $nextNoQueue = false;
/**
* Construct a new map queue instance
@ -108,8 +108,7 @@ class MapQueue implements CallbackListener, CommandListener {
* @param Player $admin |null
*/
public function clearMapQueue(Player $admin = null) {
if ($admin && !$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_CLEAR_MAPQUEUE)
) {
if ($admin && !$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_CLEAR_MAPQUEUE)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
}
@ -223,8 +222,8 @@ class MapQueue implements CallbackListener, CommandListener {
if (array_key_exists($map->uid, $this->queuedMaps)) {
unset($this->queuedMaps[$map->uid]);
}
array_unshift($this->queuedMaps, array($player, $map, true));
$this->maniaControl->callbackManager->triggerCallback(self::CB_MAPQUEUE_CHANGED, array('add', $map));
}
}
@ -308,8 +307,7 @@ class MapQueue implements CallbackListener, CommandListener {
// Check if map is in the buffer
if (in_array($uid, $this->buffer)) {
$this->maniaControl->getChat()->sendError('That map has recently been played!', $login);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CLEAR_MAPQUEUE)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CLEAR_MAPQUEUE)) {
return;
}
}
@ -329,10 +327,10 @@ class MapQueue implements CallbackListener, CommandListener {
/**
* Remove a Map from the Map queue
*
* @param Player $player
* @param string $uid
* @param Player|null $player
* @param string $uid
*/
public function removeFromMapQueue(Player $player, $uid) {
public function removeFromMapQueue($player, $uid) {
if (!isset($this->queuedMaps[$uid])) {
return;
}
@ -340,7 +338,9 @@ class MapQueue implements CallbackListener, CommandListener {
$map = $this->queuedMaps[$uid][1];
unset($this->queuedMaps[$uid]);
$this->maniaControl->getChat()->sendInformation('$fa0$<$fff' . $map->name . '$> is removed from the Map-Queue by $<$fff' . $player->nickname . '$>.');
if ($player) {
$this->maniaControl->getChat()->sendInformation('$fa0$<$fff' . $map->name . '$> is removed from the Map-Queue by $<$fff' . $player->nickname . '$>.');
}
// Trigger callback
$this->maniaControl->getCallbackManager()->triggerCallback(self::CB_MAPQUEUE_CHANGED, array('remove', $map));
@ -359,8 +359,7 @@ class MapQueue implements CallbackListener, CommandListener {
}
$this->nextMap = null;
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SKIP_MAP_ON_LEAVE)
) {
if ($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SKIP_MAP_ON_LEAVE)) {
// Skip Map if requester has left
foreach ($this->queuedMaps as $queuedMap) {
$player = $queuedMap[0];
@ -423,8 +422,7 @@ class MapQueue implements CallbackListener, CommandListener {
return;
}
if (count($this->buffer) >= $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_BUFFERSIZE)
) {
if (count($this->buffer) >= $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_BUFFERSIZE)) {
array_shift($this->buffer);
}

View File

@ -5,6 +5,7 @@ namespace ManiaControl\Players;
use ManiaControl\ManiaControl;
use ManiaControl\Utils\ClassUtil;
use ManiaControl\Utils\Formatter;
use Maniaplanet\DedicatedServer\Structures\LadderStats;
/**
* Player Model Class
@ -17,55 +18,56 @@ class Player {
/*
* Public Properties
*/
public $index = -1;
public $pid = -1;
public $login = null;
public $nickname = null;
public $index = -1;
public $pid = -1;
public $login = null;
public $nickname = null;
public $rawNickname = null;
public $path = null;
public $authLevel = 0;
public $language = null;
public $avatar = null;
public $allies = array();
public $clubLink = null;
public $teamId = -1;
public $isOfficial = null;
public $path = null;
public $authLevel = 0;
public $language = null;
public $avatar = null;
public $allies = array();
public $clubLink = null;
public $teamId = -1;
public $isOfficial = null;
public $ladderScore = -1.;
public $ladderRank = -1;
public $ladderStats = null;
public $joinTime = -1;
public $ipAddress = null;
public $isConnected = true;
public $clientVersion = null;
public $downloadRate = -1;
public $uploadRate = -1;
public $skins = null;
public $ladderRank = -1;
/** @var LadderStats $ladderStats */
public $ladderStats = null;
public $joinTime = -1;
public $ipAddress = null;
public $isConnected = true;
public $clientVersion = null;
public $downloadRate = -1;
public $uploadRate = -1;
public $skins = null;
public $daysSinceZoneInscription = -1;
//Flags details
public $forcedSpectatorState = 0;
public $isReferee = false;
public $isPodiumReady = false;
public $isUsingStereoscopy = false;
public $forcedSpectatorState = 0;
public $isReferee = false;
public $isPodiumReady = false;
public $isUsingStereoscopy = false;
public $isManagedByAnOtherServer = false;
public $isServer = false;
public $hasPlayerSlot = false;
public $isBroadcasting = false;
public $hasJoinedGame = false;
public $isServer = false;
public $hasPlayerSlot = false;
public $isBroadcasting = false;
public $hasJoinedGame = false;
//SpectatorStatus details
public $isSpectator = false;
public $isSpectator = false;
public $isTemporarySpectator = false;
public $isPureSpectator = false;
public $autoTarget = false;
public $currentTargetId = 0;
public $isPureSpectator = false;
public $autoTarget = false;
public $currentTargetId = 0;
/*
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $cache = array();
private $cache = array();
/**
* Construct a new Player
@ -75,7 +77,7 @@ class Player {
*/
public function __construct(ManiaControl $maniaControl, $connected) {
$this->maniaControl = $maniaControl;
$this->isConnected = (bool)$connected;
$this->isConnected = (bool) $connected;
if ($connected) {
$this->joinTime = time();
}
@ -89,9 +91,9 @@ class Player {
*/
public static function parseLogin($player) {
if (is_object($player) && property_exists($player, 'login')) {
return (string)$player->login;
return (string) $player->login;
}
return (string)$player;
return (string) $player;
}
/**
@ -235,14 +237,14 @@ class Player {
public function updatePlayerFlags($flags) {
//Detail flags
$this->forcedSpectatorState = $flags % 10; // 0, 1 or 2
$this->isReferee = (bool)(intval($flags / 10) % 10);
$this->isPodiumReady = (bool)(intval($flags / 100) % 10);
$this->isUsingStereoscopy = (bool)(intval($flags / 1000) % 10);
$this->isManagedByAnOtherServer = (bool)(intval($flags / 10000) % 10);
$this->isServer = (bool)(intval($flags / 100000) % 10);
$this->hasPlayerSlot = (bool)(intval($flags / 1000000) % 10);
$this->isBroadcasting = (bool)(intval($flags / 10000000) % 10);
$this->hasJoinedGame = (bool)(intval($flags / 100000000) % 10);
$this->isReferee = (bool) (intval($flags / 10) % 10);
$this->isPodiumReady = (bool) (intval($flags / 100) % 10);
$this->isUsingStereoscopy = (bool) (intval($flags / 1000) % 10);
$this->isManagedByAnOtherServer = (bool) (intval($flags / 10000) % 10);
$this->isServer = (bool) (intval($flags / 100000) % 10);
$this->hasPlayerSlot = (bool) (intval($flags / 1000000) % 10);
$this->isBroadcasting = (bool) (intval($flags / 10000000) % 10);
$this->hasJoinedGame = (bool) (intval($flags / 100000000) % 10);
}
/**
@ -252,10 +254,10 @@ class Player {
*/
public function updateSpectatorStatus($spectatorStatus) {
//Details spectatorStatus
$this->isSpectator = (bool)($spectatorStatus % 10);
$this->isTemporarySpectator = (bool)(intval($spectatorStatus / 10) % 10);
$this->isPureSpectator = (bool)(intval($spectatorStatus / 100) % 10);
$this->autoTarget = (bool)(intval($spectatorStatus / 1000) % 10);
$this->isSpectator = (bool) ($spectatorStatus % 10);
$this->isTemporarySpectator = (bool) (intval($spectatorStatus / 10) % 10);
$this->isPureSpectator = (bool) (intval($spectatorStatus / 100) % 10);
$this->autoTarget = (bool) (intval($spectatorStatus / 1000) % 10);
$this->currentTargetId = intval($spectatorStatus / 10000);
}

View File

@ -8,6 +8,10 @@ use FML\Controls\Quad;
use FML\Controls\Quads\Quad_Icons64x64_1;
use FML\ManiaLink;
use ManiaControl\Admin\AuthenticationManager;
use ManiaControl\Callbacks\EchoListener;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use ManiaControl\Manialinks\ManialinkManager;
@ -26,7 +30,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\UnknownPlayerException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class PlayerActions {
class PlayerActions implements EchoListener, CommunicationListener {
/*
* Constants
*/
@ -36,6 +40,7 @@ class PlayerActions {
const SPECTATOR_SPECTATOR = 1;
const SPECTATOR_PLAYER = 2;
const SPECTATOR_BUT_KEEP_SELECTABLE = 3;
const ECHO_WARN_PLAYER = 'ManiaControl.PlayerManager.WarnPlayer';
/*
* Permission Setting Constants
@ -48,6 +53,7 @@ class PlayerActions {
const SETTING_PERMISSION_KICK_PLAYER = 'Kick Player';
const SETTING_PERMISSION_BAN_PLAYER = 'Ban Player';
/*
* Private properties
*/
@ -70,6 +76,75 @@ class PlayerActions {
$this->maniaControl->getAuthenticationManager()->definePermissionLevel(self::SETTING_PERMISSION_FORCE_PLAYER_PLAY, AuthenticationManager::AUTH_LEVEL_MODERATOR);
$this->maniaControl->getAuthenticationManager()->definePermissionLevel(self::SETTING_PERMISSION_FORCE_PLAYER_TEAM, AuthenticationManager::AUTH_LEVEL_MODERATOR);
$this->maniaControl->getAuthenticationManager()->definePermissionLevel(self::SETTING_PERMISSION_FORCE_PLAYER_SPEC, AuthenticationManager::AUTH_LEVEL_MODERATOR);
// Echo Warn Command (Usage: sendEcho json_encode("player" => "loginName")
$this->maniaControl->getEchoManager()->registerEchoListener(self::ECHO_WARN_PLAYER, $this, function ($params) {
$this->warnPlayer(null, $params->player, false);
});
//Communication Manager Methods
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::WARN_PLAYER, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "login")) {
return new CommunicationAnswer("You have to provide a valid player Login", true);
}
$success = $this->warnPlayer(null, $data->login, false);
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::MUTE_PLAYER, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "login")) {
return new CommunicationAnswer("You have to provide a valid player Login", true);
}
$success = $this->mutePlayer(null, $data->login, false);
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::UNMUTE_PLAYER, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "login")) {
return new CommunicationAnswer("You have to provide a valid player Login", true);
}
$success = $this->unMutePlayer(null, $data->login, false);
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::KICK_PLAYER, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "login")) {
return new CommunicationAnswer("You have to provide a valid player Login", true);
}
$message = "";
if (property_exists($data, "message")) {
$message = $data->message;
}
$success = $this->kickPlayer(null, $data->login, $message, false);
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::FORCE_PLAYER_TO_SPEC, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "login")) {
return new CommunicationAnswer("You have to provide a valid player Login", true);
}
//TODO allow parameters like spectator state
$success = $this->forcePlayerToSpectator(null, $data->login, self::SPECTATOR_BUT_KEEP_SELECTABLE, true, false);
return new CommunicationAnswer(array("success" => $success));
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::FORCE_PLAYER_TO_PLAY, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "login")) {
return new CommunicationAnswer("You have to provide a valid player Login", true);
}
//TODO allow parameters like spectator state
if (property_exists($data, "teamId")) {
$success = $this->forcePlayerToTeam(null, $data->login, $data->teamId, false);
} else {
$success = $this->forcePlayerToPlay(null, $data->login, true, true, false);
}
return new CommunicationAnswer(array("success" => $success));
});
}
/**
@ -78,26 +153,34 @@ class PlayerActions {
* @param string $adminLogin
* @param string $targetLogin
* @param int $teamId
* @param bool $calledByAdmin
* @return bool
*/
public function forcePlayerToTeam($adminLogin, $targetLogin, $teamId) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_FORCE_PLAYER_TEAM)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
public function forcePlayerToTeam($adminLogin, $targetLogin, $teamId, $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_FORCE_PLAYER_TEAM)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
if (!$admin) {
return false;
}
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target || !$admin) {
return;
if (!$target) {
return false;
}
if ($target->isSpectator) {
try {
if (!$this->forcePlayerToPlay($adminLogin, $targetLogin, true, false)) {
return;
if (!$this->forcePlayerToPlay($adminLogin, $targetLogin, true, false, $calledByAdmin)) {
return false;
}
} catch (FaultException $exception) {
$this->maniaControl->getChat()->sendException($exception, $admin);
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($exception, $admin);
}
}
}
@ -105,27 +188,43 @@ class PlayerActions {
$this->maniaControl->getClient()->forcePlayerTeam($target->login, $teamId);
} catch (ServerOptionsException $exception) {
$this->forcePlayerToPlay($adminLogin, $targetLogin);
return;
return false;
} catch (UnknownPlayerException $exception) {
$this->maniaControl->getChat()->sendException($exception, $admin);
return;
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($exception, $admin);
}
return false;
} catch (GameModeException $exception) {
$this->maniaControl->getChat()->sendException($exception, $admin);
return;
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($exception, $admin);
}
return false;
}
$chatMessage = false;
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
if ($teamId === self::TEAM_BLUE) {
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' into the Blue-Team!';
} else if ($teamId === self::TEAM_RED) {
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' into the Red-Team!';
if ($calledByAdmin) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
if ($teamId === self::TEAM_BLUE) {
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' into the Blue-Team!';
} else if ($teamId === self::TEAM_RED) {
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' into the Red-Team!';
}
} else {
if ($teamId === self::TEAM_BLUE) {
$chatMessage = $target->getEscapedNickname() . ' got forced into the Blue-Team!';
} else if ($teamId === self::TEAM_RED) {
$chatMessage = $target->getEscapedNickname() . ' got forced into the Red-Team!';
}
}
if (!$chatMessage) {
return;
return false;
}
$this->maniaControl->getChat()->sendInformation($chatMessage);
Logger::logInfo($chatMessage, true);
return true;
}
/**
@ -135,15 +234,18 @@ class PlayerActions {
* @param string $targetLogin
* @param bool $userIsAbleToSelect
* @param bool $displayAnnouncement
* @param bool $calledByAdmin
* @return bool
*/
public function forcePlayerToPlay($adminLogin, $targetLogin, $userIsAbleToSelect = true, $displayAnnouncement = true) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_FORCE_PLAYER_PLAY)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
public function forcePlayerToPlay($adminLogin, $targetLogin, $userIsAbleToSelect = true, $displayAnnouncement = true, $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_FORCE_PLAYER_PLAY)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return false;
@ -152,7 +254,9 @@ class PlayerActions {
try {
$this->maniaControl->getClient()->forceSpectator($target->login, self::SPECTATOR_PLAYER);
} catch (ServerOptionsException $exception) {
$this->maniaControl->getChat()->sendException($exception, $admin);
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($exception, $admin);
}
return false;
}
@ -160,14 +264,22 @@ class PlayerActions {
try {
$this->maniaControl->getClient()->forceSpectator($target->login, self::SPECTATOR_USER_SELECTABLE);
} catch (ServerOptionsException $exception) {
$this->maniaControl->getChat()->sendException($exception, $admin);
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($exception, $admin);
}
return false;
}
}
// Announce force
if ($displayAnnouncement) {
$chatMessage = $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' to Play!';
if ($calledByAdmin) {
$chatMessage = $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' to Play!';
} else {
$chatMessage = $target->getEscapedNickname() . ' got forced to Play!';
}
$this->maniaControl->getChat()->sendInformation($chatMessage);
}
@ -181,29 +293,43 @@ class PlayerActions {
* @param string $targetLogin
* @param int $spectatorState
* @param bool $releaseSlot
* @param bool $calledByAdmin
* @return bool
*/
public function forcePlayerToSpectator($adminLogin, $targetLogin, $spectatorState = self::SPECTATOR_BUT_KEEP_SELECTABLE, $releaseSlot = true) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_FORCE_PLAYER_SPEC)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
public function forcePlayerToSpectator($adminLogin, $targetLogin, $spectatorState = self::SPECTATOR_BUT_KEEP_SELECTABLE, $releaseSlot = true, $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_FORCE_PLAYER_SPEC)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
if (!$admin) {
return false;
}
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$admin || !$target || $target->isSpectator) {
return;
if (!$target || $target->isSpectator) {
return false;
}
try {
$this->maniaControl->getClient()->forceSpectator($target->login, $spectatorState);
} catch (ServerOptionsException $exception) {
$this->maniaControl->getChat()->sendException($exception, $admin->login);
return;
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($exception, $admin->login);
}
return false;
}
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' to Spectator!';
if ($calledByAdmin) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' forced ' . $target->getEscapedNickname() . ' to Spectator!';
} else {
$chatMessage = $target->getEscapedNickname() . ' got forced to Spectator!';
}
$this->maniaControl->getChat()->sendInformation($chatMessage);
Logger::logInfo($chatMessage, true);
@ -215,39 +341,50 @@ class PlayerActions {
} catch (UnknownPlayerException $e) {
}
}
return true;
}
/**
* UnMute a Player
*
* @param string $adminLogin
* @param string $targetLogin
* @param $adminLogin
* @param $targetLogin
* @param bool $calledByAdmin
* @return bool
*/
public function unMutePlayer($adminLogin, $targetLogin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_MUTE_PLAYER)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
public function unMutePlayer($adminLogin, $targetLogin, $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_MUTE_PLAYER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return;
return false;
}
try {
$this->maniaControl->getClient()->unIgnore($targetLogin);
} catch (NotInListException $e) {
$this->maniaControl->getChat()->sendError('Player is not ignored!', $adminLogin);
return;
return false;
}
if ($calledByAdmin) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' un-muted ' . $target->getEscapedNickname() . '!';
} else {
$chatMessage = $target->getEscapedNickname() . ' got un-muted!';
}
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' un-muted ' . $target->getEscapedNickname() . '!';
$this->maniaControl->getChat()->sendInformation($chatMessage);
Logger::logInfo($chatMessage, true);
return true;
}
/**
@ -255,32 +392,42 @@ class PlayerActions {
*
* @param string $adminLogin
* @param string $targetLogin
* @param bool $calledByAdmin
* @return bool
*/
public function mutePlayer($adminLogin, $targetLogin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_MUTE_PLAYER)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
public function mutePlayer($adminLogin, $targetLogin, $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_MUTE_PLAYER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return;
return false;
}
try {
$this->maniaControl->getClient()->ignore($targetLogin);
} catch (AlreadyInListException $e) {
$this->maniaControl->getChat()->sendError("Player already ignored!", $adminLogin);
return;
return false;
}
// Announce warning
if ($calledByAdmin) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' muted ' . $target->getEscapedNickname() . '!';
} else {
$chatMessage = $target->getEscapedNickname() . ' got muted!';
}
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' muted ' . $target->getEscapedNickname() . '!';
$this->maniaControl->getChat()->sendInformation($chatMessage);
Logger::logInfo($chatMessage, true);
return true;
}
/**
@ -288,19 +435,22 @@ class PlayerActions {
*
* @param string $adminLogin
* @param string $targetLogin
* @param bool $calledByAdmin
* @return bool
*/
public function warnPlayer($adminLogin, $targetLogin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_WARN_PLAYER)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
public function warnPlayer($adminLogin, $targetLogin, $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_WARN_PLAYER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return;
return false;
}
// Display warning message
@ -360,49 +510,78 @@ class PlayerActions {
$this->maniaControl->getManialinkManager()->displayWidget($maniaLink, $target);
// Announce warning
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' warned ' . $target->getEscapedNickname() . '!';
if ($calledByAdmin) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' warned ' . $target->getEscapedNickname() . '!';
} else {
$chatMessage = $target->getEscapedNickname() . ' got an administrative warning!';
}
$this->maniaControl->getChat()->sendInformation($chatMessage);
Logger::log($chatMessage, true);
return true;
}
/**
* Kick a Player
*
* @param string $adminLogin
* @param string $targetLogin
* @param $adminLogin
* @param $targetLogin
* @param string $message
* @param bool $calledByAdmin
* @return bool
*/
public function kickPlayer($adminLogin, $targetLogin, $message = '') {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_KICK_PLAYER)
) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return;
public function kickPlayer($adminLogin, $targetLogin, $message = '', $calledByAdmin = true) {
if ($calledByAdmin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_KICK_PLAYER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return false;
}
}
try {
if ($target->isFakePlayer()) {
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return false;
}
if ($target->isFakePlayer()) {
try {
$this->maniaControl->getClient()->disconnectFakePlayer($target->login);
} else {
$this->maniaControl->getClient()->kick($target->login, $message);
} catch (PlayerStateException $e) {
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($e, $admin);
}
return false;
}
} else {
try {
$this->maniaControl->getClient()->kick($target->login, $message);
} catch (UnknownPlayerException $e) {
if ($calledByAdmin) {
$this->maniaControl->getChat()->sendException($e, $admin);
}
return false;
}
} catch (UnknownPlayerException $e) {
$this->maniaControl->getChat()->sendException($e, $admin);
return;
}
// Announce kick
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' kicked ' . $target->getEscapedNickname() . '!';
if ($calledByAdmin) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($admin->authLevel);
$chatMessage = $title . ' ' . $admin->getEscapedNickname() . ' kicked ' . $target->getEscapedNickname() . '!';
} else {
$chatMessage = $target->getEscapedNickname() . ' got kicked!';
}
$this->maniaControl->getChat()->sendInformation($chatMessage);
Logger::logInfo($chatMessage, true);
return true;
}
/**
* Ban a Player
*
@ -412,17 +591,18 @@ class PlayerActions {
*/
public function banPlayer($adminLogin, $targetLogin, $message = '') {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_BAN_PLAYER)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_BAN_PLAYER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
}
$target = $this->maniaControl->getPlayerManager()->getPlayer($targetLogin);
if (!$target) {
return;
}
if ($target->isFakePlayer()) {
//Todo Validate (Problem: Not connected player isFakePlayer)
if ($target->isOfficial && $target->isFakePlayer()) {
$this->maniaControl->getChat()->sendError('It is not possible to Ban a bot', $admin);
return;
}
@ -450,8 +630,7 @@ class PlayerActions {
*/
public function unBanPlayer($adminLogin, $targetLogin) {
$admin = $this->maniaControl->getPlayerManager()->getPlayer($adminLogin);
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_BAN_PLAYER)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($admin, self::SETTING_PERMISSION_BAN_PLAYER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($admin);
return;
}
@ -485,14 +664,12 @@ class PlayerActions {
}
$authLevelName = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($authLevel);
if (!$this->maniaControl->getAuthenticationManager()->checkRight($admin, $authLevel + 1)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkRight($admin, $authLevel + 1)) {
$this->maniaControl->getChat()->sendError("You don't have the permission to add a {$authLevelName}!", $admin);
return;
}
if ($this->maniaControl->getAuthenticationManager()->checkRight($target, $authLevel)
) {
if ($this->maniaControl->getAuthenticationManager()->checkRight($target, $authLevel)) {
$this->maniaControl->getChat()->sendError("This Player is already {$authLevelName}!", $admin);
return;
}
@ -523,15 +700,13 @@ class PlayerActions {
return;
}
if (!$this->maniaControl->getAuthenticationManager()->checkRight($admin, $target->authLevel + 1)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkRight($admin, $target->authLevel + 1)) {
$title = $this->maniaControl->getAuthenticationManager()->getAuthLevelName($target->authLevel);
$this->maniaControl->getChat()->sendError("You can't revoke the Rights of a {$title}!", $admin);
return;
}
if ($this->maniaControl->getAuthenticationManager()->checkRight($target, AuthenticationManager::AUTH_LEVEL_MASTERADMIN)
) {
if ($this->maniaControl->getAuthenticationManager()->checkRight($target, AuthenticationManager::AUTH_LEVEL_MASTERADMIN)) {
$this->maniaControl->getChat()->sendError("MasterAdmins can't be removed!", $admin);
return;
}

View File

@ -62,7 +62,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $maniaControl = null;
private $playersListShown = array();
/**
@ -163,9 +163,9 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$headFrame = new Frame();
$frame->add($headFrame);
$headFrame->setY($posY - 5);
$labelLineArray = array('Id' => $posX + 5, 'Nickname' => $posX + 18, 'Login' => $posX + 70, 'Location' => $posX + 101);
if ($this->maniaControl->getAuthenticationManager()->checkRight($player, AuthenticationManager::AUTH_LEVEL_MODERATOR)
) {
if ($this->maniaControl->getAuthenticationManager()->checkRight($player, AuthenticationManager::AUTH_LEVEL_MODERATOR)) {
$labelLineArray['Actions'] = $posX + 135;
}
$this->maniaControl->getManialinkManager()->labelLine($headFrame, $labelLineArray);
@ -195,8 +195,9 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$lineQuad->setZ(0.001);
}
$array = array($index => $posX + 5, $listPlayer->nickname => $posX + 18, $listPlayer->login => $posX + 70, $path => $posX + 101);
$this->maniaControl->getManialinkManager()->labelLine($playerFrame, $array);
$positions = array($posX + 5, $posX + 18, $posX + 70, $posX + 101);
$texts = array($index, $listPlayer->nickname, $listPlayer->login, $path);
$this->maniaControl->getManialinkManager()->labelLine($playerFrame, array($positions, $texts));
$playerFrame->setY($posY);
@ -304,8 +305,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$description = 'View Player Profile of $<' . $listPlayer->nickname . '$>';
$playerQuad->addTooltipLabelFeature($descriptionLabel, $description);
if ($this->maniaControl->getAuthenticationManager()->checkRight($player, AuthenticationManager::AUTH_LEVEL_MODERATOR)
) {
if ($this->maniaControl->getAuthenticationManager()->checkRight($player, AuthenticationManager::AUTH_LEVEL_MODERATOR)) {
// Further Player actions Quad
$playerQuad = new Quad_Icons64x64_1();
$playerFrame->add($playerQuad);
@ -320,10 +320,8 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$playerQuad->addTooltipLabelFeature($descriptionLabel, $description);
}
if ($this->maniaControl->getServer()->isTeamMode()
) {
if ($this->maniaControl->getAuthenticationManager()->checkPermission($player, PlayerActions::SETTING_PERMISSION_FORCE_PLAYER_TEAM)
) {
if ($this->maniaControl->getServer()->isTeamMode()) {
if ($this->maniaControl->getAuthenticationManager()->checkPermission($player, PlayerActions::SETTING_PERMISSION_FORCE_PLAYER_TEAM)) {
// Force to Red-Team Quad
$redQuad = new Quad_Emblems();
$playerFrame->add($redQuad);
@ -350,8 +348,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$description = 'Force $<' . $listPlayer->nickname . '$> to Blue Team!';
$blueQuad->addTooltipLabelFeature($descriptionLabel, $description);
} else if ($this->maniaControl->getPluginManager()->isPluginActive(self::DEFAULT_CUSTOM_VOTE_PLUGIN)
) {
} else if ($this->maniaControl->getPluginManager()->isPluginActive(self::DEFAULT_CUSTOM_VOTE_PLUGIN)) {
// Kick Player Vote
$kickQuad = new Quad_UIConstruction_Buttons();
$playerFrame->add($kickQuad);
@ -365,8 +362,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$kickQuad->addTooltipLabelFeature($descriptionLabel, $description);
}
} else {
if ($this->maniaControl->getAuthenticationManager()->checkPermission($player, PlayerActions::SETTING_PERMISSION_FORCE_PLAYER_PLAY)
) {
if ($this->maniaControl->getAuthenticationManager()->checkPermission($player, PlayerActions::SETTING_PERMISSION_FORCE_PLAYER_PLAY)) {
// Force to Play
$playQuad = new Quad_Emblems();
$playerFrame->add($playQuad);
@ -381,8 +377,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
}
}
if ($this->maniaControl->getAuthenticationManager()->checkPermission($player, PlayerActions::SETTING_PERMISSION_FORCE_PLAYER_SPEC)
) {
if ($this->maniaControl->getAuthenticationManager()->checkPermission($player, PlayerActions::SETTING_PERMISSION_FORCE_PLAYER_SPEC)) {
// Force to Spectator Quad
$spectatorQuad = new Quad_BgRaceScore2();
$playerFrame->add($spectatorQuad);
@ -395,8 +390,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
// Force to Spectator Description Label
$description = 'Force $<' . $listPlayer->nickname . '$> to Spectator!';
$spectatorQuad->addTooltipLabelFeature($descriptionLabel, $description);
} else if ($this->maniaControl->getPluginManager()->isPluginActive(self::DEFAULT_CUSTOM_VOTE_PLUGIN)
) {
} else if ($this->maniaControl->getPluginManager()->isPluginActive(self::DEFAULT_CUSTOM_VOTE_PLUGIN)) {
// Force to Spectator Quad
$spectatorQuad = new Quad_BgRaceScore2();
$playerFrame->add($spectatorQuad);
@ -451,7 +445,7 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
// mainframe
$frame = new Frame();
$frame->setSize($width, $height);
$frame->setPosition($posX + $width / 2, 0);
$frame->setPosition($posX + $width / 2, 0, 31);
// Add Close Quad (X)
$closeQuad = new Quad_Icons64x64_1();
@ -467,14 +461,14 @@ class PlayerList implements ManialinkPageAnswerListener, CallbackListener, Timer
$backgroundQuad->setSize($width, $height);
$backgroundQuad->setImage('https://dl.dropboxusercontent.com/u/105352981/Stuff/CAM%20SM%20BORDER%20PNG.png'); //TODO just a test
//$backgroundQuad->setStyles($quadStyle, $quadSubstyle);
$backgroundQuad->setZ(0.2);
$backgroundQuad->setZ(-0.3);
// Background Quad
$backgroundQuad = new Quad();
$frame->add($backgroundQuad);
$backgroundQuad->setSize($width - 2, $height - 2);
$backgroundQuad->setStyles($quadStyle, $quadSubstyle);
$backgroundQuad->setZ(0.1);
$backgroundQuad->setZ(-0.4);
// Show headline
$label = new Label_Text();

View File

@ -7,6 +7,9 @@ use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\CallbackManager;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Callbacks\TimerListener;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use ManiaControl\Statistics\StatisticManager;
@ -20,7 +23,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\UnknownPlayerException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class PlayerManager implements CallbackListener, TimerListener {
class PlayerManager implements CallbackListener, TimerListener, CommunicationListener {
/*
* Constants
*/
@ -105,6 +108,12 @@ class PlayerManager implements CallbackListener, TimerListener {
// Player stats
$this->maniaControl->getStatisticManager()->defineStatMetaData(self::STAT_JOIN_COUNT);
$this->maniaControl->getStatisticManager()->defineStatMetaData(self::STAT_SERVERTIME, StatisticManager::STAT_TYPE_TIME);
// Communication Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_PLAYER_LIST, $this, function ($data) {
return new CommunicationAnswer($this->players);
});
}
/**
@ -184,6 +193,7 @@ class PlayerManager implements CallbackListener, TimerListener {
return $this->adminLists;
}
/**
* Handle OnInit callback
*/
@ -374,15 +384,24 @@ class PlayerManager implements CallbackListener, TimerListener {
* Get the count of all Players
*
* @param bool $withoutSpectators
* @param bool $withoutBots
* @return int
*/
public function getPlayerCount($withoutSpectators = true) {
if (!$withoutSpectators) {
return count($this->players);
}
public function getPlayerCount($withoutSpectators = true, $withoutBots = true) {
$count = 0;
foreach ($this->players as $player) {
if (!$player->isSpectator) {
$valid = true;
if ($withoutSpectators) {
if ($player->isSpectator) {
$valid = false;
}
}
if ($withoutBots) {
if ($player->isFakePlayer()) {
$valid = false;
}
}
if ($valid) {
$count++;
}
}
@ -516,6 +535,22 @@ class PlayerManager implements CallbackListener, TimerListener {
return $this->players;
}
/**
* Get a List of Spectators
*
* @return Player[]
*/
public function getSpectators() {
$spectators = array();
foreach ($this->players as $player) {
if ($player->isSpectator) {
$spectators[] = $player;
}
}
return $spectators;
}
/**
* Get the count of all spectators
*

View File

@ -3,6 +3,7 @@
namespace ManiaControl\Plugins;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\EchoListener;
use ManiaControl\Callbacks\TimerListener;
use ManiaControl\Commands\CommandListener;
use ManiaControl\Files\FileUtil;
@ -141,6 +142,9 @@ class PluginManager {
$plugin->unload();
if ($plugin instanceof EchoListener) {
$this->maniaControl->getEchoManager()->unregisterEchoListener($plugin);
}
if ($plugin instanceof CallbackListener) {
$this->maniaControl->getCallbackManager()->unregisterCallbackListener($plugin);
$this->maniaControl->getCallbackManager()->unregisterScriptCallbackListener($plugin);

View File

@ -40,13 +40,14 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
const SETTING_PERMISSION_CHANGE_SERVERSETTINGS = 'Change ServerSettings';
const COMMAND_EXTEND_WARMUP = 'WarmUp_Extend';
const COMMAND_FORCE_WARMUP = 'Command_ForceWarmUp';
const COMMAND_SET_PAUSE = 'Command_SetPause';
/*
* Private properties
*/
/** @var ManiaControl $maniaControl */
private $maniaControl = null;
private $serverShutdownTime = -1;
private $maniaControl = null;
private $serverShutdownTime = -1;
private $serverShutdownEmpty = false;
/**
@ -113,7 +114,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
try {
$scriptInfos = $this->maniaControl->getClient()->getModeScriptInfo();
foreach ($scriptInfos->commandDescs as $param) {
if ($param->name === self::COMMAND_FORCE_WARMUP) {
if ($param->name === self::COMMAND_FORCE_WARMUP || $param->name === self::COMMAND_SET_PAUSE) {
$pauseExists = true;
break;
}
@ -163,14 +164,12 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandCancelVote(array $chatCallback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CANCEL_VOTE)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CANCEL_VOTE)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
if ($this->maniaControl->getClient()->cancelVote()
) {
if ($this->maniaControl->getClient()->cancelVote()) {
$this->maniaControl->getChat()->sendInformation($player->getEscapedNickname() . ' cancelled the Vote!');
} else {
$this->maniaControl->getChat()->sendInformation("There's no vote running currently!", $player);
@ -187,8 +186,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandExtendWarmup(array $callback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_HANDLE_WARMUP)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_HANDLE_WARMUP)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -207,8 +205,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandEndWarmup(array $callback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_HANDLE_WARMUP)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_HANDLE_WARMUP)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -227,16 +224,27 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function setPause(array $callback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_SET_PAUSE)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_SET_PAUSE)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
//Normal Gamemodes
try {
$this->maniaControl->getClient()->sendModeScriptCommands(array('Command_ForceWarmUp' => true));
$this->maniaControl->getChat()->sendInformation($player->getEscapedNickname() . ' paused the Game!');
} catch (GameModeException $e) {
}
try {
//Chase and Combo?
$this->maniaControl->getClient()->sendModeScriptCommands(array('Command_SetPause' => true));
$this->maniaControl->getChat()->sendInformation($player->getEscapedNickname() . ' paused the Game!');
//Especially for chase, force end of the round to reach a draw
$this->maniaControl->getClient()->sendModeScriptCommands(array('Command_ForceEndRound' => true));
} catch (GameModeException $ex) {
}
}
/**
@ -246,8 +254,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
// TODO: move empty & delayed shutdown code into server class
// Empty shutdown
if ($this->serverShutdownEmpty) {
if ($this->maniaControl->getPlayerManager()->getPlayerCount(false) <= 0
) {
if ($this->maniaControl->getPlayerManager()->getPlayerCount(false) <= 0) {
$this->shutdownServer('empty');
}
}
@ -277,8 +284,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandSystemInfo(array $chat, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_SHOW_SYSTEMINFO)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_SHOW_SYSTEMINFO)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -294,8 +300,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandShutdownServer(array $chat, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_SHUTDOWN_SERVER)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_SHUTDOWN_SERVER)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -312,7 +317,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
$this->maniaControl->getChat()->sendInformation("Empty-shutdown cancelled!", $player);
return;
}
$delay = (int)$param;
$delay = (int) $param;
if ($delay <= 0) {
// Cancel shutdown
$this->serverShutdownTime = -1;
@ -334,8 +339,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandSetServerName(array $chat, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -356,8 +360,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandSetPwd(array $chatCallback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -379,8 +382,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandSetSpecPwd(array $chatCallback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -402,8 +404,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandSetMaxPlayers(array $chatCallback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -417,7 +418,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
$this->maniaControl->getChat()->sendUsageInfo('Usage example: //setmaxplayers 16', $player);
return;
}
$amount = (int)$amount;
$amount = (int) $amount;
if ($amount < 0) {
$amount = 0;
}
@ -433,8 +434,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
* @param Player $player
*/
public function commandSetMaxSpectators(array $chatCallback, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVERSETTINGS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -448,7 +448,7 @@ class Commands implements CallbackListener, CommandListener, ManialinkPageAnswer
$this->maniaControl->getChat()->sendUsageInfo('Usage example: //setmaxspectators 16', $player);
return;
}
$amount = (int)$amount;
$amount = (int) $amount;
if ($amount < 0) {
$amount = 0;
}

View File

@ -4,6 +4,7 @@ namespace ManiaControl\Server;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use Maniaplanet\DedicatedServer\Xmlrpc\GameModeException;
/**
* Manager for Game Mode Script related Stuff
@ -39,13 +40,18 @@ class ScriptManager {
if (!$this->isScriptMode()) {
return false;
}
$scriptSettings = $this->maniaControl->getClient()->getModeScriptSettings();
try {
$scriptSettings = $this->maniaControl->getClient()->getModeScriptSettings();
} catch (GameModeException $e) {
return false;
}
if (!array_key_exists('S_UseScriptCallbacks', $scriptSettings)) {
return false;
}
$scriptSettings['S_UseScriptCallbacks'] = (bool)$enable;
$scriptSettings['S_UseScriptCallbacks'] = (bool) $enable;
$actionName = ($enable ? 'en' : 'dis');
$this->maniaControl->getClient()->setModeScriptSettings($scriptSettings);

View File

@ -4,6 +4,7 @@ namespace ManiaControl\Server;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Commands\CommandListener;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
@ -17,7 +18,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\Exception;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class Server implements CallbackListener {
class Server implements CallbackListener, CommandListener {
/*
* Constants
*/
@ -85,6 +86,30 @@ class Server implements CallbackListener {
// Callbacks
$this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::ONINIT, $this, 'onInit');
$this->maniaControl->getCommandManager()->registerCommandListener("uptime", $this, "chatUpTime", true, "Show how long the server is running.");
}
/**
* Displays how long the Server is running already in the Chat
*
* @param array $chatCallback
* @param \ManiaControl\Players\Player $player
*/
public function chatUpTime(array $chatCallback, Player $player) {
$networkStats = $this->maniaControl->getClient()->getNetworkStats();
$minutestotal = $networkStats->uptime / 60;
$hourstotal = $minutestotal / 60;
$days = intval($hourstotal / 24);
$hours = intval($hourstotal - 24 * $days);
$minutes = intval($minutestotal - 24 * 60 * $days - $hours * 60);
$days > 1 ? $dayString = 'days' : $dayString = 'day';
$hours > 1 ? $hourString = 'hours' : $hourString = 'hour';
$minutes > 1 ? $minuteString = 'minutes' : $minuteString = 'minute';
$this->maniaControl->getChat()->sendChat('Server is running since $<$fff' . $days . '$> ' . $dayString . ', $<$fff' . $hours . '$> ' . $hourString . ' and $<$fff' . $minutes . '$> ' . $minuteString, $player);
}
/**
@ -226,6 +251,30 @@ class Server implements CallbackListener {
return $servers;
}
/** Get Server Login by Index
*
* @param int $index
* @return string
*/
public function getServerLoginByIndex($index) {
$mysqli = $this->maniaControl->getDatabase()->getMysqli();
$query = "SELECT * FROM `" . self::TABLE_SERVERS . "` WHERE `index`=" . $index . ";";
$result = $mysqli->query($query);
if (!$result) {
trigger_error($mysqli->error);
return "";
}
if ($result->num_rows != 1) {
return "";
}
$row = $result->fetch_object();
return $row->login;
}
/**
* Handle OnInit Callback
*/

View File

@ -14,6 +14,9 @@ use ManiaControl\Admin\AuthenticationManager;
use ManiaControl\Callbacks\CallbackListener;
use ManiaControl\Callbacks\Callbacks;
use ManiaControl\Callbacks\TimerListener;
use ManiaControl\Communication\CommunicationAnswer;
use ManiaControl\Communication\CommunicationListener;
use ManiaControl\Communication\CommunicationMethods;
use ManiaControl\Configurator\ConfiguratorMenu;
use ManiaControl\Logger;
use ManiaControl\ManiaControl;
@ -28,7 +31,7 @@ use Maniaplanet\DedicatedServer\Xmlrpc\ServerOptionsException;
* @copyright 2014-2015 ManiaControl Team
* @license http://www.gnu.org/licenses/ GNU General Public License, Version 3
*/
class ServerOptionsMenu implements CallbackListener, ConfiguratorMenu, TimerListener {
class ServerOptionsMenu implements CallbackListener, ConfiguratorMenu, TimerListener, CommunicationListener {
/*
* Constants
*/
@ -64,6 +67,10 @@ class ServerOptionsMenu implements CallbackListener, ConfiguratorMenu, TimerList
// Permissions
$this->maniaControl->getAuthenticationManager()->definePermissionLevel(self::SETTING_PERMISSION_CHANGE_SERVER_OPTIONS, AuthenticationManager::AUTH_LEVEL_SUPERADMIN);
//TODO remove to somewhere cleaner
//Communication Listenings
$this->initalizeCommunicationListenings();
}
/**
@ -322,8 +329,7 @@ class ServerOptionsMenu implements CallbackListener, ConfiguratorMenu, TimerList
* @see \ManiaControl\Configurators\ConfiguratorMenu::saveConfigData()
*/
public function saveConfigData(array $configData, Player $player) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVER_OPTIONS)
) {
if (!$this->maniaControl->getAuthenticationManager()->checkPermission($player, self::SETTING_PERMISSION_CHANGE_SERVER_OPTIONS)) {
$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
return;
}
@ -376,4 +382,43 @@ class ServerOptionsMenu implements CallbackListener, ConfiguratorMenu, TimerList
return true;
}
/**
* Initializes the communication Listenings
*/
private function initalizeCommunicationListenings() {
//Communication Listenings
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_SERVER_OPTIONS, $this, function ($data) {
return new CommunicationAnswer($this->maniaControl->getClient()->getServerOptions());
});
$this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::SET_SERVER_OPTIONS, $this, function ($data) {
if (!is_object($data) || !property_exists($data, "serverOptions")) {
return new CommunicationAnswer("No valid ServerOptions provided!", true);
}
$oldServerOptions = $this->maniaControl->getClient()->getServerOptions();
$newServerOptions = new ServerOptions();
foreach ($data->serverOptions as $name => $value) {
$optionName = $name;
$newServerOptions->$optionName = $value;
settype($newServerOptions->$optionName, gettype($oldServerOptions->$optionName));
}
$this->fillUpMandatoryOptions($newServerOptions, $oldServerOptions);
try {
$success = $this->maniaControl->getClient()->setServerOptions($newServerOptions);
} catch (ServerOptionsException $exception) {
return new CommunicationAnswer($exception->getMessage(), true);
}
//Trigger Server Options Changed Callback
$this->maniaControl->getCallbackManager()->triggerCallback(self::CB_SERVER_OPTIONS_CHANGED, array(self::CB_SERVER_OPTIONS_CHANGED));
return new CommunicationAnswer(array("success" => $success));
});
}
}

View File

@ -77,6 +77,16 @@ class SystemUtil {
Logger::log($message . 'FOUND!');
}
// Check for Zlib
$message = 'Checking for installed Zlib ... ';
if (!extension_loaded('zlib')) {
Logger::log($message . 'NOT FOUND!');
Logger::log(" -- You don't have Zlib installed! Check: http://php.net/manual/de/zlib.setup.php");
$success = false;
} else {
Logger::log($message . 'FOUND!');
}
if (!$success) {
// Missing requirements
self::quit();

View File

@ -0,0 +1,17 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement;
class EventEmitter implements EventEmitterInterface
{
use EventEmitterTrait;
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement;
interface EventEmitterInterface
{
public function on($event, callable $listener);
public function once($event, callable $listener);
public function removeListener($event, callable $listener);
public function removeAllListeners($event = null);
public function listeners($event);
public function emit($event, array $arguments = []);
}

View File

@ -0,0 +1,68 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement;
trait EventEmitterTrait
{
protected $listeners = [];
public function on($event, callable $listener)
{
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $listener;
}
public function once($event, callable $listener)
{
$onceListener = function () use (&$onceListener, $event, $listener) {
$this->removeListener($event, $onceListener);
call_user_func_array($listener, func_get_args());
};
$this->on($event, $onceListener);
}
public function removeListener($event, callable $listener)
{
if (isset($this->listeners[$event])) {
$index = array_search($listener, $this->listeners[$event], true);
if (false !== $index) {
unset($this->listeners[$event][$index]);
}
}
}
public function removeAllListeners($event = null)
{
if ($event !== null) {
unset($this->listeners[$event]);
} else {
$this->listeners = [];
}
}
public function listeners($event)
{
return isset($this->listeners[$event]) ? $this->listeners[$event] : [];
}
public function emit($event, array $arguments = [])
{
foreach ($this->listeners($event) as $listener) {
call_user_func_array($listener, $arguments);
}
}
}

View File

@ -4250,9 +4250,9 @@ class Connection
if(is_string($filename))
{
$filename = $this->stripBom($filename);
if(mb_check_encoding($filename, 'ascii'))
return $filename;
return "\xEF\xBB\xBF".$filename;
if(preg_match('/[^\x09\x0A\x0D\x20-\x7E]/', $filename))
return "\xEF\xBB\xBF".$filename;
return $filename;
}
return array_map(array($this, 'secureUtf8'), $filename);
}

View File

@ -7,12 +7,9 @@
namespace Maniaplanet\DedicatedServer\Xmlrpc;
class FaultException extends Exception
{
static function create($faultString, $faultCode)
{
switch($faultString)
{
class FaultException extends Exception {
static function create($faultString, $faultCode) {
switch ($faultString) {
case 'Password incorrect.':
case 'Permission denied.':
return new AuthenticationException($faultString, $faultCode);
@ -34,6 +31,7 @@ class FaultException extends Exception
return new LockedFeatureException($faultString, $faultCode);
case 'Login or Uid unknown.':
case 'Login unknown.':
case 'Payer login unknown.':
return new UnknownPlayerException($faultString, $faultCode);
case 'The player is not a spectator':
case 'The player is not a spectator.':
@ -72,6 +70,7 @@ class FaultException extends Exception
case 'You cannot change the max spectators count: AllowSpectatorRelays is activated.':
case 'There are too many players':
case 'There are too many spectators':
case 'Unknown hideserver value':
return new ServerOptionsException($faultString, $faultCode);
case 'New mode unknown.':
case 'You need to stop the server to change to/from script mode.':
@ -79,6 +78,7 @@ class FaultException extends Exception
case 'Not in Team mode.':
case 'Not in Rounds or Laps mode.':
case 'The scores must be decreasing.':
case 'No current script.':
return new GameModeException($faultString, $faultCode);
case 'Unable to write the black list file.':
case 'Unable to write the guest list file.':
@ -90,26 +90,55 @@ class FaultException extends Exception
case 'Invalid url.':
return new FileException($faultString, $faultCode);
}
if(preg_match('~^Unknown setting \'.*\'\.$~iu', $faultString))
if (preg_match('~^Unknown setting \'.*\'\.$~iu', $faultString)) {
return new GameModeException($faultString, $faultCode);
if(preg_match('~^Couldn\'t load \'.*\'\.$~iu', $faultString))
}
if (preg_match('~^Couldn\'t load \'.*\'\.$~iu', $faultString)) {
return new FileException($faultString, $faultCode);
}
return new self($faultString, $faultCode);
}
}
class AuthenticationException extends FaultException {}
class UnavailableFeatureException extends FaultException {}
class LockedFeatureException extends FaultException {}
class UnknownPlayerException extends FaultException {}
class PlayerStateException extends FaultException {}
class AlreadyInListException extends FaultException {}
class NotInListException extends FaultException {}
class IndexOutOfBoundException extends FaultException {}
class NextMapException extends FaultException{}
class ChangeInProgressException extends FaultException {}
class InvalidMapException extends FaultException{}
class GameModeException extends FaultException {}
class ServerOptionsException extends FaultException {}
class FileException extends FaultException {}
class AuthenticationException extends FaultException {
}
class UnavailableFeatureException extends FaultException {
}
class LockedFeatureException extends FaultException {
}
class UnknownPlayerException extends FaultException {
}
class PlayerStateException extends FaultException {
}
class AlreadyInListException extends FaultException {
}
class NotInListException extends FaultException {
}
class IndexOutOfBoundException extends FaultException {
}
class NextMapException extends FaultException {
}
class ChangeInProgressException extends FaultException {
}
class InvalidMapException extends FaultException {
}
class GameModeException extends FaultException {
}
class ServerOptionsException extends FaultException {
}
class FileException extends FaultException {
}

View File

@ -16,8 +16,8 @@ class GbxRemote
public static $sent;
private $socket;
private $readTimeout = array('sec' => 5, 'usec' => 0);
private $writeTimeout = array('sec' => 5, 'usec' => 0);
private $readTimeout = array('sec' => 30, 'usec' => 0);
private $writeTimeout = array('sec' => 30, 'usec' => 0);
private $requestHandle;
private $callbacksBuffer = array();
private $multicallBuffer = array();

View File

@ -0,0 +1,327 @@
<?php
namespace React\EventLoop;
use Event;
use EventBase;
use EventConfig as EventBaseConfig;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;
/**
* An ext-event based React.
*/
class ExtEventLoop implements LoopInterface
{
private $eventBase;
private $nextTickQueue;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $streamEvents = [];
private $streamFlags = [];
private $readListeners = [];
private $writeListeners = [];
private $running;
public function __construct(EventBaseConfig $config = null)
{
$this->eventBase = new EventBase($config);
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timerEvents = new SplObjectStorage();
$this->createTimerCallback();
$this->createStreamCallback();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->readListeners[$key])) {
$this->readListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, Event::READ);
}
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->writeListeners[$key])) {
$this->writeListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, Event::WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
unset($this->readListeners[$key]);
$this->unsubscribeStreamEvent($stream, Event::READ);
}
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
unset($this->writeListeners[$key]);
$this->unsubscribeStreamEvent($stream, Event::WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$this->streamEvents[$key]->free();
unset(
$this->streamFlags[$key],
$this->streamEvents[$key],
$this->readListeners[$key],
$this->writeListeners[$key]
);
}
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if ($this->isTimerActive($timer)) {
$this->timerEvents[$timer]->free();
$this->timerEvents->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
// @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
@$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$flags = EventBase::LOOP_ONCE;
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$flags |= EventBase::LOOP_NONBLOCK;
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
break;
}
// @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
@$this->eventBase->loop($flags);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$flags = Event::TIMEOUT;
if ($timer->isPeriodic()) {
$flags |= Event::PERSIST;
}
$event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
$this->timerEvents[$timer] = $event;
$event->add($timer->getInterval());
}
/**
* Create a new ext-event Event object, or update the existing one.
*
* @param resource $stream
* @param integer $flag Event::READ or Event::WRITE
*/
private function subscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
$flags = ($this->streamFlags[$key] |= $flag);
$event->del();
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
} else {
$event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);
$this->streamEvents[$key] = $event;
$this->streamFlags[$key] = $flag;
}
$event->add();
}
/**
* Update the ext-event Event object for this stream to stop listening to
* the given event type, or remove it entirely if it's no longer needed.
*
* @param resource $stream
* @param integer $flag Event::READ or Event::WRITE
*/
private function unsubscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
$flags = $this->streamFlags[$key] &= ~$flag;
if (0 === $flags) {
$this->removeStream($stream);
return;
}
$event = $this->streamEvents[$key];
$event->del();
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
$event->add();
}
/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$this->timerCallback = function ($_, $_, $timer) {
call_user_func($timer->getCallback(), $timer);
if (!$timer->isPeriodic() && $this->isTimerActive($timer)) {
$this->cancelTimer($timer);
}
};
}
/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$this->streamCallback = function ($stream, $flags) {
$key = (int) $stream;
if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
call_user_func($this->readListeners[$key], $stream, $this);
}
if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
};
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace React\EventLoop;
class Factory
{
public static function create()
{
// @codeCoverageIgnoreStart
if (function_exists('event_base_new')) {
return new LibEventLoop();
} elseif (class_exists('libev\EventLoop', false)) {
return new LibEvLoop;
} elseif (class_exists('EventBase', false)) {
return new ExtEventLoop;
}
return new StreamSelectLoop();
// @codeCoverageIgnoreEnd
}
}

View File

@ -0,0 +1,218 @@
<?php
namespace React\EventLoop;
use libev\EventLoop;
use libev\IOEvent;
use libev\TimerEvent;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;
/**
* @see https://github.com/m4rw3r/php-libev
* @see https://gist.github.com/1688204
*/
class LibEvLoop implements LoopInterface
{
private $loop;
private $nextTickQueue;
private $futureTickQueue;
private $timerEvents;
private $readEvents = [];
private $writeEvents = [];
private $running;
public function __construct()
{
$this->loop = new EventLoop();
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timerEvents = new SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$callback = function () use ($stream, $listener) {
call_user_func($listener, $stream, $this);
};
$event = new IOEvent($callback, $stream, IOEvent::READ);
$this->loop->add($event);
$this->readEvents[(int) $stream] = $event;
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$callback = function () use ($stream, $listener) {
call_user_func($listener, $stream, $this);
};
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
$this->loop->add($event);
$this->writeEvents[(int) $stream] = $event;
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readEvents[$key])) {
$this->readEvents[$key]->stop();
unset($this->readEvents[$key]);
}
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeEvents[$key])) {
$this->writeEvents[$key]->stop();
unset($this->writeEvents[$key]);
}
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$this->removeReadStream($stream);
$this->removeWriteStream($stream);
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$callback = function () use ($timer) {
call_user_func($timer->getCallback(), $timer);
if ($this->isTimerActive($timer)) {
$this->cancelTimer($timer);
}
};
$event = new TimerEvent($callback, $timer->getInterval());
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$callback = function () use ($timer) {
call_user_func($timer->getCallback(), $timer);
};
$event = new TimerEvent($callback, $interval, $interval);
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if (isset($this->timerEvents[$timer])) {
$this->loop->remove($this->timerEvents[$timer]);
$this->timerEvents->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$flags = EventLoop::RUN_ONCE;
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$flags |= EventLoop::RUN_NOWAIT;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
break;
}
$this->loop->run($flags);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
}

View File

@ -0,0 +1,343 @@
<?php
namespace React\EventLoop;
use Event;
use EventBase;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;
/**
* An ext-libevent based React.
*/
class LibEventLoop implements LoopInterface
{
const MICROSECONDS_PER_SECOND = 1000000;
private $eventBase;
private $nextTickQueue;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $streamEvents = [];
private $streamFlags = [];
private $readListeners = [];
private $writeListeners = [];
private $running;
public function __construct()
{
$this->eventBase = event_base_new();
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timerEvents = new SplObjectStorage();
$this->createTimerCallback();
$this->createStreamCallback();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->readListeners[$key])) {
$this->readListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, EV_READ);
}
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->writeListeners[$key])) {
$this->writeListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, EV_WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
unset($this->readListeners[$key]);
$this->unsubscribeStreamEvent($stream, EV_READ);
}
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
unset($this->writeListeners[$key]);
$this->unsubscribeStreamEvent($stream, EV_WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
event_del($event);
event_free($event);
unset(
$this->streamFlags[$key],
$this->streamEvents[$key],
$this->readListeners[$key],
$this->writeListeners[$key]
);
}
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if ($this->isTimerActive($timer)) {
$event = $this->timerEvents[$timer];
event_del($event);
event_free($event);
$this->timerEvents->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$flags = EVLOOP_ONCE;
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$flags |= EVLOOP_NONBLOCK;
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
break;
}
event_base_loop($this->eventBase, $flags);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$this->timerEvents[$timer] = $event = event_timer_new();
event_timer_set($event, $this->timerCallback, $timer);
event_base_set($event, $this->eventBase);
event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
}
/**
* Create a new ext-libevent event resource, or update the existing one.
*
* @param resource $stream
* @param integer $flag EV_READ or EV_WRITE
*/
private function subscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
$flags = $this->streamFlags[$key] |= $flag;
event_del($event);
event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
} else {
$event = event_new();
event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback);
event_base_set($event, $this->eventBase);
$this->streamEvents[$key] = $event;
$this->streamFlags[$key] = $flag;
}
event_add($event);
}
/**
* Update the ext-libevent event resource for this stream to stop listening to
* the given event type, or remove it entirely if it's no longer needed.
*
* @param resource $stream
* @param integer $flag EV_READ or EV_WRITE
*/
private function unsubscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
$flags = $this->streamFlags[$key] &= ~$flag;
if (0 === $flags) {
$this->removeStream($stream);
return;
}
$event = $this->streamEvents[$key];
event_del($event);
event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
event_add($event);
}
/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$this->timerCallback = function ($_, $_, $timer) {
call_user_func($timer->getCallback(), $timer);
// Timer already cancelled ...
if (!$this->isTimerActive($timer)) {
return;
// Reschedule periodic timers ...
} elseif ($timer->isPeriodic()) {
event_add(
$this->timerEvents[$timer],
$timer->getInterval() * self::MICROSECONDS_PER_SECOND
);
// Clean-up one shot timers ...
} else {
$this->cancelTimer($timer);
}
};
}
/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$this->streamCallback = function ($stream, $flags) {
$key = (int) $stream;
if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) {
call_user_func($this->readListeners[$key], $stream, $this);
}
if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
};
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Timer\TimerInterface;
interface LoopInterface
{
/**
* Register a listener to be notified when a stream is ready to read.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
*/
public function addReadStream($stream, callable $listener);
/**
* Register a listener to be notified when a stream is ready to write.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
*/
public function addWriteStream($stream, callable $listener);
/**
* Remove the read event listener for the given stream.
*
* @param resource $stream The PHP stream resource.
*/
public function removeReadStream($stream);
/**
* Remove the write event listener for the given stream.
*
* @param resource $stream The PHP stream resource.
*/
public function removeWriteStream($stream);
/**
* Remove all listeners for the given stream.
*
* @param resource $stream The PHP stream resource.
*/
public function removeStream($stream);
/**
* Enqueue a callback to be invoked once after the given interval.
*
* The execution order of timers scheduled to execute at the same time is
* not guaranteed.
*
* @param int|float $interval The number of seconds to wait before execution.
* @param callable $callback The callback to invoke.
*
* @return TimerInterface
*/
public function addTimer($interval, callable $callback);
/**
* Enqueue a callback to be invoked repeatedly after the given interval.
*
* The execution order of timers scheduled to execute at the same time is
* not guaranteed.
*
* @param int|float $interval The number of seconds to wait before execution.
* @param callable $callback The callback to invoke.
*
* @return TimerInterface
*/
public function addPeriodicTimer($interval, callable $callback);
/**
* Cancel a pending timer.
*
* @param TimerInterface $timer The timer to cancel.
*/
public function cancelTimer(TimerInterface $timer);
/**
* Check if a given timer is active.
*
* @param TimerInterface $timer The timer to check.
*
* @return boolean True if the timer is still enqueued for execution.
*/
public function isTimerActive(TimerInterface $timer);
/**
* Schedule a callback to be invoked on the next tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued,
* before any timer or stream events.
*
* @param callable $listener The callback to invoke.
*/
public function nextTick(callable $listener);
/**
* Schedule a callback to be invoked on a future tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued.
*
* @param callable $listener The callback to invoke.
*/
public function futureTick(callable $listener);
/**
* Perform a single iteration of the event loop.
*/
public function tick();
/**
* Run the event loop until there are no more tasks to perform.
*/
public function run();
/**
* Instruct a running event loop to stop.
*/
public function stop();
}

View File

@ -0,0 +1,262 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use React\EventLoop\Timer\Timers;
/**
* A stream_select() based React.
*/
class StreamSelectLoop implements LoopInterface
{
const MICROSECONDS_PER_SECOND = 1000000;
private $nextTickQueue;
private $futureTickQueue;
private $timers;
private $readStreams = [];
private $readListeners = [];
private $writeStreams = [];
private $writeListeners = [];
private $running;
public function __construct()
{
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timers = new Timers();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->readStreams[$key])) {
$this->readStreams[$key] = $stream;
$this->readListeners[$key] = $listener;
}
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->writeStreams[$key])) {
$this->writeStreams[$key] = $stream;
$this->writeListeners[$key] = $listener;
}
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
unset(
$this->readStreams[$key],
$this->readListeners[$key]
);
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
unset(
$this->writeStreams[$key],
$this->writeListeners[$key]
);
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$this->removeReadStream($stream);
$this->removeWriteStream($stream);
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$this->timers->add($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$this->timers->add($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
$this->timers->cancel($timer);
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timers->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$this->timers->tick();
$this->waitForStreamActivity(0);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$this->timers->tick();
// Next-tick or future-tick queues have pending callbacks ...
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$timeout = 0;
// There is a pending timer, only block until it is due ...
} elseif ($scheduledAt = $this->timers->getFirst()) {
$timeout = $scheduledAt - $this->timers->getTime();
if ($timeout < 0) {
$timeout = 0;
} else {
$timeout *= self::MICROSECONDS_PER_SECOND;
}
// The only possible event is stream activity, so wait forever ...
} elseif ($this->readStreams || $this->writeStreams) {
$timeout = null;
// There's nothing left to do ...
} else {
break;
}
$this->waitForStreamActivity($timeout);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
/**
* Wait/check for stream activity, or until the next timer is due.
*/
private function waitForStreamActivity($timeout)
{
$read = $this->readStreams;
$write = $this->writeStreams;
$this->streamSelect($read, $write, $timeout);
foreach ($read as $stream) {
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
call_user_func($this->readListeners[$key], $stream, $this);
}
}
foreach ($write as $stream) {
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
}
}
/**
* Emulate a stream_select() implementation that does not break when passed
* empty stream arrays.
*
* @param array &$read An array of read streams to select upon.
* @param array &$write An array of write streams to select upon.
* @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
*
* @return integer The total number of streams that are ready for read/write.
*/
protected function streamSelect(array &$read, array &$write, $timeout)
{
if ($read || $write) {
$except = null;
return stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
}
usleep($timeout);
return 0;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace React\EventLoop\Tick;
use React\EventLoop\LoopInterface;
use SplQueue;
class FutureTickQueue
{
private $eventLoop;
private $queue;
/**
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
*/
public function __construct(LoopInterface $eventLoop)
{
$this->eventLoop = $eventLoop;
$this->queue = new SplQueue();
}
/**
* Add a callback to be invoked on a future tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued.
*
* @param callable $listener The callback to invoke.
*/
public function add(callable $listener)
{
$this->queue->enqueue($listener);
}
/**
* Flush the callback queue.
*/
public function tick()
{
// Only invoke as many callbacks as were on the queue when tick() was called.
$count = $this->queue->count();
while ($count--) {
call_user_func(
$this->queue->dequeue(),
$this->eventLoop
);
}
}
/**
* Check if the next tick queue is empty.
*
* @return boolean
*/
public function isEmpty()
{
return $this->queue->isEmpty();
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace React\EventLoop\Tick;
use React\EventLoop\LoopInterface;
use SplQueue;
class NextTickQueue
{
private $eventLoop;
private $queue;
/**
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
*/
public function __construct(LoopInterface $eventLoop)
{
$this->eventLoop = $eventLoop;
$this->queue = new SplQueue();
}
/**
* Add a callback to be invoked on the next tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued,
* before any timer or stream events.
*
* @param callable $listener The callback to invoke.
*/
public function add(callable $listener)
{
$this->queue->enqueue($listener);
}
/**
* Flush the callback queue.
*/
public function tick()
{
while (!$this->queue->isEmpty()) {
call_user_func(
$this->queue->dequeue(),
$this->eventLoop
);
}
}
/**
* Check if the next tick queue is empty.
*
* @return boolean
*/
public function isEmpty()
{
return $this->queue->isEmpty();
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\LoopInterface;
class Timer implements TimerInterface
{
const MIN_INTERVAL = 0.000001;
protected $loop;
protected $interval;
protected $callback;
protected $periodic;
protected $data;
/**
* Constructor initializes the fields of the Timer
*
* @param LoopInterface $loop The loop with which this timer is associated
* @param float $interval The interval after which this timer will execute, in seconds
* @param callable $callback The callback that will be executed when this timer elapses
* @param bool $periodic Whether the time is periodic
* @param mixed $data Arbitrary data associated with timer
*/
public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false, $data = null)
{
if ($interval < self::MIN_INTERVAL) {
$interval = self::MIN_INTERVAL;
}
$this->loop = $loop;
$this->interval = (float) $interval;
$this->callback = $callback;
$this->periodic = (bool) $periodic;
$this->data = null;
}
/**
* {@inheritdoc}
*/
public function getLoop()
{
return $this->loop;
}
/**
* {@inheritdoc}
*/
public function getInterval()
{
return $this->interval;
}
/**
* {@inheritdoc}
*/
public function getCallback()
{
return $this->callback;
}
/**
* {@inheritdoc}
*/
public function setData($data)
{
$this->data = $data;
}
/**
* {@inheritdoc}
*/
public function getData()
{
return $this->data;
}
/**
* {@inheritdoc}
*/
public function isPeriodic()
{
return $this->periodic;
}
/**
* {@inheritdoc}
*/
public function isActive()
{
return $this->loop->isTimerActive($this);
}
/**
* {@inheritdoc}
*/
public function cancel()
{
$this->loop->cancelTimer($this);
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\LoopInterface;
interface TimerInterface
{
/**
* Get the loop with which this timer is associated
*
* @return LoopInterface
*/
public function getLoop();
/**
* Get the interval after which this timer will execute, in seconds
*
* @return float
*/
public function getInterval();
/**
* Get the callback that will be executed when this timer elapses
*
* @return callable
*/
public function getCallback();
/**
* Set arbitrary data associated with timer
*
* @param mixed $data
*/
public function setData($data);
/**
* Get arbitrary data associated with timer
*
* @return mixed
*/
public function getData();
/**
* Determine whether the time is periodic
*
* @return bool
*/
public function isPeriodic();
/**
* Determine whether the time is active
*
* @return bool
*/
public function isActive();
/**
* Cancel this timer
*/
public function cancel();
}

View File

@ -0,0 +1,100 @@
<?php
namespace React\EventLoop\Timer;
use SplObjectStorage;
use SplPriorityQueue;
class Timers
{
private $time;
private $timers;
private $scheduler;
public function __construct()
{
$this->timers = new SplObjectStorage();
$this->scheduler = new SplPriorityQueue();
}
public function updateTime()
{
return $this->time = microtime(true);
}
public function getTime()
{
return $this->time ?: $this->updateTime();
}
public function add(TimerInterface $timer)
{
$interval = $timer->getInterval();
$scheduledAt = $interval + $this->getTime();
$this->timers->attach($timer, $scheduledAt);
$this->scheduler->insert($timer, -$scheduledAt);
}
public function contains(TimerInterface $timer)
{
return $this->timers->contains($timer);
}
public function cancel(TimerInterface $timer)
{
$this->timers->detach($timer);
}
public function getFirst()
{
while ($this->scheduler->count()) {
$timer = $this->scheduler->top();
if ($this->timers->contains($timer)) {
return $this->timers[$timer];
}
$this->scheduler->extract();
}
return null;
}
public function isEmpty()
{
return count($this->timers) === 0;
}
public function tick()
{
$time = $this->updateTime();
$timers = $this->timers;
$scheduler = $this->scheduler;
while (!$scheduler->isEmpty()) {
$timer = $scheduler->top();
if (!isset($timers[$timer])) {
$scheduler->extract();
$timers->detach($timer);
continue;
}
if ($timers[$timer] >= $time) {
break;
}
$scheduler->extract();
call_user_func($timer->getCallback(), $timer);
if ($timer->isPeriodic() && isset($timers[$timer])) {
$timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
$scheduler->insert($timer, -$scheduledAt);
} else {
$timers->detach($timer);
}
}
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace React\Socket;
use React\Stream\Stream;
class Connection extends Stream implements ConnectionInterface
{
public function handleData($stream)
{
// Socket is raw, not using fread as it's interceptable by filters
// See issues #192, #209, and #240
$data = stream_socket_recvfrom($stream, $this->bufferSize);
if ('' !== $data && false !== $data) {
$this->emit('data', array($data, $this));
}
if ('' === $data || false === $data || !is_resource($stream) || feof($stream)) {
$this->end();
}
}
public function handleClose()
{
if (is_resource($this->stream)) {
// http://chat.stackoverflow.com/transcript/message/7727858#7727858
stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR);
stream_set_blocking($this->stream, false);
fclose($this->stream);
}
}
public function getRemoteAddress()
{
return $this->parseAddress(stream_socket_get_name($this->stream, true));
}
private function parseAddress($address)
{
return trim(substr($address, 0, strrpos($address, ':')), '[]');
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace React\Socket;
class ConnectionException extends \ErrorException
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace React\Socket;
use Evenement\EventEmitterInterface;
use React\Stream\ReadableStreamInterface;
use React\Stream\WritableStreamInterface;
interface ConnectionInterface extends ReadableStreamInterface, WritableStreamInterface
{
public function getRemoteAddress();
}

View File

@ -0,0 +1,71 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\LoopInterface;
/** @event connection */
class Server extends EventEmitter implements ServerInterface
{
public $master;
private $loop;
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}
public function listen($port, $host = '127.0.0.1')
{
if (strpos($host, ':') !== false) {
// enclose IPv6 addresses in square brackets before appending port
$host = '[' . $host . ']';
}
$this->master = @stream_socket_server("tcp://$host:$port", $errno, $errstr);
if (false === $this->master) {
$message = "Could not bind to tcp://$host:$port: $errstr";
throw new ConnectionException($message, $errno);
}
stream_set_blocking($this->master, 0);
$this->loop->addReadStream($this->master, function ($master) {
$newSocket = stream_socket_accept($master);
if (false === $newSocket) {
$this->emit('error', array(new \RuntimeException('Error accepting new connection')));
return;
}
$this->handleConnection($newSocket);
});
}
public function handleConnection($socket)
{
stream_set_blocking($socket, 0);
$client = $this->createConnection($socket);
$this->emit('connection', array($client));
}
public function getPort()
{
$name = stream_socket_get_name($this->master, false);
return (int) substr(strrchr($name, ':'), 1);
}
public function shutdown()
{
$this->loop->removeStream($this->master);
fclose($this->master);
$this->removeAllListeners();
}
public function createConnection($socket)
{
return new Connection($socket, $this->loop);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace React\Socket;
use Evenement\EventEmitterInterface;
/** @event connection */
interface ServerInterface extends EventEmitterInterface
{
public function listen($port, $host = '127.0.0.1');
public function getPort();
public function shutdown();
}

View File

@ -0,0 +1,135 @@
<?php
namespace React\Stream;
use Evenement\EventEmitter;
use React\EventLoop\LoopInterface;
/** @event full-drain */
class Buffer extends EventEmitter implements WritableStreamInterface
{
public $stream;
public $listening = false;
public $softLimit = 2048;
private $writable = true;
private $loop;
private $data = '';
private $lastError = array(
'number' => 0,
'message' => '',
'file' => '',
'line' => 0,
);
public function __construct($stream, LoopInterface $loop)
{
$this->stream = $stream;
$this->loop = $loop;
}
public function isWritable()
{
return $this->writable;
}
public function write($data)
{
if (!$this->writable) {
return;
}
$this->data .= $data;
if (!$this->listening) {
$this->listening = true;
$this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
}
$belowSoftLimit = strlen($this->data) < $this->softLimit;
return $belowSoftLimit;
}
public function end($data = null)
{
if (null !== $data) {
$this->write($data);
}
$this->writable = false;
if ($this->listening) {
$this->on('full-drain', array($this, 'close'));
} else {
$this->close();
}
}
public function close()
{
$this->writable = false;
$this->listening = false;
$this->data = '';
$this->emit('close', [$this]);
}
public function handleWrite()
{
if (!is_resource($this->stream)) {
$this->emit('error', array(new \RuntimeException('Tried to write to invalid stream.'), $this));
return;
}
set_error_handler(array($this, 'errorHandler'));
$sent = fwrite($this->stream, $this->data);
restore_error_handler();
if (false === $sent) {
$this->emit('error', array(
new \ErrorException(
$this->lastError['message'],
0,
$this->lastError['number'],
$this->lastError['file'],
$this->lastError['line']
),
$this
));
return;
}
if (0 === $sent && feof($this->stream)) {
$this->emit('error', array(new \RuntimeException('Tried to write to closed stream.'), $this));
return;
}
$len = strlen($this->data);
if ($len >= $this->softLimit && $len - $sent < $this->softLimit) {
$this->emit('drain', [$this]);
}
$this->data = (string) substr($this->data, $sent);
if (0 === strlen($this->data)) {
$this->loop->removeWriteStream($this->stream);
$this->listening = false;
$this->emit('full-drain', [$this]);
}
}
private function errorHandler($errno, $errstr, $errfile, $errline)
{
$this->lastError['number'] = $errno;
$this->lastError['message'] = $errstr;
$this->lastError['file'] = $errfile;
$this->lastError['line'] = $errline;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace React\Stream;
use React\Promise\Deferred;
use React\Promise\PromisorInterface;
class BufferedSink extends WritableStream implements PromisorInterface
{
private $buffer = '';
private $deferred;
public function __construct()
{
$this->deferred = new Deferred();
$this->on('pipe', array($this, 'handlePipeEvent'));
$this->on('error', array($this, 'handleErrorEvent'));
}
public function handlePipeEvent($source)
{
Util::forwardEvents($source, $this, array('error'));
}
public function handleErrorEvent($e)
{
$this->deferred->reject($e);
}
public function write($data)
{
$this->buffer .= $data;
$this->deferred->progress($data);
}
public function close()
{
if ($this->closed) {
return;
}
parent::close();
$this->deferred->resolve($this->buffer);
}
public function promise()
{
return $this->deferred->promise();
}
public static function createPromise(ReadableStreamInterface $stream)
{
$sink = new static();
$stream->pipe($sink);
return $sink->promise();
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace React\Stream;
use Evenement\EventEmitter;
class CompositeStream extends EventEmitter implements DuplexStreamInterface
{
protected $readable;
protected $writable;
protected $pipeSource;
public function __construct(ReadableStreamInterface $readable, WritableStreamInterface $writable)
{
$this->readable = $readable;
$this->writable = $writable;
Util::forwardEvents($this->readable, $this, array('data', 'end', 'error', 'close'));
Util::forwardEvents($this->writable, $this, array('drain', 'error', 'close', 'pipe'));
$this->readable->on('close', array($this, 'close'));
$this->writable->on('close', array($this, 'close'));
$this->on('pipe', array($this, 'handlePipeEvent'));
}
public function handlePipeEvent($source)
{
$this->pipeSource = $source;
}
public function isReadable()
{
return $this->readable->isReadable();
}
public function pause()
{
if ($this->pipeSource) {
$this->pipeSource->pause();
}
$this->readable->pause();
}
public function resume()
{
if ($this->pipeSource) {
$this->pipeSource->resume();
}
$this->readable->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function isWritable()
{
return $this->writable->isWritable();
}
public function write($data)
{
return $this->writable->write($data);
}
public function end($data = null)
{
$this->writable->end($data);
}
public function close()
{
$this->pipeSource = null;
$this->readable->close();
$this->writable->close();
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace React\Stream;
interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
{
}

View File

@ -0,0 +1,42 @@
<?php
namespace React\Stream;
use Evenement\EventEmitter;
class ReadableStream extends EventEmitter implements ReadableStreamInterface
{
protected $closed = false;
public function isReadable()
{
return !$this->closed;
}
public function pause()
{
}
public function resume()
{
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->emit('end', array($this));
$this->emit('close', array($this));
$this->removeAllListeners();
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace React\Stream;
use Evenement\EventEmitterInterface;
/**
* @event data
* @event end
* @event error
* @event close
*/
interface ReadableStreamInterface extends EventEmitterInterface
{
public function isReadable();
public function pause();
public function resume();
public function pipe(WritableStreamInterface $dest, array $options = array());
public function close();
}

View File

@ -0,0 +1,141 @@
<?php
namespace React\Stream;
use Evenement\EventEmitter;
use React\EventLoop\LoopInterface;
use InvalidArgumentException;
class Stream extends EventEmitter implements DuplexStreamInterface
{
public $bufferSize = 4096;
public $stream;
protected $readable = true;
protected $writable = true;
protected $closing = false;
protected $loop;
protected $buffer;
public function __construct($stream, LoopInterface $loop)
{
$this->stream = $stream;
if (!is_resource($this->stream) || get_resource_type($this->stream) !== "stream") {
throw new InvalidArgumentException('First parameter must be a valid stream resource');
}
stream_set_blocking($this->stream, 0);
$this->loop = $loop;
$this->buffer = new Buffer($this->stream, $this->loop);
$this->buffer->on('error', function ($error) {
$this->emit('error', array($error, $this));
$this->close();
});
$this->buffer->on('drain', function () {
$this->emit('drain', array($this));
});
$this->resume();
}
public function isReadable()
{
return $this->readable;
}
public function isWritable()
{
return $this->writable;
}
public function pause()
{
$this->loop->removeReadStream($this->stream);
}
public function resume()
{
if ($this->readable) {
$this->loop->addReadStream($this->stream, array($this, 'handleData'));
}
}
public function write($data)
{
if (!$this->writable) {
return;
}
return $this->buffer->write($data);
}
public function close()
{
if (!$this->writable && !$this->closing) {
return;
}
$this->closing = false;
$this->readable = false;
$this->writable = false;
$this->emit('end', array($this));
$this->emit('close', array($this));
$this->loop->removeStream($this->stream);
$this->buffer->removeAllListeners();
$this->removeAllListeners();
$this->handleClose();
}
public function end($data = null)
{
if (!$this->writable) {
return;
}
$this->closing = true;
$this->readable = false;
$this->writable = false;
$this->buffer->on('close', function () {
$this->close();
});
$this->buffer->end($data);
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function handleData($stream)
{
$data = fread($stream, $this->bufferSize);
$this->emit('data', array($data, $this));
if (!is_resource($stream) || feof($stream)) {
$this->end();
}
}
public function handleClose()
{
if (is_resource($this->stream)) {
fclose($this->stream);
}
}
public function getBuffer()
{
return $this->buffer;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace React\Stream;
class ThroughStream extends CompositeStream
{
public function __construct()
{
$readable = new ReadableStream();
$writable = new WritableStream();
parent::__construct($readable, $writable);
}
public function filter($data)
{
return $data;
}
public function write($data)
{
$this->readable->emit('data', array($this->filter($data), $this));
}
public function end($data = null)
{
if (null !== $data) {
$this->readable->emit('data', array($this->filter($data), $this));
}
$this->writable->end($data);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace React\Stream;
// TODO: move to a trait
class Util
{
public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = array())
{
// TODO: use stream_copy_to_stream
// it is 4x faster than this
// but can lose data under load with no way to recover it
$dest->emit('pipe', array($source));
$source->on('data', function ($data) use ($source, $dest) {
$feedMore = $dest->write($data);
if (false === $feedMore) {
$source->pause();
}
});
$dest->on('drain', function () use ($source) {
$source->resume();
});
$end = isset($options['end']) ? $options['end'] : true;
if ($end && $source !== $dest) {
$source->on('end', function () use ($dest) {
$dest->end();
});
}
}
public static function forwardEvents($source, $target, array $events)
{
foreach ($events as $event) {
$source->on($event, function () use ($event, $target) {
$target->emit($event, func_get_args());
});
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace React\Stream;
use Evenement\EventEmitter;
class WritableStream extends EventEmitter implements WritableStreamInterface
{
protected $closed = false;
public function write($data)
{
}
public function end($data = null)
{
if (null !== $data) {
$this->write($data);
}
$this->close();
}
public function isWritable()
{
return !$this->closed;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->emit('end', array($this));
$this->emit('close', array($this));
$this->removeAllListeners();
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace React\Stream;
use Evenement\EventEmitterInterface;
/**
* @event drain
* @event error
* @event close
* @event pipe
*/
interface WritableStreamInterface extends EventEmitterInterface
{
public function isWritable();
public function write($data);
public function end($data = null);
public function close();
}

View File

@ -121,7 +121,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher
public function getListeners($eventName = null)
{
if (null === $eventName) {
foreach (array_keys($this->listenerIds) as $serviceEventName) {
foreach ($this->listenerIds as $serviceEventName => $args) {
$this->lazyLoad($serviceEventName);
}
} else {

View File

@ -31,6 +31,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
private $called;
private $dispatcher;
private $wrappedListeners;
/**
* Constructor.
@ -45,6 +46,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
$this->wrappedListeners = array();
}
/**
@ -68,6 +70,16 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
*/
public function removeListener($eventName, $listener)
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
}
}
}
return $this->dispatcher->removeListener($eventName, $listener);
}
@ -146,7 +158,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$allListeners = $this->getListeners();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->info(sprintf('An exception was thrown while getting the uncalled listeners (%s)', $e->getMessage()), array('exception' => $e));
$this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
}
// unable to retrieve the uncalled listeners
@ -216,12 +228,15 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->dispatcher->removeListener($eventName, $listener);
$info = $this->getListenerInfo($listener, $eventName);
$name = isset($info['class']) ? $info['class'] : $info['type'];
$this->dispatcher->addListener($eventName, new WrappedListener($listener, $name, $this->stopwatch));
$wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->addListener($eventName, $wrappedListener);
}
}
private function postProcess($eventName)
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
@ -259,7 +274,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
}
/**
* Returns information about the listener
* Returns information about the listener.
*
* @param object $listener The listener
* @param string $eventName The event name

View File

@ -25,12 +25,14 @@ class WrappedListener
private $called;
private $stoppedPropagation;
private $stopwatch;
private $dispatcher;
public function __construct($listener, $name, Stopwatch $stopwatch)
public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{
$this->listener = $listener;
$this->name = $name;
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->called = false;
$this->stoppedPropagation = false;
}
@ -56,7 +58,7 @@ class WrappedListener
$e = $this->stopwatch->start($this->name, 'event_listener');
call_user_func($this->listener, $event, $eventName, $dispatcher);
call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
if ($e->isStarted()) {
$e->stop();

View File

@ -91,8 +91,12 @@ class RegisterListenersPass implements CompilerPassInterface
throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
}
if ($def->isAbstract()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
}
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $def->getClass();
$class = $container->getParameterBag()->resolveValue($def->getClass());
$refClass = new \ReflectionClass($class);
$interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';

View File

@ -77,7 +77,7 @@ class Event
*
* @param EventDispatcherInterface $dispatcher
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
* @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
*
* @api
*/
@ -91,12 +91,14 @@ class Event
*
* @return EventDispatcherInterface
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
* @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
*
* @api
*/
public function getDispatcher()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0. The event dispatcher instance can be received in the listener call instead.', E_USER_DEPRECATED);
return $this->dispatcher;
}
@ -105,12 +107,14 @@ class Event
*
* @return string
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call.
* @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
*
* @api
*/
public function getName()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0. The event name can be received in the listener call instead.', E_USER_DEPRECATED);
return $this->name;
}
@ -119,7 +123,7 @@ class Event
*
* @param string $name The event name.
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call.
* @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
*
* @api
*/

View File

@ -68,7 +68,7 @@ class EventDispatcher implements EventDispatcherInterface
return $this->sorted[$eventName];
}
foreach (array_keys($this->listeners) as $eventName) {
foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}

View File

@ -77,7 +77,7 @@ interface EventDispatcherInterface
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners.
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @param string $eventName The name of the event
*

View File

@ -71,7 +71,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('%s not found in %s', $key, $this->getName()));
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
}
/**

View File

@ -23,5 +23,5 @@ Resources
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/EventDispatcher/
$ composer.phar install
$ composer install
$ phpunit

View File

@ -118,10 +118,21 @@ abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
$event = new Event();
$return = $this->dispatcher->dispatch(self::preFoo, $event);
$this->assertEquals('pre.foo', $event->getName());
$this->assertSame($event, $return);
}
/**
* @group legacy
*/
public function testLegacyDispatch()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$event = new Event();
$return = $this->dispatcher->dispatch(self::preFoo, $event);
$this->assertEquals('pre.foo', $event->getName());
}
public function testDispatchForClosure()
{
$invoked = 0;
@ -239,8 +250,13 @@ abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testEventReceivesTheDispatcherInstance()
/**
* @group legacy
*/
public function testLegacyEventReceivesTheDispatcherInstance()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$dispatcher = null;
$this->dispatcher->addListener('test', function ($event) use (&$dispatcher) {
$dispatcher = $event->getDispatcher();

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\EventDispatcher\Tests\Debug;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
@ -86,6 +87,20 @@ class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
}
public function testGetCalledListenersNested()
{
$tdispatcher = null;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
$tdispatcher = $dispatcher;
$dispatcher->dispatch('bar');
});
$dispatcher->addListener('bar', function (Event $event) {});
$dispatcher->dispatch('foo');
$this->assertSame($dispatcher, $tdispatcher);
$this->assertCount(2, $dispatcher->getCalledListeners());
}
public function testLogger()
{
$logger = $this->getMock('Psr\Log\LoggerInterface');
@ -160,6 +175,19 @@ class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
$dispatcher->dispatch('foo');
$this->assertTrue($nestedCall);
}
public function testListenerCanRemoveItselfWhenExecuted()
{
$eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
$dispatcher->removeListener('foo', $listener1);
};
$eventDispatcher->addListener('foo', $listener1);
$eventDispatcher->addListener('foo', function () {});
$eventDispatcher->dispatch('foo');
$this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
}
}
class EventSubscriber implements EventSubscriberInterface

Some files were not shown because too many files have changed in this diff Show More