Initial Commit
This commit is contained in:
commit
1cf4021d4f
36
application/configs/authentication.iControl.xml
Normal file
36
application/configs/authentication.iControl.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure authentication for players -->
|
||||||
|
<authentication-config>
|
||||||
|
|
||||||
|
<!-- SuperAdmins with full access -->
|
||||||
|
<superadmin>
|
||||||
|
<!-- <login>login1</login> <login>login2</login> -->
|
||||||
|
<login>steeffeen</login>
|
||||||
|
</superadmin>
|
||||||
|
|
||||||
|
<!-- Admins with almost full access -->
|
||||||
|
<admin>
|
||||||
|
<!-- <login>login1</login> <login>login2</login> -->
|
||||||
|
<login>gorby</login>
|
||||||
|
<login>canyondrive</login>
|
||||||
|
</admin>
|
||||||
|
|
||||||
|
<!-- Operators with only moderating access -->
|
||||||
|
<operator>
|
||||||
|
<!-- <login>login1</login> <login>login2</login> -->
|
||||||
|
<login>eyebo</login>
|
||||||
|
<login>jojo95183</login>
|
||||||
|
<login>xanashea</login>
|
||||||
|
<login>ardid</login>
|
||||||
|
<login>gugli</login>
|
||||||
|
<login>phil13hebert</login>
|
||||||
|
<login>xcaliber</login>
|
||||||
|
<login>eole</login>
|
||||||
|
<login>fix</login>
|
||||||
|
<login>kremsy</login>
|
||||||
|
<login>papychampy</login>
|
||||||
|
<login>titishu</login>
|
||||||
|
<login>wurstigewurst</login>
|
||||||
|
</operator>
|
||||||
|
|
||||||
|
</authentication-config>
|
12
application/configs/chat.iControl.xml
Normal file
12
application/configs/chat.iControl.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure how iControl sends chat message -->
|
||||||
|
<chat-config>
|
||||||
|
|
||||||
|
<!-- Set which codes should be used to compose chat messages -->
|
||||||
|
<messages>
|
||||||
|
<information>$fff</information>
|
||||||
|
<success>$0f0</success>
|
||||||
|
<error>$f00</error>
|
||||||
|
</messages>
|
||||||
|
|
||||||
|
</chat-config>
|
14
application/configs/chatlog.plugin.xml
Normal file
14
application/configs/chatlog.plugin.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the chatlog plugin -->
|
||||||
|
<chatlog-config>
|
||||||
|
|
||||||
|
<!-- Enable plugin -->
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Filename for the chatlog -->
|
||||||
|
<filename>chat.log</filename>
|
||||||
|
|
||||||
|
<!-- Log server messages -->
|
||||||
|
<log_server_messages>true</log_server_messages>
|
||||||
|
|
||||||
|
</chatlog-config>
|
44
application/configs/commands.iControl.xml
Normal file
44
application/configs/commands.iControl.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Enable/Disable chat commands -->
|
||||||
|
<!-- Authentication levels: superadmin, admin, operator, all, none -->
|
||||||
|
<!-- 'Operator' means that you have to be at least Operator to perform the command -->
|
||||||
|
<!-- 'None' means that nobody is able to use the command (disabled) -->
|
||||||
|
<commands-config>
|
||||||
|
|
||||||
|
<!-- SuperAdmin commands -->
|
||||||
|
<superadmin>
|
||||||
|
<shutdown />
|
||||||
|
<shutdownserver />
|
||||||
|
<networkstats />
|
||||||
|
<systeminfo />
|
||||||
|
<pay />
|
||||||
|
</superadmin>
|
||||||
|
|
||||||
|
<!-- Admin commands -->
|
||||||
|
<admin>
|
||||||
|
<getplanets />
|
||||||
|
<setservername />
|
||||||
|
<getservername />
|
||||||
|
</admin>
|
||||||
|
|
||||||
|
<!-- Operator commands -->
|
||||||
|
<operator>
|
||||||
|
<kick />
|
||||||
|
<nextmap />
|
||||||
|
<addmap />
|
||||||
|
<removemap />
|
||||||
|
<startwarmup />
|
||||||
|
<stopwarmup />
|
||||||
|
</operator>
|
||||||
|
|
||||||
|
<!-- Everybody commands -->
|
||||||
|
<all>
|
||||||
|
<help />
|
||||||
|
<donate />
|
||||||
|
</all>
|
||||||
|
|
||||||
|
<!-- Nobody commands -->
|
||||||
|
<none>
|
||||||
|
</none>
|
||||||
|
|
||||||
|
</commands-config>
|
15
application/configs/core.iControl.xml
Normal file
15
application/configs/core.iControl.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Core configuration for iControl -->
|
||||||
|
<core-config>
|
||||||
|
|
||||||
|
<!-- Connection timeout -->
|
||||||
|
<timeout>20</timeout>
|
||||||
|
|
||||||
|
<!-- Define the file name for automatically saving the match settings after each map (relative from /Maps/MatchSettings/) -->
|
||||||
|
<!-- (leave empty for not saving) -->
|
||||||
|
<autosave_matchsettings>tracklist.txt</autosave_matchsettings>
|
||||||
|
|
||||||
|
<!-- Directory for downloaded maps (relative from /Maps/) -->
|
||||||
|
<maps_dir>mx</maps_dir>
|
||||||
|
|
||||||
|
</core-config>
|
16
application/configs/database.iControl.xml
Normal file
16
application/configs/database.iControl.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the mysql database used by iControl -->
|
||||||
|
<database-config>
|
||||||
|
|
||||||
|
<!-- MySQL Server -->
|
||||||
|
<host>localhost</host>
|
||||||
|
<port>3306</port>
|
||||||
|
|
||||||
|
<!-- MySQL User -->
|
||||||
|
<user>steff</user>
|
||||||
|
<pass>kjhgvhbjnfih2394ugnjk</pass>
|
||||||
|
|
||||||
|
<!-- Database Name -->
|
||||||
|
<database>steff_united</database>
|
||||||
|
|
||||||
|
</database-config>
|
15
application/configs/karma.plugin.xml
Normal file
15
application/configs/karma.plugin.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the karma plugin -->
|
||||||
|
<karma-plugin-config>
|
||||||
|
|
||||||
|
<!-- Enable the plugin -->
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<title>Map Voting</title>
|
||||||
|
|
||||||
|
<!-- Position -->
|
||||||
|
<pos_x>92</pos_x>
|
||||||
|
<pos_y>83</pos_y>
|
||||||
|
|
||||||
|
</karma-plugin-config>
|
11
application/configs/obstacle.plugin.xml
Normal file
11
application/configs/obstacle.plugin.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the obstacle plugin -->
|
||||||
|
<obstacle-config>
|
||||||
|
|
||||||
|
<!-- Enable/Disable the whole plugin -->
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Set right level needed for jumps -->
|
||||||
|
<jumps_rightlevel>operator</jumps_rightlevel>
|
||||||
|
|
||||||
|
</obstacle-config>
|
13
application/configs/plugins.iControl.xml
Normal file
13
application/configs/plugins.iControl.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure which plugins should be loaded -->
|
||||||
|
<plugins-config>
|
||||||
|
<!-- <plugin>FILENAME1</plugin> <plugin>FILENAME2</plugin> -->
|
||||||
|
|
||||||
|
<plugin>chatlog.plugin.php</plugin>
|
||||||
|
<plugin>karma.plugin.php</plugin>
|
||||||
|
<plugin>records.plugin.php</plugin>
|
||||||
|
<plugin>united.plugin.php</plugin>
|
||||||
|
|
||||||
|
<!-- <plugin>obstacle.plugin.php</plugin> -->
|
||||||
|
|
||||||
|
</plugins-config>
|
67
application/configs/records.plugin.xml
Normal file
67
application/configs/records.plugin.xml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the records plugin -->
|
||||||
|
<records-config>
|
||||||
|
|
||||||
|
<!-- Enable/Disable the whole plugin -->
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Configure local records -->
|
||||||
|
<local_records>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Define how many records per map are saved -->
|
||||||
|
<count>200</count>
|
||||||
|
|
||||||
|
<!-- Define the widget -->
|
||||||
|
<widget>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<pos_x>-139</pos_x>
|
||||||
|
<pos_y>65</pos_y>
|
||||||
|
<title>Local Records</title>
|
||||||
|
<width>40</width>
|
||||||
|
<lines>25</lines>
|
||||||
|
<line_height>4</line_height>
|
||||||
|
</widget>
|
||||||
|
|
||||||
|
</local_records>
|
||||||
|
|
||||||
|
<!-- Configure dedimania records -->
|
||||||
|
<dedimania_records>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Define your dedimania account (http://dedimania.net/tm2stats/?do=register) -->
|
||||||
|
<account>
|
||||||
|
<login>steff_test</login>
|
||||||
|
<code>468a0a185c</code>
|
||||||
|
</account>
|
||||||
|
<account>
|
||||||
|
<login>nsa_dev</login>
|
||||||
|
<code>3c20cbc737</code>
|
||||||
|
</account>
|
||||||
|
<account>
|
||||||
|
<login>united_canyon</login>
|
||||||
|
<code>703d080ddd</code>
|
||||||
|
</account>
|
||||||
|
<account>
|
||||||
|
<login>united_stadium</login>
|
||||||
|
<code>067b0d0017</code>
|
||||||
|
</account>
|
||||||
|
<account>
|
||||||
|
<login>united_valley</login>
|
||||||
|
<code>37ed993fe1</code>
|
||||||
|
</account>
|
||||||
|
|
||||||
|
<!-- Define the widget -->
|
||||||
|
<widget>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<pos_x>139</pos_x>
|
||||||
|
<pos_y>60</pos_y>
|
||||||
|
<title>Dedimania</title>
|
||||||
|
<width>40</width>
|
||||||
|
<lines>20</lines>
|
||||||
|
<line_height>4</line_height>
|
||||||
|
</widget>
|
||||||
|
|
||||||
|
</dedimania_records>
|
||||||
|
|
||||||
|
</records-config>
|
17
application/configs/server.iControl.xml
Normal file
17
application/configs/server.iControl.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure your maniaplanet server -->
|
||||||
|
<server-config>
|
||||||
|
|
||||||
|
<!-- Enable to use only one static server (default behavior) -->
|
||||||
|
<enable>true</enable>
|
||||||
|
|
||||||
|
<!-- Server connection details (Make sure that the firewall and the server config are properly configured!) -->
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<host>localhost</host>
|
||||||
|
<port>21003</port>
|
||||||
|
|
||||||
|
<!-- XmlRpc login details -->
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
|
||||||
|
</server-config>
|
43
application/configs/stats.iControl.xml
Normal file
43
application/configs/stats.iControl.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the stats manager and enable/disable specific stats -->
|
||||||
|
<stats-config>
|
||||||
|
|
||||||
|
<stats>
|
||||||
|
|
||||||
|
<!-- Server stats -->
|
||||||
|
<server>
|
||||||
|
|
||||||
|
<!-- Track how many players connected on the server -->
|
||||||
|
<track_server_connects>true</track_server_connects>
|
||||||
|
|
||||||
|
<!-- Track how many players have been on the server at max -->
|
||||||
|
<track_server_max_players>true</track_server_max_players>
|
||||||
|
|
||||||
|
<!-- Track how many maps have been played during the day -->
|
||||||
|
<track_server_played_maps>true</track_server_played_maps>
|
||||||
|
|
||||||
|
<!-- Track how many times players finished during the day -->
|
||||||
|
<track_server_finishes>true</track_server_finishes>
|
||||||
|
|
||||||
|
</server>
|
||||||
|
|
||||||
|
<!-- Player stats -->
|
||||||
|
<player>
|
||||||
|
|
||||||
|
<!-- Track player connect counts -->
|
||||||
|
<track_player_connects>true</track_player_connects>
|
||||||
|
|
||||||
|
<!-- Track player play time -->
|
||||||
|
<track_player_playtime>true</track_player_playtime>
|
||||||
|
|
||||||
|
<!-- Track how many chat message players have sent -->
|
||||||
|
<track_player_chats>true</track_player_chats>
|
||||||
|
|
||||||
|
<!-- Track player finishes -->
|
||||||
|
<track_player_finishes>true</track_player_finishes>
|
||||||
|
|
||||||
|
</player>
|
||||||
|
|
||||||
|
</stats>
|
||||||
|
|
||||||
|
</stats-config>
|
119
application/configs/united.plugin.xml
Normal file
119
application/configs/united.plugin.xml
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Configure the United plugin -->
|
||||||
|
<!-- Note: You should disable the config server.iControl.xml while using this plugin -->
|
||||||
|
<united-config>
|
||||||
|
|
||||||
|
<!-- Enable the whole plugin -->
|
||||||
|
<enabled>false</enabled>
|
||||||
|
|
||||||
|
<!-- Configure the server order (Random or as defined in the xml) -->
|
||||||
|
<random_order>false</random_order>
|
||||||
|
|
||||||
|
<!-- Define the mode that should be running on all the server -->
|
||||||
|
<!-- Rounds (1), TimeAttack (2), Team (3), Laps (4), Cup (5), Stunts (6) -->
|
||||||
|
<mode>2</mode>
|
||||||
|
|
||||||
|
<!-- Whether game server should be hidden from the browser -->
|
||||||
|
<hide_game_server>false</hide_game_server>
|
||||||
|
|
||||||
|
<!-- Set lobby status for lobby server -->
|
||||||
|
<set_lobby_status>true</set_lobby_status>
|
||||||
|
|
||||||
|
<!-- Define the game server -->
|
||||||
|
<gameserver>
|
||||||
|
|
||||||
|
<!-- Define the join/spec password for game server -->
|
||||||
|
<password>a</password>
|
||||||
|
|
||||||
|
<!-- Template -->
|
||||||
|
<!-- <server> -->
|
||||||
|
<!-- <host>ip</host> -->
|
||||||
|
<!-- <port>5000</port> -->
|
||||||
|
<!-- <login>SuperAdmin</login> -->
|
||||||
|
<!-- <pass>SuperAdmin</pass> -->
|
||||||
|
<!-- </server> -->
|
||||||
|
|
||||||
|
<!-- Canyon -->
|
||||||
|
<server>
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<port>21013</port>
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
<!-- Stadium -->
|
||||||
|
<server>
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<port>21033</port>
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
<!-- Valley -->
|
||||||
|
<server>
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<port>21053</port>
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
</gameserver>
|
||||||
|
|
||||||
|
<!-- Define lobbies -->
|
||||||
|
<lobbies>
|
||||||
|
|
||||||
|
<!-- Define the join/spec password for lobbies -->
|
||||||
|
<password>a</password>
|
||||||
|
|
||||||
|
<!-- Template -->
|
||||||
|
<!-- <server> -->
|
||||||
|
<!-- <host>ip</host> -->
|
||||||
|
<!-- <port>5000</port> -->
|
||||||
|
<!-- <login>SuperAdmin</login> -->
|
||||||
|
<!-- <pass>SuperAdmin</pass> -->
|
||||||
|
<!-- </server> -->
|
||||||
|
|
||||||
|
<!-- Canyon -->
|
||||||
|
<server>
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<port>21023</port>
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
<!-- Stadium -->
|
||||||
|
<server>
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<port>21043</port>
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
<!-- Valley -->
|
||||||
|
<server>
|
||||||
|
<host>144.76.158.111</host>
|
||||||
|
<port>21063</port>
|
||||||
|
<login>SuperAdmin</login>
|
||||||
|
<pass>dtcfvgubhnjomkjnbhv</pass>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
</lobbies>
|
||||||
|
|
||||||
|
<!-- Configure widgets -->
|
||||||
|
<widgets>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
|
||||||
|
<!-- Favorite widget -->
|
||||||
|
<addfavorite>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<pos_x>76</pos_x>
|
||||||
|
<pos_y>85.5</pos_y>
|
||||||
|
<width>10</width>
|
||||||
|
<height>10</height>
|
||||||
|
<!-- Enable add_all to let players add all game and lobby server to favorite with 1 click -->
|
||||||
|
<add_all>true</add_all>
|
||||||
|
</addfavorite>
|
||||||
|
|
||||||
|
</widgets>
|
||||||
|
|
||||||
|
</united-config>
|
892
application/core/PhpRemote/GbxRemote.bem.php
Normal file
892
application/core/PhpRemote/GbxRemote.bem.php
Normal file
@ -0,0 +1,892 @@
|
|||||||
|
<?php
|
||||||
|
/* vim: set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
|
||||||
|
|
||||||
|
/*
|
||||||
|
IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002
|
||||||
|
Version 1.61 - Simon Willison, 11th July 2003 (htmlentities -> htmlspecialchars)
|
||||||
|
Site: http://scripts.incutio.com/xmlrpc/
|
||||||
|
Manual: http://scripts.incutio.com/xmlrpc/manual.php
|
||||||
|
Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
|
||||||
|
Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php
|
||||||
|
|
||||||
|
Modified to support protocol 'GbxRemote 2' ('GbxRemote 1')
|
||||||
|
This version is for BigEndian machines. For LittleEndian (e.g. Intel PC)
|
||||||
|
machines use the original GbxRemote.inc.php instead.
|
||||||
|
|
||||||
|
Release 2007-09-22 - Slig:
|
||||||
|
Modified to support >256KB received data (and now >2MB data produce a specific error message)
|
||||||
|
Modified readCB() to wait the initial timeout only before first read packet
|
||||||
|
Modified readCB() to return true if there is data to get with getCBResponses()
|
||||||
|
Modified to support amd64 (for $recvhandle)
|
||||||
|
Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1
|
||||||
|
Added IXR_Client_Gbx::bytes_sent & bytes_received counters
|
||||||
|
Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select)
|
||||||
|
Workaround for stream_select return value bug with amd64
|
||||||
|
|
||||||
|
Release 2008-01-20 - Slig / Xymph / Assembler Maniac:
|
||||||
|
Workaround for fread delay bug in some cases
|
||||||
|
Added IXR_Client_Gbx::resetError() method (by Xymph)
|
||||||
|
Some comments and strings code cleanup (by Xymph)
|
||||||
|
Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache)
|
||||||
|
Added a default timeout value to IXR_Client_Gbx::readCB($timeout)
|
||||||
|
Changed calls with timeout on a stream to use microseconds instead of seconds (by AM)
|
||||||
|
Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM)
|
||||||
|
|
||||||
|
Release 2008-02-05 - Slig:
|
||||||
|
Changed some socket read/write timeouts back to seconds to avoid 'transport error'
|
||||||
|
Changed max data received from 2MB to 4MB
|
||||||
|
|
||||||
|
Release 2008-05-20 - Xymph:
|
||||||
|
Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies
|
||||||
|
Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError()
|
||||||
|
Tweaked some 'transport error' messages
|
||||||
|
|
||||||
|
Release 2009-04-08 - Gou1:
|
||||||
|
Added method IXR_Client_Gbx::queryIgnoreResult()
|
||||||
|
Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult()
|
||||||
|
IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors
|
||||||
|
If larger than 512KB and method is system.multicall, try to divide the request into
|
||||||
|
two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls
|
||||||
|
|
||||||
|
Release 2009-06-03 - Xymph:
|
||||||
|
Suppress possible repetitive CRT warning at stream_select
|
||||||
|
|
||||||
|
Release 2011-04-09 - Xymph / La beuze:
|
||||||
|
Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp()
|
||||||
|
|
||||||
|
Release 2011-05-22 - Xymph:
|
||||||
|
Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult()
|
||||||
|
Updated status codes and messages for transport/endian errors
|
||||||
|
Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage()
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('LF')) {
|
||||||
|
define('LF', "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
class IXR_Value {
|
||||||
|
public $data;
|
||||||
|
public $type;
|
||||||
|
|
||||||
|
function IXR_Value ($data, $type = false) {
|
||||||
|
$this->data = $data;
|
||||||
|
if (!$type) {
|
||||||
|
$type = $this->calculateType();
|
||||||
|
}
|
||||||
|
$this->type = $type;
|
||||||
|
if ($type == 'struct') {
|
||||||
|
// Turn all the values in the array into new IXR_Value objects
|
||||||
|
foreach ($this->data as $key => $value) {
|
||||||
|
$this->data[$key] = new IXR_Value($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type == 'array') {
|
||||||
|
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
|
||||||
|
$this->data[$i] = new IXR_Value($this->data[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateType() {
|
||||||
|
if ($this->data === true || $this->data === false) {
|
||||||
|
return 'boolean';
|
||||||
|
}
|
||||||
|
if (is_integer($this->data)) {
|
||||||
|
return 'int';
|
||||||
|
}
|
||||||
|
if (is_double($this->data)) {
|
||||||
|
return 'double';
|
||||||
|
}
|
||||||
|
// Deal with IXR object types base64 and date
|
||||||
|
if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
|
||||||
|
return 'date';
|
||||||
|
}
|
||||||
|
if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
|
||||||
|
return 'base64';
|
||||||
|
}
|
||||||
|
// If it is a normal PHP object convert it into a struct
|
||||||
|
if (is_object($this->data)) {
|
||||||
|
$this->data = get_object_vars($this->data);
|
||||||
|
return 'struct';
|
||||||
|
}
|
||||||
|
if (!is_array($this->data)) {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
// We have an array - is it an array or a struct?
|
||||||
|
if ($this->isStruct($this->data)) {
|
||||||
|
return 'struct';
|
||||||
|
} else {
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
// Return XML for this value
|
||||||
|
switch ($this->type) {
|
||||||
|
case 'boolean':
|
||||||
|
return '<boolean>' . ($this->data ? '1' : '0') . '</boolean>';
|
||||||
|
break;
|
||||||
|
case 'int':
|
||||||
|
return '<int>' . $this->data . '</int>';
|
||||||
|
break;
|
||||||
|
case 'double':
|
||||||
|
return '<double>' . $this->data . '</double>';
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
return '<string>' . htmlspecialchars($this->data) . '</string>';
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
$return = '<array><data>' . LF;
|
||||||
|
foreach ($this->data as $item) {
|
||||||
|
$return .= ' <value>' . $item->getXml() . '</value>' . LF;
|
||||||
|
}
|
||||||
|
$return .= '</data></array>';
|
||||||
|
return $return;
|
||||||
|
break;
|
||||||
|
case 'struct':
|
||||||
|
$return = '<struct>' . LF;
|
||||||
|
foreach ($this->data as $name => $value) {
|
||||||
|
$return .= ' <member><name>' . $name . '</name><value>';
|
||||||
|
$return .= $value->getXml() . '</value></member>' . LF;
|
||||||
|
}
|
||||||
|
$return .= '</struct>';
|
||||||
|
return $return;
|
||||||
|
break;
|
||||||
|
case 'date':
|
||||||
|
case 'base64':
|
||||||
|
return $this->data->getXml();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStruct($array) {
|
||||||
|
// Nasty function to check if an array is a struct or not
|
||||||
|
$expected = 0;
|
||||||
|
foreach ($array as $key => $value) {
|
||||||
|
if ((string)$key != (string)$expected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$expected++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Message {
|
||||||
|
public $message;
|
||||||
|
public $messageType; // methodCall / methodResponse / fault
|
||||||
|
public $faultCode;
|
||||||
|
public $faultString;
|
||||||
|
public $methodName;
|
||||||
|
public $params;
|
||||||
|
// Current variable stacks
|
||||||
|
protected $_arraystructs = array(); // Stack to keep track of the current array/struct
|
||||||
|
protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array
|
||||||
|
protected $_currentStructName = array(); // A stack as well
|
||||||
|
protected $_param;
|
||||||
|
protected $_value;
|
||||||
|
protected $_currentTag;
|
||||||
|
protected $_currentTagContents;
|
||||||
|
// The XML parser
|
||||||
|
protected $_parser;
|
||||||
|
|
||||||
|
function IXR_Message ($message) {
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse() {
|
||||||
|
// first remove the XML declaration
|
||||||
|
$this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
|
||||||
|
if (trim($this->message) == '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->_parser = xml_parser_create();
|
||||||
|
// Set XML parser to take the case of tags into account
|
||||||
|
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
|
||||||
|
// Set XML parser callback functions
|
||||||
|
xml_set_object($this->_parser, $this);
|
||||||
|
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
|
||||||
|
xml_set_character_data_handler($this->_parser, 'cdata');
|
||||||
|
if (!xml_parse($this->_parser, $this->message)) {
|
||||||
|
/* die(sprintf('GbxRemote XML error: %s at line %d',
|
||||||
|
xml_error_string(xml_get_error_code($this->_parser)),
|
||||||
|
xml_get_current_line_number($this->_parser))); */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
xml_parser_free($this->_parser);
|
||||||
|
// Grab the error messages, if any
|
||||||
|
if ($this->messageType == 'fault') {
|
||||||
|
$this->faultCode = $this->params[0]['faultCode'];
|
||||||
|
$this->faultString = $this->params[0]['faultString'];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tag_open($parser, $tag, $attr) {
|
||||||
|
$this->currentTag = $tag;
|
||||||
|
switch ($tag) {
|
||||||
|
case 'methodCall':
|
||||||
|
case 'methodResponse':
|
||||||
|
case 'fault':
|
||||||
|
$this->messageType = $tag;
|
||||||
|
break;
|
||||||
|
// Deal with stacks of arrays and structs
|
||||||
|
case 'data': // data is to all intents and purposes more interesting than array
|
||||||
|
$this->_arraystructstypes[] = 'array';
|
||||||
|
$this->_arraystructs[] = array();
|
||||||
|
break;
|
||||||
|
case 'struct':
|
||||||
|
$this->_arraystructstypes[] = 'struct';
|
||||||
|
$this->_arraystructs[] = array();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cdata($parser, $cdata) {
|
||||||
|
$this->_currentTagContents .= $cdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tag_close($parser, $tag) {
|
||||||
|
$valueFlag = false;
|
||||||
|
switch ($tag) {
|
||||||
|
case 'int':
|
||||||
|
case 'i4':
|
||||||
|
$value = (int)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'double':
|
||||||
|
$value = (double)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
$value = (string)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'dateTime.iso8601':
|
||||||
|
$value = new IXR_Date(trim($this->_currentTagContents));
|
||||||
|
// $value = $iso->getTimestamp();
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'value':
|
||||||
|
// If no type is indicated, the type is string
|
||||||
|
if (trim($this->_currentTagContents) != '') {
|
||||||
|
$value = (string)$this->_currentTagContents;
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
$value = (boolean)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'base64':
|
||||||
|
$value = base64_decode($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
// Deal with stacks of arrays and structs
|
||||||
|
case 'data':
|
||||||
|
case 'struct':
|
||||||
|
$value = array_pop($this->_arraystructs);
|
||||||
|
array_pop($this->_arraystructstypes);
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'member':
|
||||||
|
array_pop($this->_currentStructName);
|
||||||
|
break;
|
||||||
|
case 'name':
|
||||||
|
$this->_currentStructName[] = trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
break;
|
||||||
|
case 'methodName':
|
||||||
|
$this->methodName = trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($valueFlag) {
|
||||||
|
/*
|
||||||
|
if (!is_array($value) && !is_object($value)) {
|
||||||
|
$value = trim($value);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (count($this->_arraystructs) > 0) {
|
||||||
|
// Add value to struct or array
|
||||||
|
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
|
||||||
|
// Add to struct
|
||||||
|
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
|
||||||
|
} else {
|
||||||
|
// Add to array
|
||||||
|
$this->_arraystructs[count($this->_arraystructs)-1][] = $value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just add as a paramater
|
||||||
|
$this->params[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Request {
|
||||||
|
public $method;
|
||||||
|
public $args;
|
||||||
|
public $xml;
|
||||||
|
|
||||||
|
function IXR_Request($method, $args) {
|
||||||
|
$this->method = $method;
|
||||||
|
$this->args = $args;
|
||||||
|
$this->xml = '<?xml version="1.0" encoding="utf-8" ?><methodCall><methodName>' . $this->method . '</methodName><params>';
|
||||||
|
foreach ($this->args as $arg) {
|
||||||
|
$this->xml .= '<param><value>';
|
||||||
|
$v = new IXR_Value($arg);
|
||||||
|
$this->xml .= $v->getXml();
|
||||||
|
$this->xml .= '</value></param>' . LF;
|
||||||
|
}
|
||||||
|
$this->xml .= '</params></methodCall>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLength() {
|
||||||
|
return strlen($this->xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
return $this->xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Error {
|
||||||
|
public $code;
|
||||||
|
public $message;
|
||||||
|
|
||||||
|
function IXR_Error($code, $message) {
|
||||||
|
$this->code = $code;
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
$xml = <<<EOD
|
||||||
|
<methodResponse>
|
||||||
|
<fault>
|
||||||
|
<value>
|
||||||
|
<struct>
|
||||||
|
<member>
|
||||||
|
<name>faultCode</name>
|
||||||
|
<value><int>{$this->code}</int></value>
|
||||||
|
</member>
|
||||||
|
<member>
|
||||||
|
<name>faultString</name>
|
||||||
|
<value><string>{$this->message}</string></value>
|
||||||
|
</member>
|
||||||
|
</struct>
|
||||||
|
</value>
|
||||||
|
</fault>
|
||||||
|
</methodResponse>
|
||||||
|
EOD;
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Date {
|
||||||
|
public $year;
|
||||||
|
public $month;
|
||||||
|
public $day;
|
||||||
|
public $hour;
|
||||||
|
public $minute;
|
||||||
|
public $second;
|
||||||
|
|
||||||
|
function IXR_Date($time) {
|
||||||
|
// $time can be a PHP timestamp or an ISO one
|
||||||
|
if (is_numeric($time)) {
|
||||||
|
$this->parseTimestamp($time);
|
||||||
|
} else {
|
||||||
|
$this->parseIso($time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTimestamp($timestamp) {
|
||||||
|
$this->year = date('Y', $timestamp);
|
||||||
|
$this->month = date('Y', $timestamp);
|
||||||
|
$this->day = date('Y', $timestamp);
|
||||||
|
$this->hour = date('H', $timestamp);
|
||||||
|
$this->minute = date('i', $timestamp);
|
||||||
|
$this->second = date('s', $timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIso($iso) {
|
||||||
|
$this->year = substr($iso, 0, 4);
|
||||||
|
$this->month = substr($iso, 4, 2);
|
||||||
|
$this->day = substr($iso, 6, 2);
|
||||||
|
$this->hour = substr($iso, 9, 2);
|
||||||
|
$this->minute = substr($iso, 12, 2);
|
||||||
|
$this->second = substr($iso, 15, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIso() {
|
||||||
|
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimestamp() {
|
||||||
|
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Base64 {
|
||||||
|
public $data;
|
||||||
|
|
||||||
|
function IXR_Base64($data) {
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
return '<base64>'.base64_encode($this->data).'</base64>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// Nadeo modifications //
|
||||||
|
// (many thanks to slig for adding callback support) //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
class IXR_Client_Gbx {
|
||||||
|
public $socket;
|
||||||
|
public $message = false;
|
||||||
|
public $cb_message = array();
|
||||||
|
public $reqhandle;
|
||||||
|
public $protocol = 0;
|
||||||
|
// Storage place for an error message
|
||||||
|
public $error = false;
|
||||||
|
|
||||||
|
function bigEndianTest() {
|
||||||
|
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
|
||||||
|
if ($endiantest == 1) {
|
||||||
|
echo "Machine reports itself as LittleEndian, float handling will work correctly with unpack\r\n";
|
||||||
|
echo "Use original GbxRemote.inc.php instead of GbxRemote.bem.php\r\n";
|
||||||
|
die('App Terminated');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} // bigEndianTest
|
||||||
|
|
||||||
|
function IXR_Client_Gbx() {
|
||||||
|
$this->socket = false;
|
||||||
|
$this->reqhandle = 0x80000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InitWithIp($ip, $port, $timeout = null) {
|
||||||
|
|
||||||
|
if (!$this->bigEndianTest()) {
|
||||||
|
$this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open connection, with timeout if specified
|
||||||
|
if (!isset($timeout)) {
|
||||||
|
$this->socket = @fsockopen($ip, $port, $errno, $errstr);
|
||||||
|
} else {
|
||||||
|
$init_time = microtime(true);
|
||||||
|
$init_timeout = 5; // retry every 5s
|
||||||
|
while (true) {
|
||||||
|
$this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout);
|
||||||
|
if ($this->socket || (microtime(true) - $init_time >= $timeout))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$this->socket) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// handshake
|
||||||
|
$array_result = big_endian_unpack('Vsize', fread($this->socket, 4));
|
||||||
|
$size = $array_result['size'];
|
||||||
|
if ($size > 64) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$handshake = fread($this->socket, $size);
|
||||||
|
if ($handshake == 'GBXRemote 1') {
|
||||||
|
$this->protocol = 1;
|
||||||
|
} else if ($handshake == 'GBXRemote 2') {
|
||||||
|
$this->protocol = 2;
|
||||||
|
} else {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Init($port) {
|
||||||
|
return $this->InitWithIp('localhost', $port);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Terminate() {
|
||||||
|
if ($this->socket) {
|
||||||
|
fclose($this->socket);
|
||||||
|
$this->socket = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sendRequest(IXR_Request $request) {
|
||||||
|
$xml = $request->getXml();
|
||||||
|
|
||||||
|
@stream_set_timeout($this->socket, 20); // timeout 20s (to write the request)
|
||||||
|
// send request
|
||||||
|
$this->reqhandle++;
|
||||||
|
if ($this->protocol == 1) {
|
||||||
|
$bytes = pack('Va*', strlen($xml), $xml);
|
||||||
|
} else {
|
||||||
|
$bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes_to_write = strlen($bytes);
|
||||||
|
while ($bytes_to_write > 0) {
|
||||||
|
$r = @fwrite($this->socket, $bytes);
|
||||||
|
if ($r === false || $r == 0) {
|
||||||
|
// connection interrupted
|
||||||
|
return false; // or die?
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes_to_write -= $r;
|
||||||
|
if ($bytes_to_write == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
$bytes = substr($bytes, $r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getResult() {
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
do {
|
||||||
|
$size = 0;
|
||||||
|
$recvhandle = 0;
|
||||||
|
@stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header)
|
||||||
|
// Get result
|
||||||
|
if ($this->protocol == 1) {
|
||||||
|
$contents = fread($this->socket, 4);
|
||||||
|
if (strlen($contents) == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - cannot read size');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$array_result = big_endian_unpack('Vsize', $contents);
|
||||||
|
$size = $array_result['size'];
|
||||||
|
$recvhandle = $this->reqhandle;
|
||||||
|
} else {
|
||||||
|
$contents = fread($this->socket, 8);
|
||||||
|
if (strlen($contents) == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$array_result = big_endian_unpack('Vsize/Vhandle', $contents);
|
||||||
|
$size = $array_result['size'];
|
||||||
|
$recvhandle = $array_result['handle'];
|
||||||
|
// -- amd64 support --
|
||||||
|
$bits = sprintf('%b', $recvhandle);
|
||||||
|
if (strlen($bits) == 64) {
|
||||||
|
$recvhandle = bindec(substr($bits, 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($recvhandle == 0 || $size == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($size > 4096*1024) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end)
|
||||||
|
while ($contents_length < $size) {
|
||||||
|
$contents .= fread($this->socket, $size-$contents_length);
|
||||||
|
$contents_length = strlen($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($recvhandle & 0x80000000) == 0) {
|
||||||
|
// this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
|
||||||
|
// just add it to the message list for the user to read
|
||||||
|
$new_cb_message = new IXR_Message($contents);
|
||||||
|
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
|
||||||
|
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while ((int)$recvhandle != (int)$this->reqhandle);
|
||||||
|
|
||||||
|
$this->message = new IXR_Message($contents);
|
||||||
|
if (!$this->message->parse()) {
|
||||||
|
// XML error
|
||||||
|
$this->error = new IXR_Error(-32700, 'parse error. not well formed');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Is the message a fault?
|
||||||
|
if ($this->message->messageType == 'fault') {
|
||||||
|
$this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Message must be OK
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function query() {
|
||||||
|
$args = func_get_args();
|
||||||
|
$method = array_shift($args);
|
||||||
|
|
||||||
|
if (!$this->socket || $this->protocol == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = new IXR_Request($method, $args);
|
||||||
|
|
||||||
|
// Check if request is larger than 512 Kbytes
|
||||||
|
if (($size = $request->getLength()) > 512*1024-8) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
$ok = $this->sendRequest($request);
|
||||||
|
if (!$ok) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get result
|
||||||
|
return $this->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-blocking query method: doesn't read the response
|
||||||
|
function queryIgnoreResult() {
|
||||||
|
$args = func_get_args();
|
||||||
|
$method = array_shift($args);
|
||||||
|
|
||||||
|
if (!$this->socket || $this->protocol == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = new IXR_Request($method, $args);
|
||||||
|
|
||||||
|
// Check if the request is greater than 512 Kbytes to avoid errors
|
||||||
|
// If the method is system.multicall, make two calls (possibly recursively)
|
||||||
|
if (($size = $request->getLength()) > 512*1024-8) {
|
||||||
|
if ($method = 'system.multicall' && isset($args[0])) {
|
||||||
|
$count = count($args[0]);
|
||||||
|
// If count is 1, query cannot be reduced
|
||||||
|
if ($count < 2) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$length = floor($count/2);
|
||||||
|
$args1 = array_slice($args[0], 0, $length);
|
||||||
|
$args2 = array_slice($args[0], $length, ($count-$length));
|
||||||
|
|
||||||
|
$res1 = $this->queryIgnoreResult('system.multicall', $args1);
|
||||||
|
$res2 = $this->queryIgnoreResult('system.multicall', $args2);
|
||||||
|
return ($res1 && $res2);
|
||||||
|
}
|
||||||
|
// If the method is not a multicall, just stop
|
||||||
|
else {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
$ok = $this->sendRequest($request);
|
||||||
|
if (!$ok) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCB($timeout = 2000) { // timeout 2 ms
|
||||||
|
if (!$this->socket || $this->protocol == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($this->protocol == 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$something_received = count($this->cb_message)>0;
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
|
||||||
|
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data)
|
||||||
|
// (assignment in arguments is forbidden since php 5.1.1)
|
||||||
|
$read = array($this->socket);
|
||||||
|
$write = NULL;
|
||||||
|
$except = NULL;
|
||||||
|
$nb = @stream_select($read, $write, $except, 0, $timeout);
|
||||||
|
// workaround for stream_select bug with amd64
|
||||||
|
if ($nb !== false)
|
||||||
|
$nb = count($read);
|
||||||
|
|
||||||
|
while ($nb !== false && $nb > 0) {
|
||||||
|
$timeout = 0; // we don't want to wait for the full time again, just flush the available data
|
||||||
|
|
||||||
|
$size = 0;
|
||||||
|
$recvhandle = 0;
|
||||||
|
// Get result
|
||||||
|
$contents = fread($this->socket, 8);
|
||||||
|
if (strlen($contents) == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$array_result = big_endian_unpack('Vsize/Vhandle', $contents);
|
||||||
|
$size = $array_result['size'];
|
||||||
|
$recvhandle = $array_result['handle'];
|
||||||
|
|
||||||
|
if ($recvhandle == 0 || $size == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($size > 4096*1024) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
while ($contents_length < $size) {
|
||||||
|
$contents .= fread($this->socket, $size-$contents_length);
|
||||||
|
$contents_length = strlen($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($recvhandle & 0x80000000) == 0) {
|
||||||
|
// this is a callback. handle= $recvhandle, xml-rpc= $contents
|
||||||
|
//echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
|
||||||
|
$new_cb_message = new IXR_Message($contents);
|
||||||
|
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
|
||||||
|
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
|
||||||
|
}
|
||||||
|
$something_received = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (assignment in arguments is forbidden since php 5.1.1)
|
||||||
|
$read = array($this->socket);
|
||||||
|
$write = NULL;
|
||||||
|
$except = NULL;
|
||||||
|
$nb = @stream_select($read, $write, $except, 0, $timeout);
|
||||||
|
// workaround for stream_select bug with amd64
|
||||||
|
if ($nb !== false)
|
||||||
|
$nb = count($read);
|
||||||
|
}
|
||||||
|
return $something_received;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResponse() {
|
||||||
|
// methodResponses can only have one param - return that
|
||||||
|
return $this->message->params[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCBResponses() {
|
||||||
|
// (look at the end of basic.php for an example)
|
||||||
|
$messages = $this->cb_message;
|
||||||
|
$this->cb_message = array();
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isError() {
|
||||||
|
return is_object($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetError() {
|
||||||
|
$this->error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorCode() {
|
||||||
|
if ($this->isError())
|
||||||
|
return $this->error->code;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage() {
|
||||||
|
if ($this->isError())
|
||||||
|
return $this->error->message;
|
||||||
|
else
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx {
|
||||||
|
public $calls = array();
|
||||||
|
|
||||||
|
function addCall($methodName, $args) {
|
||||||
|
$struct = array('methodName' => $methodName, 'params' => $args);
|
||||||
|
$this->calls[] = $struct;
|
||||||
|
|
||||||
|
return (count($this->calls) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function multiquery($ignoreResult = false) {
|
||||||
|
// Prepare multicall, then call the parent::query() (or queryIgnoreResult) method
|
||||||
|
if ($ignoreResult) {
|
||||||
|
$result = parent::queryIgnoreResult('system.multicall', $this->calls);
|
||||||
|
} else {
|
||||||
|
$result = parent::query('system.multicall', $this->calls);
|
||||||
|
}
|
||||||
|
$this->calls = array(); // reset for next calls
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following code is a workaround for php's unpack function which
|
||||||
|
* does not have the capability of unpacking double precision floats
|
||||||
|
* that were packed in the opposite byte order of the current machine.
|
||||||
|
*/
|
||||||
|
function big_endian_unpack($format, $data) {
|
||||||
|
$ar = unpack($format, $data);
|
||||||
|
$vals = array_values($ar);
|
||||||
|
$f = explode('/', $format);
|
||||||
|
$i = 0;
|
||||||
|
foreach ($f as $f_k => $f_v) {
|
||||||
|
$repeater = intval(substr($f_v, 1));
|
||||||
|
if ($repeater == 0)
|
||||||
|
$repeater = 1;
|
||||||
|
if ($f_v{1} == '*') {
|
||||||
|
$repeater = count($ar) - $i;
|
||||||
|
}
|
||||||
|
if ($f_v{0} != 'd') {
|
||||||
|
$i += $repeater;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$j = $i + $repeater;
|
||||||
|
for ($a = $i; $a < $j; ++$a) {
|
||||||
|
$p = pack('d', $vals[$i]);
|
||||||
|
$p = strrev($p);
|
||||||
|
list($vals[$i]) = array_values(unpack('d1d', $p));
|
||||||
|
++$i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$a = 0;
|
||||||
|
foreach ($ar as $ar_k => $ar_v) {
|
||||||
|
$ar[$ar_k] = $vals[$a];
|
||||||
|
++$a;
|
||||||
|
}
|
||||||
|
return $ar;
|
||||||
|
}
|
||||||
|
?>
|
855
application/core/PhpRemote/GbxRemote.inc.php
Normal file
855
application/core/PhpRemote/GbxRemote.inc.php
Normal file
@ -0,0 +1,855 @@
|
|||||||
|
<?php
|
||||||
|
/* vim: set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
|
||||||
|
|
||||||
|
/*
|
||||||
|
IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002
|
||||||
|
Version 1.61 - Simon Willison, 11th July 2003 (htmlentities -> htmlspecialchars)
|
||||||
|
Site: http://scripts.incutio.com/xmlrpc/
|
||||||
|
Manual: http://scripts.incutio.com/xmlrpc/manual.php
|
||||||
|
Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
|
||||||
|
Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php
|
||||||
|
|
||||||
|
Modified to support protocol 'GbxRemote 2' ('GbxRemote 1')
|
||||||
|
This version is for LittleEndian (e.g. Intel PC) machines. For BegEndian
|
||||||
|
machines use GbxRemote.bem.php as GbxRemote.inc.php instead.
|
||||||
|
|
||||||
|
Release 2007-09-22 - Slig:
|
||||||
|
Modified to support >256KB received data (and now >2MB data produce a specific error message)
|
||||||
|
Modified readCB() to wait the initial timeout only before first read packet
|
||||||
|
Modified readCB() to return true if there is data to get with getCBResponses()
|
||||||
|
Modified to support amd64 (for $recvhandle)
|
||||||
|
Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1
|
||||||
|
Added IXR_Client_Gbx::bytes_sent & bytes_received counters
|
||||||
|
Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select)
|
||||||
|
Workaround for stream_select return value bug with amd64
|
||||||
|
|
||||||
|
Release 2008-01-20 - Slig / Xymph / Assembler Maniac:
|
||||||
|
Workaround for fread delay bug in some cases
|
||||||
|
Added IXR_Client_Gbx::resetError() method (by Xymph)
|
||||||
|
Some comments and strings code cleanup (by Xymph)
|
||||||
|
Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache)
|
||||||
|
Added a default timeout value to IXR_Client_Gbx::readCB($timeout)
|
||||||
|
Changed calls with timeout on a stream to use microseconds instead of seconds (by AM)
|
||||||
|
Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM)
|
||||||
|
|
||||||
|
Release 2008-02-05 - Slig:
|
||||||
|
Changed some socket read/write timeouts back to seconds to avoid 'transport error'
|
||||||
|
Changed max data received from 2MB to 4MB
|
||||||
|
|
||||||
|
Release 2008-05-20 - Xymph:
|
||||||
|
Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies
|
||||||
|
Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError()
|
||||||
|
Tweaked some 'transport error' messages
|
||||||
|
|
||||||
|
Release 2009-04-08 - Gou1:
|
||||||
|
Added method IXR_Client_Gbx::queryIgnoreResult()
|
||||||
|
Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult()
|
||||||
|
IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors
|
||||||
|
If larger than 512KB and method is system.multicall, try to divide the request into
|
||||||
|
two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls
|
||||||
|
|
||||||
|
Release 2009-06-03 - Xymph:
|
||||||
|
Suppress possible repetitive CRT warning at stream_select
|
||||||
|
|
||||||
|
Release 2011-04-09 - Xymph / La beuze:
|
||||||
|
Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp()
|
||||||
|
|
||||||
|
Release 2011-05-22 - Xymph:
|
||||||
|
Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult()
|
||||||
|
Updated status codes and messages for transport/endian errors
|
||||||
|
Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage()
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('LF')) {
|
||||||
|
define('LF', "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
class IXR_Value {
|
||||||
|
public $data;
|
||||||
|
public $type;
|
||||||
|
|
||||||
|
function IXR_Value ($data, $type = false) {
|
||||||
|
$this->data = $data;
|
||||||
|
if (!$type) {
|
||||||
|
$type = $this->calculateType();
|
||||||
|
}
|
||||||
|
$this->type = $type;
|
||||||
|
if ($type == 'struct') {
|
||||||
|
// Turn all the values in the array into new IXR_Value objects
|
||||||
|
foreach ($this->data as $key => $value) {
|
||||||
|
$this->data[$key] = new IXR_Value($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type == 'array') {
|
||||||
|
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
|
||||||
|
$this->data[$i] = new IXR_Value($this->data[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateType() {
|
||||||
|
if ($this->data === true || $this->data === false) {
|
||||||
|
return 'boolean';
|
||||||
|
}
|
||||||
|
if (is_integer($this->data)) {
|
||||||
|
return 'int';
|
||||||
|
}
|
||||||
|
if (is_double($this->data)) {
|
||||||
|
return 'double';
|
||||||
|
}
|
||||||
|
// Deal with IXR object types base64 and date
|
||||||
|
if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
|
||||||
|
return 'date';
|
||||||
|
}
|
||||||
|
if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
|
||||||
|
return 'base64';
|
||||||
|
}
|
||||||
|
// If it is a normal PHP object convert it into a struct
|
||||||
|
if (is_object($this->data)) {
|
||||||
|
$this->data = get_object_vars($this->data);
|
||||||
|
return 'struct';
|
||||||
|
}
|
||||||
|
if (!is_array($this->data)) {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
// We have an array - is it an array or a struct?
|
||||||
|
if ($this->isStruct($this->data)) {
|
||||||
|
return 'struct';
|
||||||
|
} else {
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
// Return XML for this value
|
||||||
|
switch ($this->type) {
|
||||||
|
case 'boolean':
|
||||||
|
return '<boolean>' . ($this->data ? '1' : '0') . '</boolean>';
|
||||||
|
break;
|
||||||
|
case 'int':
|
||||||
|
return '<int>' . $this->data . '</int>';
|
||||||
|
break;
|
||||||
|
case 'double':
|
||||||
|
return '<double>' . $this->data . '</double>';
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
return '<string>' . htmlspecialchars($this->data) . '</string>';
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
$return = '<array><data>' . LF;
|
||||||
|
foreach ($this->data as $item) {
|
||||||
|
$return .= ' <value>' . $item->getXml() . '</value>' . LF;
|
||||||
|
}
|
||||||
|
$return .= '</data></array>';
|
||||||
|
return $return;
|
||||||
|
break;
|
||||||
|
case 'struct':
|
||||||
|
$return = '<struct>' . LF;
|
||||||
|
foreach ($this->data as $name => $value) {
|
||||||
|
$return .= ' <member><name>' . $name . '</name><value>';
|
||||||
|
$return .= $value->getXml() . '</value></member>' . LF;
|
||||||
|
}
|
||||||
|
$return .= '</struct>';
|
||||||
|
return $return;
|
||||||
|
break;
|
||||||
|
case 'date':
|
||||||
|
case 'base64':
|
||||||
|
return $this->data->getXml();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStruct($array) {
|
||||||
|
// Nasty function to check if an array is a struct or not
|
||||||
|
$expected = 0;
|
||||||
|
foreach ($array as $key => $value) {
|
||||||
|
if ((string)$key != (string)$expected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$expected++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Message {
|
||||||
|
public $message;
|
||||||
|
public $messageType; // methodCall / methodResponse / fault
|
||||||
|
public $faultCode;
|
||||||
|
public $faultString;
|
||||||
|
public $methodName;
|
||||||
|
public $params;
|
||||||
|
// Current variable stacks
|
||||||
|
protected $_arraystructs = array(); // Stack to keep track of the current array/struct
|
||||||
|
protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array
|
||||||
|
protected $_currentStructName = array(); // A stack as well
|
||||||
|
protected $_param;
|
||||||
|
protected $_value;
|
||||||
|
protected $_currentTag;
|
||||||
|
protected $_currentTagContents;
|
||||||
|
// The XML parser
|
||||||
|
protected $_parser;
|
||||||
|
|
||||||
|
function IXR_Message ($message) {
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse() {
|
||||||
|
// first remove the XML declaration
|
||||||
|
$this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
|
||||||
|
if (trim($this->message) == '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->_parser = xml_parser_create();
|
||||||
|
// Set XML parser to take the case of tags into account
|
||||||
|
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
|
||||||
|
// Set XML parser callback functions
|
||||||
|
xml_set_object($this->_parser, $this);
|
||||||
|
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
|
||||||
|
xml_set_character_data_handler($this->_parser, 'cdata');
|
||||||
|
if (!xml_parse($this->_parser, $this->message)) {
|
||||||
|
/* die(sprintf('GbxRemote XML error: %s at line %d',
|
||||||
|
xml_error_string(xml_get_error_code($this->_parser)),
|
||||||
|
xml_get_current_line_number($this->_parser))); */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
xml_parser_free($this->_parser);
|
||||||
|
// Grab the error messages, if any
|
||||||
|
if ($this->messageType == 'fault') {
|
||||||
|
$this->faultCode = $this->params[0]['faultCode'];
|
||||||
|
$this->faultString = $this->params[0]['faultString'];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tag_open($parser, $tag, $attr) {
|
||||||
|
$this->currentTag = $tag;
|
||||||
|
switch ($tag) {
|
||||||
|
case 'methodCall':
|
||||||
|
case 'methodResponse':
|
||||||
|
case 'fault':
|
||||||
|
$this->messageType = $tag;
|
||||||
|
break;
|
||||||
|
// Deal with stacks of arrays and structs
|
||||||
|
case 'data': // data is to all intents and purposes more interesting than array
|
||||||
|
$this->_arraystructstypes[] = 'array';
|
||||||
|
$this->_arraystructs[] = array();
|
||||||
|
break;
|
||||||
|
case 'struct':
|
||||||
|
$this->_arraystructstypes[] = 'struct';
|
||||||
|
$this->_arraystructs[] = array();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cdata($parser, $cdata) {
|
||||||
|
$this->_currentTagContents .= $cdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tag_close($parser, $tag) {
|
||||||
|
$valueFlag = false;
|
||||||
|
switch ($tag) {
|
||||||
|
case 'int':
|
||||||
|
case 'i4':
|
||||||
|
$value = (int)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'double':
|
||||||
|
$value = (double)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
$value = (string)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'dateTime.iso8601':
|
||||||
|
$value = new IXR_Date(trim($this->_currentTagContents));
|
||||||
|
// $value = $iso->getTimestamp();
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'value':
|
||||||
|
// If no type is indicated, the type is string
|
||||||
|
if (trim($this->_currentTagContents) != '') {
|
||||||
|
$value = (string)$this->_currentTagContents;
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
$value = (boolean)trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'base64':
|
||||||
|
$value = base64_decode($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
// Deal with stacks of arrays and structs
|
||||||
|
case 'data':
|
||||||
|
case 'struct':
|
||||||
|
$value = array_pop($this->_arraystructs);
|
||||||
|
array_pop($this->_arraystructstypes);
|
||||||
|
$valueFlag = true;
|
||||||
|
break;
|
||||||
|
case 'member':
|
||||||
|
array_pop($this->_currentStructName);
|
||||||
|
break;
|
||||||
|
case 'name':
|
||||||
|
$this->_currentStructName[] = trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
break;
|
||||||
|
case 'methodName':
|
||||||
|
$this->methodName = trim($this->_currentTagContents);
|
||||||
|
$this->_currentTagContents = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($valueFlag) {
|
||||||
|
/*
|
||||||
|
if (!is_array($value) && !is_object($value)) {
|
||||||
|
$value = trim($value);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (count($this->_arraystructs) > 0) {
|
||||||
|
// Add value to struct or array
|
||||||
|
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
|
||||||
|
// Add to struct
|
||||||
|
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
|
||||||
|
} else {
|
||||||
|
// Add to array
|
||||||
|
$this->_arraystructs[count($this->_arraystructs)-1][] = $value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just add as a paramater
|
||||||
|
$this->params[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Request {
|
||||||
|
public $method;
|
||||||
|
public $args;
|
||||||
|
public $xml;
|
||||||
|
|
||||||
|
function IXR_Request($method, $args) {
|
||||||
|
$this->method = $method;
|
||||||
|
$this->args = $args;
|
||||||
|
$this->xml = '<?xml version="1.0" encoding="utf-8" ?><methodCall><methodName>' . $this->method . '</methodName><params>';
|
||||||
|
foreach ($this->args as $arg) {
|
||||||
|
$this->xml .= '<param><value>';
|
||||||
|
$v = new IXR_Value($arg);
|
||||||
|
$this->xml .= $v->getXml();
|
||||||
|
$this->xml .= '</value></param>' . LF;
|
||||||
|
}
|
||||||
|
$this->xml .= '</params></methodCall>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLength() {
|
||||||
|
return strlen($this->xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
return $this->xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Error {
|
||||||
|
public $code;
|
||||||
|
public $message;
|
||||||
|
|
||||||
|
function IXR_Error($code, $message) {
|
||||||
|
$this->code = $code;
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
$xml = <<<EOD
|
||||||
|
<methodResponse>
|
||||||
|
<fault>
|
||||||
|
<value>
|
||||||
|
<struct>
|
||||||
|
<member>
|
||||||
|
<name>faultCode</name>
|
||||||
|
<value><int>{$this->code}</int></value>
|
||||||
|
</member>
|
||||||
|
<member>
|
||||||
|
<name>faultString</name>
|
||||||
|
<value><string>{$this->message}</string></value>
|
||||||
|
</member>
|
||||||
|
</struct>
|
||||||
|
</value>
|
||||||
|
</fault>
|
||||||
|
</methodResponse>
|
||||||
|
EOD;
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Date {
|
||||||
|
public $year;
|
||||||
|
public $month;
|
||||||
|
public $day;
|
||||||
|
public $hour;
|
||||||
|
public $minute;
|
||||||
|
public $second;
|
||||||
|
|
||||||
|
function IXR_Date($time) {
|
||||||
|
// $time can be a PHP timestamp or an ISO one
|
||||||
|
if (is_numeric($time)) {
|
||||||
|
$this->parseTimestamp($time);
|
||||||
|
} else {
|
||||||
|
$this->parseIso($time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTimestamp($timestamp) {
|
||||||
|
$this->year = date('Y', $timestamp);
|
||||||
|
$this->month = date('Y', $timestamp);
|
||||||
|
$this->day = date('Y', $timestamp);
|
||||||
|
$this->hour = date('H', $timestamp);
|
||||||
|
$this->minute = date('i', $timestamp);
|
||||||
|
$this->second = date('s', $timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIso($iso) {
|
||||||
|
$this->year = substr($iso, 0, 4);
|
||||||
|
$this->month = substr($iso, 4, 2);
|
||||||
|
$this->day = substr($iso, 6, 2);
|
||||||
|
$this->hour = substr($iso, 9, 2);
|
||||||
|
$this->minute = substr($iso, 12, 2);
|
||||||
|
$this->second = substr($iso, 15, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIso() {
|
||||||
|
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimestamp() {
|
||||||
|
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_Base64 {
|
||||||
|
public $data;
|
||||||
|
|
||||||
|
function IXR_Base64($data) {
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXml() {
|
||||||
|
return '<base64>'.base64_encode($this->data).'</base64>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// Nadeo modifications //
|
||||||
|
// (many thanks to slig for adding callback support) //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
class IXR_Client_Gbx {
|
||||||
|
public $socket;
|
||||||
|
public $message = false;
|
||||||
|
public $cb_message = array();
|
||||||
|
public $reqhandle;
|
||||||
|
public $protocol = 0;
|
||||||
|
// Storage place for an error message
|
||||||
|
public $error = false;
|
||||||
|
|
||||||
|
function bigEndianTest() {
|
||||||
|
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
|
||||||
|
if ($endiantest != 1) {
|
||||||
|
echo "Machine reports itself as BigEndian, float handling must be altered\r\n";
|
||||||
|
echo "Overwrite GbxRemote.inc.php with GbxRemote.bem.php\r\n";
|
||||||
|
die('App Terminated');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} // bigEndianTest
|
||||||
|
|
||||||
|
function IXR_Client_Gbx() {
|
||||||
|
$this->socket = false;
|
||||||
|
$this->reqhandle = 0x80000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InitWithIp($ip, $port, $timeout = null) {
|
||||||
|
|
||||||
|
if (!$this->bigEndianTest()) {
|
||||||
|
$this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open connection, with timeout if specified
|
||||||
|
if (!isset($timeout)) {
|
||||||
|
$this->socket = @fsockopen($ip, $port, $errno, $errstr);
|
||||||
|
} else {
|
||||||
|
$init_time = microtime(true);
|
||||||
|
$init_timeout = 5; // retry every 5s
|
||||||
|
while (true) {
|
||||||
|
$this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout);
|
||||||
|
if ($this->socket || (microtime(true) - $init_time >= $timeout))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$this->socket) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// handshake
|
||||||
|
$array_result = unpack('Vsize', fread($this->socket, 4));
|
||||||
|
$size = $array_result['size'];
|
||||||
|
if ($size > 64) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$handshake = fread($this->socket, $size);
|
||||||
|
if ($handshake == 'GBXRemote 1') {
|
||||||
|
$this->protocol = 1;
|
||||||
|
} else if ($handshake == 'GBXRemote 2') {
|
||||||
|
$this->protocol = 2;
|
||||||
|
} else {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Init($port) {
|
||||||
|
return $this->InitWithIp('localhost', $port);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Terminate() {
|
||||||
|
if ($this->socket) {
|
||||||
|
fclose($this->socket);
|
||||||
|
$this->socket = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sendRequest(IXR_Request $request) {
|
||||||
|
$xml = $request->getXml();
|
||||||
|
|
||||||
|
@stream_set_timeout($this->socket, 20); // timeout 20s (to write the request)
|
||||||
|
// send request
|
||||||
|
$this->reqhandle++;
|
||||||
|
if ($this->protocol == 1) {
|
||||||
|
$bytes = pack('Va*', strlen($xml), $xml);
|
||||||
|
} else {
|
||||||
|
$bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes_to_write = strlen($bytes);
|
||||||
|
while ($bytes_to_write > 0) {
|
||||||
|
$r = @fwrite($this->socket, $bytes);
|
||||||
|
if ($r === false || $r == 0) {
|
||||||
|
// connection interrupted
|
||||||
|
return false; // or die?
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes_to_write -= $r;
|
||||||
|
if ($bytes_to_write == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
$bytes = substr($bytes, $r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getResult() {
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
do {
|
||||||
|
$size = 0;
|
||||||
|
$recvhandle = 0;
|
||||||
|
@stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header)
|
||||||
|
// Get result
|
||||||
|
if ($this->protocol == 1) {
|
||||||
|
$contents = fread($this->socket, 4);
|
||||||
|
if (strlen($contents) == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - cannot read size');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$array_result = unpack('Vsize', $contents);
|
||||||
|
$size = $array_result['size'];
|
||||||
|
$recvhandle = $this->reqhandle;
|
||||||
|
} else {
|
||||||
|
$contents = fread($this->socket, 8);
|
||||||
|
if (strlen($contents) == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$array_result = unpack('Vsize/Vhandle', $contents);
|
||||||
|
$size = $array_result['size'];
|
||||||
|
$recvhandle = $array_result['handle'];
|
||||||
|
// -- amd64 support --
|
||||||
|
$bits = sprintf('%b', $recvhandle);
|
||||||
|
if (strlen($bits) == 64) {
|
||||||
|
$recvhandle = bindec(substr($bits, 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($recvhandle == 0 || $size == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($size > 4096*1024) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end)
|
||||||
|
while ($contents_length < $size) {
|
||||||
|
$contents .= fread($this->socket, $size-$contents_length);
|
||||||
|
$contents_length = strlen($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($recvhandle & 0x80000000) == 0) {
|
||||||
|
// this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
|
||||||
|
// just add it to the message list for the user to read
|
||||||
|
$new_cb_message = new IXR_Message($contents);
|
||||||
|
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
|
||||||
|
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while ((int)$recvhandle != (int)$this->reqhandle);
|
||||||
|
|
||||||
|
$this->message = new IXR_Message($contents);
|
||||||
|
if (!$this->message->parse()) {
|
||||||
|
// XML error
|
||||||
|
$this->error = new IXR_Error(-32700, 'parse error. not well formed');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Is the message a fault?
|
||||||
|
if ($this->message->messageType == 'fault') {
|
||||||
|
$this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Message must be OK
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function query() {
|
||||||
|
$args = func_get_args();
|
||||||
|
$method = array_shift($args);
|
||||||
|
|
||||||
|
if (!$this->socket || $this->protocol == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = new IXR_Request($method, $args);
|
||||||
|
|
||||||
|
// Check if request is larger than 512 Kbytes
|
||||||
|
if (($size = $request->getLength()) > 512*1024-8) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
$ok = $this->sendRequest($request);
|
||||||
|
if (!$ok) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get result
|
||||||
|
return $this->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-blocking query method: doesn't read the response
|
||||||
|
function queryIgnoreResult() {
|
||||||
|
$args = func_get_args();
|
||||||
|
$method = array_shift($args);
|
||||||
|
|
||||||
|
if (!$this->socket || $this->protocol == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = new IXR_Request($method, $args);
|
||||||
|
|
||||||
|
// Check if the request is greater than 512 Kbytes to avoid errors
|
||||||
|
// If the method is system.multicall, make two calls (possibly recursively)
|
||||||
|
if (($size = $request->getLength()) > 512*1024-8) {
|
||||||
|
if ($method = 'system.multicall' && isset($args[0])) {
|
||||||
|
$count = count($args[0]);
|
||||||
|
// If count is 1, query cannot be reduced
|
||||||
|
if ($count < 2) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$length = floor($count/2);
|
||||||
|
$args1 = array_slice($args[0], 0, $length);
|
||||||
|
$args2 = array_slice($args[0], $length, ($count-$length));
|
||||||
|
|
||||||
|
$res1 = $this->queryIgnoreResult('system.multicall', $args1);
|
||||||
|
$res2 = $this->queryIgnoreResult('system.multicall', $args2);
|
||||||
|
return ($res1 && $res2);
|
||||||
|
}
|
||||||
|
// If the method is not a multicall, just stop
|
||||||
|
else {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
$ok = $this->sendRequest($request);
|
||||||
|
if (!$ok) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCB($timeout = 2000) { // timeout 2 ms
|
||||||
|
if (!$this->socket || $this->protocol == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($this->protocol == 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$something_received = count($this->cb_message)>0;
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
|
||||||
|
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data)
|
||||||
|
// (assignment in arguments is forbidden since php 5.1.1)
|
||||||
|
$read = array($this->socket);
|
||||||
|
$write = NULL;
|
||||||
|
$except = NULL;
|
||||||
|
$nb = @stream_select($read, $write, $except, 0, $timeout);
|
||||||
|
// workaround for stream_select bug with amd64
|
||||||
|
if ($nb !== false)
|
||||||
|
$nb = count($read);
|
||||||
|
|
||||||
|
while ($nb !== false && $nb > 0) {
|
||||||
|
$timeout = 0; // we don't want to wait for the full time again, just flush the available data
|
||||||
|
|
||||||
|
$size = 0;
|
||||||
|
$recvhandle = 0;
|
||||||
|
// Get result
|
||||||
|
$contents = fread($this->socket, 8);
|
||||||
|
if (strlen($contents) == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$array_result = unpack('Vsize/Vhandle', $contents);
|
||||||
|
$size = $array_result['size'];
|
||||||
|
$recvhandle = $array_result['handle'];
|
||||||
|
|
||||||
|
if ($recvhandle == 0 || $size == 0) {
|
||||||
|
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($size > 4096*1024) {
|
||||||
|
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = '';
|
||||||
|
$contents_length = 0;
|
||||||
|
while ($contents_length < $size) {
|
||||||
|
$contents .= fread($this->socket, $size-$contents_length);
|
||||||
|
$contents_length = strlen($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($recvhandle & 0x80000000) == 0) {
|
||||||
|
// this is a callback. handle= $recvhandle, xml-rpc= $contents
|
||||||
|
//echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
|
||||||
|
$new_cb_message = new IXR_Message($contents);
|
||||||
|
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
|
||||||
|
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
|
||||||
|
}
|
||||||
|
$something_received = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (assignment in arguments is forbidden since php 5.1.1)
|
||||||
|
$read = array($this->socket);
|
||||||
|
$write = NULL;
|
||||||
|
$except = NULL;
|
||||||
|
$nb = @stream_select($read, $write, $except, 0, $timeout);
|
||||||
|
// workaround for stream_select bug with amd64
|
||||||
|
if ($nb !== false)
|
||||||
|
$nb = count($read);
|
||||||
|
}
|
||||||
|
return $something_received;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResponse() {
|
||||||
|
// methodResponses can only have one param - return that
|
||||||
|
return $this->message->params[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCBResponses() {
|
||||||
|
// (look at the end of basic.php for an example)
|
||||||
|
$messages = $this->cb_message;
|
||||||
|
$this->cb_message = array();
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isError() {
|
||||||
|
return is_object($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetError() {
|
||||||
|
$this->error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorCode() {
|
||||||
|
if ($this->isError())
|
||||||
|
return $this->error->code;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage() {
|
||||||
|
if ($this->isError())
|
||||||
|
return $this->error->message;
|
||||||
|
else
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx {
|
||||||
|
public $calls = array();
|
||||||
|
|
||||||
|
function addCall($methodName, $args) {
|
||||||
|
$struct = array('methodName' => $methodName, 'params' => $args);
|
||||||
|
$this->calls[] = $struct;
|
||||||
|
|
||||||
|
return (count($this->calls) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function multiquery($ignoreResult = false) {
|
||||||
|
// Prepare multicall, then call the parent::query() (or queryIgnoreResult) method
|
||||||
|
if ($ignoreResult) {
|
||||||
|
$result = parent::queryIgnoreResult('system.multicall', $this->calls);
|
||||||
|
} else {
|
||||||
|
$result = parent::query('system.multicall', $this->calls);
|
||||||
|
}
|
||||||
|
$this->calls = array(); // reset for next calls
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
103
application/core/authentication.iControl.php
Normal file
103
application/core/authentication.iControl.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class handling authentication levels
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Authentication {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
public $RIGHTS_LEVELS = array(-1 => 'none', 0 => 'superadmin', 1 => 'admin', 2 => 'operator', 3 => 'all');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct authentication manager
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('authentication.iControl.xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the player has enough rights
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @param string $defaultRight
|
||||||
|
* @param string $neededRight
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function checkRight($login, $neededRight) {
|
||||||
|
$right = $this->getRights($login);
|
||||||
|
return $this->compareRights($right, $neededRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare if the rights are enough
|
||||||
|
*
|
||||||
|
* @param string $hasRight
|
||||||
|
* @param string $neededRight
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function compareRights($hasRight, $neededRight) {
|
||||||
|
if (!in_array($hasRight, $this->RIGHTS_LEVELS) || !in_array($neededRight, $this->RIGHTS_LEVELS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$hasLevel = array_search($hasRight, $this->RIGHTS_LEVELS);
|
||||||
|
$neededLevel = array_search($neededRight, $this->RIGHTS_LEVELS);
|
||||||
|
if ($hasLevel > $neededLevel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get rights of the given login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @param string $defaultRights
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRights($login, $defaultRight = 'all') {
|
||||||
|
$groups = $this->config->xpath('//login[text()="' . $login . '"]/..');
|
||||||
|
if (empty($groups)) return $defaultRight;
|
||||||
|
$right = $defaultRight;
|
||||||
|
$rightLevel = array_search($right, $this->RIGHTS_LEVELS);
|
||||||
|
foreach ($groups as $group) {
|
||||||
|
$level = array_search($group->getName(), $this->RIGHTS_LEVELS);
|
||||||
|
if ($level === false) continue;
|
||||||
|
if ($level < $rightLevel || $rightLevel === false) {
|
||||||
|
$right = $group->getName();
|
||||||
|
$rightLevel = $level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an error message to the login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
*/
|
||||||
|
public function sendNotAllowed($login) {
|
||||||
|
if (!$this->iControl->chat->sendError('You do not have the required rights to perform this command!', $login)) {
|
||||||
|
trigger_error("Couldn't send forbidden message to login '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
191
application/core/callbacks.iControl.php
Normal file
191
application/core/callbacks.iControl.php
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for handling server callbacks
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Callbacks {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
// iControl callbacks
|
||||||
|
const CB_IC_1_SECOND = 'iControl.1Second';
|
||||||
|
const CB_IC_5_SECOND = 'iControl.5Second';
|
||||||
|
const CB_IC_1_MINUTE = 'iControl.1Minute';
|
||||||
|
const CB_IC_3_MINUTE = 'iControl.3Minute';
|
||||||
|
const CB_IC_ONINIT = 'iControl.OnInit';
|
||||||
|
const CB_IC_CLIENTUPDATED = 'iControl.ClientUpdated';
|
||||||
|
const CB_IC_BEGINMAP = 'iControl.BeginMap';
|
||||||
|
const CB_IC_ENDMAP = 'iControl.EndMap';
|
||||||
|
// ManiaPlanet callbacks
|
||||||
|
const CB_MP_SERVERSTART = 'ManiaPlanet.ServerStart';
|
||||||
|
const CB_MP_SERVERSTOP = 'ManiaPlanet.ServerStop';
|
||||||
|
const CB_MP_BEGINMAP = 'ManiaPlanet.BeginMap';
|
||||||
|
const CB_MP_BEGINMATCH = 'ManiaPlanet.BeginMatch';
|
||||||
|
const CB_MP_ENDMATCH = 'ManiaPlanet.EndMatch';
|
||||||
|
const CB_MP_ENDMAP = 'ManiaPlanet.EndMap';
|
||||||
|
const CB_MP_MAPLISTMODIFIED = 'ManiaPlanet.MapListModified';
|
||||||
|
const CB_MP_ECHO = 'ManiaPlanet.Echo';
|
||||||
|
const CB_MP_BILLUPDATED = 'ManiaPlanet.BillUpdated';
|
||||||
|
const CB_MP_PLAYERCHAT = 'ManiaPlanet.PlayerChat';
|
||||||
|
const CB_MP_PLAYERCONNECT = 'ManiaPlanet.PlayerConnect';
|
||||||
|
const CB_MP_PLAYERDISCONNECT = 'ManiaPlanet.PlayerDisconnect';
|
||||||
|
const CB_MP_PLAYERMANIALINKPAGEANSWER = 'ManiaPlanet.PlayerManialinkPageAnswer';
|
||||||
|
const CB_MP_PLAYERINFOCHANGED = 'ManiaPlanet.PlayerInfoChanged';
|
||||||
|
const CB_MP_PLAYERALLIESCHANGED = 'ManiaPlanet.PlayerAlliesChanged';
|
||||||
|
const CB_MP_VOTEUPDATED = 'ManiaPlanet.VoteUpdated';
|
||||||
|
const CB_MP_STATUSCHANGED = 'ManiaPlanet.StatusChanged';
|
||||||
|
const CB_MP_MODESCRIPTCALLBACK = 'ManiaPlanet.ModeScriptCallback';
|
||||||
|
const CB_MP_MODESCRIPTCALLBACKARRAY = 'ManiaPlanet.ModeScriptCallbackArray';
|
||||||
|
const CB_MP_TUNNELDATARECEIVED = 'ManiaPlanet.TunnelDataReceived';
|
||||||
|
// TrackMania callbacks
|
||||||
|
const CB_TM_PLAYERCHECKPOINT = 'TrackMania.PlayerCheckpoint';
|
||||||
|
const CB_TM_PLAYERFINISH = 'TrackMania.PlayerFinish';
|
||||||
|
const CB_TM_PLAYERINCOHERENCE = 'TrackMania.PlayerIncoherence';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $callbackHandlers = array();
|
||||||
|
|
||||||
|
private $last1Second = -1;
|
||||||
|
|
||||||
|
private $last5Second = -1;
|
||||||
|
|
||||||
|
private $last1Minute = -1;
|
||||||
|
|
||||||
|
private $last3Minute = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct callbacks handler
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Init values
|
||||||
|
$this->last1Second = time();
|
||||||
|
$this->last5Second = time();
|
||||||
|
$this->last1Minute = time();
|
||||||
|
$this->last3Minute = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform OnInit callback
|
||||||
|
*/
|
||||||
|
public function onInit() {
|
||||||
|
// On init callback
|
||||||
|
$this->triggerCallback(self::CB_IC_ONINIT, array(self::CB_IC_ONINIT));
|
||||||
|
|
||||||
|
// Simulate begin map
|
||||||
|
$map = $this->iControl->server->getMap();
|
||||||
|
if ($map) {
|
||||||
|
$this->triggerCallback(self::CB_IC_BEGINMAP, array(self::CB_IC_BEGINMAP, array($map)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the given array of callbacks
|
||||||
|
*/
|
||||||
|
public function handleCallbacks() {
|
||||||
|
// Perform iControl callbacks
|
||||||
|
if ($this->last1Second <= time() - 1) {
|
||||||
|
$this->last1Second = time();
|
||||||
|
|
||||||
|
// 1 second
|
||||||
|
$this->triggerCallback(self::CB_IC_1_SECOND, array(self::CB_IC_1_SECOND));
|
||||||
|
|
||||||
|
if ($this->last5Second <= time() - 5) {
|
||||||
|
$this->last5Second = time();
|
||||||
|
|
||||||
|
// 5 second
|
||||||
|
$this->triggerCallback(self::CB_IC_5_SECOND, array(self::CB_IC_5_SECOND));
|
||||||
|
|
||||||
|
if ($this->last1Minute <= time() - 60) {
|
||||||
|
$this->last1Minute = time();
|
||||||
|
|
||||||
|
// 1 minute
|
||||||
|
$this->triggerCallback(self::CB_IC_1_MINUTE, array(self::CB_IC_1_MINUTE));
|
||||||
|
|
||||||
|
if ($this->last3Minute <= time() - 180) {
|
||||||
|
$this->last3Minute = time();
|
||||||
|
|
||||||
|
// 3 minute
|
||||||
|
$this->triggerCallback(self::CB_IC_3_MINUTE, array(self::CB_IC_3_MINUTE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get server callbacks
|
||||||
|
if (!$this->iControl->client) return;
|
||||||
|
$this->iControl->client->resetError();
|
||||||
|
$this->iControl->client->readCB();
|
||||||
|
$callbacks = $this->iControl->client->getCBResponses();
|
||||||
|
if (!is_array($callbacks) || $this->iControl->client->isError()) {
|
||||||
|
trigger_error("Error reading server callbacks. " . $this->iControl->getClientErrorText());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle callbacks
|
||||||
|
foreach ($callbacks as $index => $callback) {
|
||||||
|
$callbackName = $callback[0];
|
||||||
|
switch ($callbackName) {
|
||||||
|
case self::CB_MP_BEGINMAP:
|
||||||
|
{
|
||||||
|
// Map begin
|
||||||
|
$this->triggerCallback($callbackName, $callback);
|
||||||
|
$this->triggerCallback(self::CB_IC_BEGINMAP, $callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case self::CB_MP_ENDMAP:
|
||||||
|
{
|
||||||
|
// Map end
|
||||||
|
$this->triggerCallback($callbackName, $callback);
|
||||||
|
$this->triggerCallback(self::CB_IC_ENDMAP, $callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
$this->triggerCallback($callbackName, $callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a specific callback
|
||||||
|
*
|
||||||
|
* @param string $callbackName
|
||||||
|
* @param mixed $data
|
||||||
|
*/
|
||||||
|
public function triggerCallback($callbackName, $data) {
|
||||||
|
if (!array_key_exists($callbackName, $this->callbackHandlers) || !is_array($this->callbackHandlers[$callbackName])) return;
|
||||||
|
foreach ($this->callbackHandlers[$callbackName] as $handler) {
|
||||||
|
call_user_func(array($handler[0], $handler[1]), $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new callback handler
|
||||||
|
*/
|
||||||
|
public function registerCallbackHandler($callback, $handler, $method) {
|
||||||
|
if (!is_object($handler) || !method_exists($handler, $method)) {
|
||||||
|
trigger_error("Given handler can't handle callback '" . $callback . "' (no method '" . $method . "')!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!array_key_exists($callback, $this->callbackHandlers) || !is_array($this->callbackHandlers[$callback])) {
|
||||||
|
// Init callback handler array
|
||||||
|
$this->callbackHandlers[$callback] = array();
|
||||||
|
}
|
||||||
|
// Register callback handler
|
||||||
|
array_push($this->callbackHandlers[$callback], array($handler, $method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
85
application/core/chat.iControl.php
Normal file
85
application/core/chat.iControl.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for chat methods
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Chat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
private $prefix = 'iControl>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct iControl chat
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('chat.iControl.xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a chat message to the given login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @param string $message
|
||||||
|
* @param bool $prefix
|
||||||
|
*/
|
||||||
|
public function sendChat($message, $login = null, $prefix = false) {
|
||||||
|
if (!$this->iControl->client) return false;
|
||||||
|
if ($login === null) {
|
||||||
|
return $this->iControl->client->query('ChatSendServerMessage', ($prefix ? $this->prefix : '') . $message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $this->iControl->client->query('ChatSendServerMessageToLogin', ($prefix ? $this->prefix : '') . $message, $login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an information message to the given login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @param string $message
|
||||||
|
* @param bool $prefix
|
||||||
|
*/
|
||||||
|
public function sendInformation($message, $login = null, $prefix = false) {
|
||||||
|
$format = (string) $this->config->messages->information;
|
||||||
|
return $this->sendChat($format . $message, $login);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a success message to the given login
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param string $login
|
||||||
|
* @param bool $prefix
|
||||||
|
*/
|
||||||
|
public function sendSuccess($message, $login = null, $prefix = false) {
|
||||||
|
$format = (string) $this->config->messages->success;
|
||||||
|
return $this->sendChat($format . $message, $login);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an error message to the given login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @param string $message
|
||||||
|
* @param bool $prefix
|
||||||
|
*/
|
||||||
|
public function sendError($message, $login = null, $prefix = false) {
|
||||||
|
$format = (string) $this->config->messages->error;
|
||||||
|
return $this->sendChat($format . $message, $login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
684
application/core/commands.iControl.php
Normal file
684
application/core/commands.iControl.php
Normal file
@ -0,0 +1,684 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for handling chat commands
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Commands {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
private $commandHandlers = array();
|
||||||
|
|
||||||
|
private $openBills = array();
|
||||||
|
|
||||||
|
private $serverShutdownTime = -1;
|
||||||
|
|
||||||
|
private $serverShutdownEmpty = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct commands handler
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('commands.iControl.xml');
|
||||||
|
|
||||||
|
// Register for callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'each5Seconds');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_BILLUPDATED, $this, 'handleBillUpdated');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handleChatCallback');
|
||||||
|
|
||||||
|
// Register basic commands
|
||||||
|
$commands = array('help', 'version', 'shutdown', 'shutdownserver', 'networkstats', 'systeminfo', 'getservername',
|
||||||
|
'setservername', 'getplanets', 'donate', 'pay', 'kick', 'nextmap', 'restartmap', 'addmap', 'removemap', 'startwarmup',
|
||||||
|
'stopwarmup');
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$this->registerCommandHandler($command, $this, 'command_' . $command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a command handler
|
||||||
|
*
|
||||||
|
* @param string $commandName
|
||||||
|
* @param object $handler
|
||||||
|
* @param string $method
|
||||||
|
*/
|
||||||
|
public function registerCommandHandler($commandName, $handler, $method) {
|
||||||
|
$command = strtolower($commandName);
|
||||||
|
if (!is_object($handler) || !method_exists($handler, $method)) {
|
||||||
|
trigger_error("Given handler can't handle command '" . $command . "' (no method '" . $method . "')!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!array_key_exists($command, $this->commandHandlers) || !is_array($this->commandHandlers[$command])) {
|
||||||
|
// Init handlers array
|
||||||
|
$this->commandHandlers[$command] = array();
|
||||||
|
}
|
||||||
|
// Register command handler
|
||||||
|
array_push($this->commandHandlers[$command], array($handler, $method));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle chat callback
|
||||||
|
*/
|
||||||
|
public function handleChatCallback($callback) {
|
||||||
|
$chat = $callback[1];
|
||||||
|
// Check for command
|
||||||
|
if (!$chat[3]) return;
|
||||||
|
// Check for valid player
|
||||||
|
if ($chat[0] <= 0 || strlen($chat[1]) <= 0) return;
|
||||||
|
// Handle command
|
||||||
|
$command = explode(" ", substr($chat[2], 1));
|
||||||
|
$command = strtolower($command[0]);
|
||||||
|
if (!array_key_exists($command, $this->commandHandlers) || !is_array($this->commandHandlers[$command])) {
|
||||||
|
// No command handler registered
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Inform command handlers
|
||||||
|
foreach ($this->commandHandlers[$command] as $handler) {
|
||||||
|
call_user_func(array($handler[0], $handler[1]), $callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle bill updated callback
|
||||||
|
*/
|
||||||
|
public function handleBillUpdated($callback) {
|
||||||
|
$bill = $callback[1];
|
||||||
|
if (!array_key_exists($bill[0], $this->openBills)) return;
|
||||||
|
$login = $this->openBills[$bill[0]];
|
||||||
|
switch ($bill[1]) {
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
// Payed
|
||||||
|
$message = 'Success! Thanks.';
|
||||||
|
$this->iControl->chat->sendSuccess($message, $login);
|
||||||
|
unset($this->openBills[$bill[0]]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
{
|
||||||
|
// Refused
|
||||||
|
$message = 'Transaction cancelled.';
|
||||||
|
$this->iControl->chat->sendError($message, $login);
|
||||||
|
unset($this->openBills[$bill[0]]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
{
|
||||||
|
// Error
|
||||||
|
$this->iControl->chat->sendError($bill[2], $login);
|
||||||
|
unset($this->openBills[$bill[0]]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the needed rights level to perform the given command
|
||||||
|
*
|
||||||
|
* @param string $commandName
|
||||||
|
* @param string $defaultLevel
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getRightsLevel($commandName, $defaultLevel) {
|
||||||
|
$command_rights = $this->config->xpath('//' . strtolower($commandName) . '/..');
|
||||||
|
if (empty($command_rights)) return $defaultLevel;
|
||||||
|
$rights = $this->iControl->authentication->RIGHTS_LEVELS;
|
||||||
|
$highest_level = null;
|
||||||
|
foreach ($command_rights as $right) {
|
||||||
|
$levelName = $right->getName();
|
||||||
|
$levelInt = array_search($levelName, $rights);
|
||||||
|
if ($levelInt !== false && ($highest_level === null || $highest_level < $levelInt)) {
|
||||||
|
$highest_level = $levelInt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($highest_level === null || !array_key_exists($highest_level, $rights)) return $defaultLevel;
|
||||||
|
return $rights[$highest_level];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send iControl version
|
||||||
|
*/
|
||||||
|
private function command_version($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('version', 'all'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->chat->sendInformation('This server is using iControl v' . iControl::VERSION . '!', $login)) {
|
||||||
|
trigger_error("Couldn't send version to '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send help list
|
||||||
|
*/
|
||||||
|
private function command_help($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('help', 'all'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: improve help command
|
||||||
|
// TODO: enable help for specific commands
|
||||||
|
$list = 'Available commands: ';
|
||||||
|
$commands = array_keys($this->commandHandlers);
|
||||||
|
$count = count($commands);
|
||||||
|
for ($index = 0; $index < $count; $index++) {
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel($commands[$index], 'superadmin'))) {
|
||||||
|
unset($commands[$index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count = count($commands);
|
||||||
|
$index = 0;
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$list .= $command;
|
||||||
|
if ($index < $count - 1) {
|
||||||
|
$list .= ', ';
|
||||||
|
}
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->chat->sendInformation($list, $login)) {
|
||||||
|
trigger_error("Couldn't send help list to '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle getplanets command
|
||||||
|
*/
|
||||||
|
private function command_getplanets($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('getplanets', 'admin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->client->query('GetServerPlanets')) {
|
||||||
|
trigger_error("Couldn't retrieve server planets. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$planets = $this->iControl->client->getResponse();
|
||||||
|
if (!$this->iControl->chat->sendInformation('This Server has ' . $planets . ' Planets!', $login)) {
|
||||||
|
trigger_error("Couldn't send server planets to '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle donate command
|
||||||
|
*/
|
||||||
|
private function command_donate($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('donate', 'all'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$params = explode(' ', $chat[1][2]);
|
||||||
|
if (count($params) < 2) {
|
||||||
|
// TODO: send usage information
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$amount = (int) $params[1];
|
||||||
|
if (!$amount || $amount <= 0) {
|
||||||
|
// TODO: send usage information
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (count($params) >= 3) {
|
||||||
|
$receiver = $params[2];
|
||||||
|
$receiverPlayer = $this->iControl->database->getPlayer($receiver);
|
||||||
|
$receiverName = ($receiverPlayer ? $receiverPlayer['NickName'] : $receiver);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$receiver = '';
|
||||||
|
$receiverName = $this->iControl->server->getName();
|
||||||
|
}
|
||||||
|
$message = 'Donate ' . $amount . ' Planets to $<' . $receiverName . '$>?';
|
||||||
|
if (!$this->iControl->client->query('SendBill', $login, $amount, $message, $receiver)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't create donation of " . $amount . " planets from '" . $login . "' for '" . $receiver . "'. " .
|
||||||
|
$this->iControl->getClientErrorText());
|
||||||
|
$this->iControl->chat->sendError("Creating donation failed.", $login);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$bill = $this->iControl->client->getResponse();
|
||||||
|
$this->openBills[$bill] = $login;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle pay command
|
||||||
|
*/
|
||||||
|
private function command_pay($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('pay', 'superadmin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$params = explode(' ', $chat[1][2]);
|
||||||
|
if (count($params) < 2) {
|
||||||
|
// TODO: send usage information
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$amount = (int) $params[1];
|
||||||
|
if (!$amount || $amount <= 0) {
|
||||||
|
// TODO: send usage information
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (count($params) >= 3) {
|
||||||
|
$receiver = $params[2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$receiver = $login;
|
||||||
|
}
|
||||||
|
$message = 'Payout from $<' . $this->iControl->server->getName() . '$>.';
|
||||||
|
if (!$this->iControl->client->query('Pay', $receiver, $amount, $message)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't create payout of" . $amount . " planets by '" . $login . "' for '" . $receiver . "'. " .
|
||||||
|
$this->iControl->getClientErrorText());
|
||||||
|
$this->iControl->chat->sendError("Creating payout failed.", $login);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$bill = $this->iControl->client->getResponse();
|
||||||
|
$this->openBills[$bill] = $login;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle networkstats command
|
||||||
|
*/
|
||||||
|
private function command_networkstats($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('networkstats', 'superadmin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$networkStats = $this->iControl->server->getNetworkStats();
|
||||||
|
$message = 'NetworkStats: ' . 'uptime=' . $networkStats['Uptime'] . ', ' . 'nbConn=' . $networkStats['NbrConnection'] . ', ' .
|
||||||
|
'recvRate=' . $networkStats['RecvNetRate'] . ', ' . 'sendRate=' . $networkStats['SendNetRate'] . ', ' . 'recvTotal=' .
|
||||||
|
$networkStats['SendNetRate'] . ', ' . 'sentTotal=' . $networkStats['SendNetRate'];
|
||||||
|
if (!$this->iControl->chat->sendInformation($message, $login)) {
|
||||||
|
trigger_error("Couldn't send network stats to '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle systeminfo command
|
||||||
|
*/
|
||||||
|
private function command_systeminfo($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('systeminfo', 'superadmin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$systemInfo = $this->iControl->server->getSystemInfo();
|
||||||
|
$message = 'SystemInfo: ' . 'ip=' . $systemInfo['PublishedIp'] . ', ' . 'port=' . $systemInfo['Port'] . ', ' . 'p2pPort=' .
|
||||||
|
$systemInfo['P2PPort'] . ', ' . 'title=' . $systemInfo['TitleId'] . ', ' . 'login=' . $systemInfo['ServerLogin'] . ', ';
|
||||||
|
if (!$this->iControl->chat->sendInformation($message, $login)) {
|
||||||
|
trigger_error("Couldn't send system info to '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle shutdown command
|
||||||
|
*/
|
||||||
|
private function command_shutdown($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('shutdown', 'superadmin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->iControl->quit("iControl shutdown requested by '" . $login . "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle startwarmup command
|
||||||
|
*/
|
||||||
|
private function command_startwarmup($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('startwarmup', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->client->query("SetWarmUp", true)) {
|
||||||
|
trigger_error("Couldn't start warmup. " . $this->iControl->getClientErrorText());
|
||||||
|
$player = $this->iControl->database->getPlayer($login);
|
||||||
|
$this->iControl->chat->sendInformation('$<' . ($player ? $player['NickName'] : $login) . '$> started WarmUp!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle stopwarmup command
|
||||||
|
*/
|
||||||
|
private function command_stopwarmup($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('stopwarmup', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->client->query("SetWarmUp", false)) {
|
||||||
|
trigger_error("Couldn't stop warmup. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$player = $this->iControl->database->getPlayer($login);
|
||||||
|
$this->iControl->chat->sendInformation('$<' . ($player ? $player['NickName'] : $login) . '$> stopped WarmUp!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle server shutdown command
|
||||||
|
*/
|
||||||
|
private function command_shutdownserver($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('shutdownserver', 'superadmin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check for delayed shutdown
|
||||||
|
$params = explode(' ', $chat[1][2]);
|
||||||
|
if (count($params) >= 2) {
|
||||||
|
$param = $params[1];
|
||||||
|
if ($param == 'empty') {
|
||||||
|
$this->serverShutdownEmpty = !$this->serverShutdownEmpty;
|
||||||
|
if ($this->serverShutdownEmpty) {
|
||||||
|
$this->iControl->chat->sendInformation("The server will shutdown as soon as it's empty!", $login);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->iControl->chat->sendInformation("Empty-shutdown cancelled!", $login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$delay = (int) $param;
|
||||||
|
if ($delay <= 0) {
|
||||||
|
// Cancel shutdown
|
||||||
|
$this->serverShutdownTime = -1;
|
||||||
|
$this->iControl->chat->sendInformation("Delayed shutdown cancelled!", $login);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Trigger delayed shutdown
|
||||||
|
$this->serverShutdownTime = time() + $delay * 60.;
|
||||||
|
$this->iControl->chat->sendInformation("The server will shut down in " . $delay . " minutes!", $login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->shutdownServer($login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle kick command
|
||||||
|
*/
|
||||||
|
private function command_kick($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('kick', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$params = explode(' ', $chat[1][2], 3);
|
||||||
|
if (count($params) < 2) {
|
||||||
|
// TODO: show usage
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$target = $params[1];
|
||||||
|
$players = $this->iControl->server->getPlayers();
|
||||||
|
foreach ($players as $player) {
|
||||||
|
if ($player['Login'] != $target) continue;
|
||||||
|
// Kick player
|
||||||
|
if (isset($params[2])) {
|
||||||
|
$message = $params[2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$message = "";
|
||||||
|
}
|
||||||
|
if (!$this->iControl->client->query('Kick', $target, $message)) {
|
||||||
|
trigger_error("Couldn't kick player '" . $target . "'! " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->iControl->chat->sendError("Invalid player login.", $login);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle removemap command
|
||||||
|
*/
|
||||||
|
private function command_removemap($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('kick', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: allow params
|
||||||
|
// Get map name
|
||||||
|
$map = $this->iControl->server->getMap();
|
||||||
|
if (!$map) {
|
||||||
|
$this->iControl->chat->sendError("Couldn't remove map.", $login);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$mapName = $map['FileName'];
|
||||||
|
|
||||||
|
// Remove map
|
||||||
|
if (!$this->iControl->client->query('RemoveMap', $mapName)) {
|
||||||
|
trigger_error("Couldn't remove current map. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->iControl->chat->sendSuccess('Map removed.', $login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle addmap command
|
||||||
|
*/
|
||||||
|
private function command_addmap($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('addmap', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$params = explode(' ', $chat[1][2], 2);
|
||||||
|
if (count($params) < 2) {
|
||||||
|
// TODO: show usage
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if iControl can even write to the maps dir
|
||||||
|
if (!$this->iControl->client->query('GetMapsDirectory')) {
|
||||||
|
trigger_error("Couldn't get map directory. " . $this->iControl->getClientErrorText());
|
||||||
|
$this->iControl->chat->sendError("iControl couldn't retrieve the maps directory.", $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$mapDir = $this->iControl->client->getResponse();
|
||||||
|
if (!is_dir($mapDir)) {
|
||||||
|
trigger_error("iControl doesn't have have access to the maps directory in '" . $mapDir . "'.");
|
||||||
|
$this->iControl->chat->sendError("iControl doesn't have access to the maps directory.", $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$dlDir = (string) $this->iControl->config->maps_dir;
|
||||||
|
// Create mx directory if necessary
|
||||||
|
if (!is_dir($mapDir . $dlDir) && !mkdir($mapDir . $dlDir)) {
|
||||||
|
trigger_error("iControl doesn't have to rights to save maps in'" . $mapDir . $dlDir, "'.");
|
||||||
|
$this->iControl->chat->sendError("iControl doesn't have to rights to save maps.", $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$mapDir .= $dlDir . '/';
|
||||||
|
// Download the map
|
||||||
|
if (is_numeric($params[1])) {
|
||||||
|
$serverInfo = $this->iControl->server->getSystemInfo();
|
||||||
|
$title = strtolower(substr($serverInfo['TitleId'], 0, 2));
|
||||||
|
// Check if map exists
|
||||||
|
$url = 'http://' . $title . '.mania-exchange.com/api/tracks/get_track_info/id/' . $params[1] . '?format=json';
|
||||||
|
$mapInfo = Tools::loadFile($url);
|
||||||
|
if (!$mapInfo || strlen($mapInfo) <= 0) {
|
||||||
|
// Invalid id
|
||||||
|
$this->iControl->chat->sendError('Invalid MX-Id!', $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$mapInfo = json_decode($mapInfo, true);
|
||||||
|
$url = 'http://' . $title . '.mania-exchange.com/tracks/download/' . $params[1];
|
||||||
|
$file = Tools::loadFile($url);
|
||||||
|
if (!$file) {
|
||||||
|
// Download error
|
||||||
|
$this->iControl->chat->sendError('Download failed!', $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Save map
|
||||||
|
$fileName = $mapDir . $mapInfo['TrackID'] . '_' . $mapInfo['Name'] . '.Map.Gbx';
|
||||||
|
if (!file_put_contents($fileName, $file)) {
|
||||||
|
// Save error
|
||||||
|
$this->iControl->chat->sendError('Saving map failed!', $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check for valid map
|
||||||
|
if (!$this->iControl->client->query('CheckMapForCurrentServerParams', $fileName)) {
|
||||||
|
trigger_error("Couldn't check if map is valid. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$response = $this->iControl->client->getResponse();
|
||||||
|
if (!$response) {
|
||||||
|
// Inalid map type
|
||||||
|
$this->iControl->chat->sendError("Invalid map type.", $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add map to map list
|
||||||
|
if (!$this->iControl->client->query('InsertMap', $fileName)) {
|
||||||
|
$this->iControl->chat->sendError("Couldn't add map to match settings!", $login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->iControl->chat->sendSuccess('Map $<' . $mapInfo['Name'] . '$> successfully added!');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: check if map exists locally
|
||||||
|
// TODO: load map from direct url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle nextmap command
|
||||||
|
*/
|
||||||
|
private function command_nextmap($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('nextmap', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->client->query('NextMap')) {
|
||||||
|
trigger_error("Couldn't skip map. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle retartmap command
|
||||||
|
*/
|
||||||
|
private function command_restartmap($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('restartmap', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->iControl->client->query('RestartMap')) {
|
||||||
|
trigger_error("Couldn't restart map. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle getservername command
|
||||||
|
*/
|
||||||
|
private function command_getservername($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('getservername', 'operator'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$serverName = $this->iControl->server->getName();
|
||||||
|
$this->iControl->chat->sendInformation("Server Name: " . $serverName, $login);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle setservername command
|
||||||
|
*/
|
||||||
|
private function command_setservername($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('setservername', 'admin'))) {
|
||||||
|
// Not allowed!
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$params = explode(' ', $chat[1][2], 2);
|
||||||
|
if (count($params) < 2) {
|
||||||
|
// TODO: show usage
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$serverName = $params[1];
|
||||||
|
if (!$this->iControl->client->query('SetServerName', $serverName)) {
|
||||||
|
trigger_error("Couldn't set server name. " . $this->iControl->getClientErrorText());
|
||||||
|
$this->iControl->chat->sendError("Error!");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$serverName = $this->iControl->server->getName();
|
||||||
|
$this->iControl->chat->sendInformation("New Name: " . $serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check stuff each 5 seconds
|
||||||
|
*/
|
||||||
|
public function each5Seconds() {
|
||||||
|
// Empty shutdown
|
||||||
|
if ($this->serverShutdownEmpty) {
|
||||||
|
$players = $this->iControl->server->getPlayers();
|
||||||
|
if (count($players) <= 0) {
|
||||||
|
$this->shutdownServer('empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delayed shutdown
|
||||||
|
if ($this->serverShutdownTime > 0) {
|
||||||
|
if (time() >= $this->serverShutdownTime) {
|
||||||
|
$this->shutdownServer('delayed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform server shutdown
|
||||||
|
*/
|
||||||
|
private function shutdownServer($login = '#') {
|
||||||
|
$this->iControl->client->resetError();
|
||||||
|
if (!$this->iControl->client->query('StopServer') || $this->iControl->client->isError()) {
|
||||||
|
trigger_error("Server shutdown command from '" . $login . "' failed. " . $this->iControl->getClientErrorText());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->iControl->quit("Server shutdown requested by '" . $login . "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
348
application/core/core.iControl.php
Normal file
348
application/core/core.iControl.php
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed includes
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/authentication.iControl.php';
|
||||||
|
require_once __DIR__ . '/callbacks.iControl.php';
|
||||||
|
require_once __DIR__ . '/chat.iControl.php';
|
||||||
|
require_once __DIR__ . '/commands.iControl.php';
|
||||||
|
require_once __DIR__ . '/database.iControl.php';
|
||||||
|
require_once __DIR__ . '/server.iControl.php';
|
||||||
|
require_once __DIR__ . '/stats.iControl.php';
|
||||||
|
require_once __DIR__ . '/tools.iControl.php';
|
||||||
|
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
|
||||||
|
if ($endiantest == 1) {
|
||||||
|
require_once __DIR__ . '/PhpRemote/GbxRemote.inc.php';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
require_once __DIR__ . '/PhpRemote/GbxRemote.bem.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iControl Server Controller for ManiaPlanet Server
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class iControl {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const VERSION = '0.1';
|
||||||
|
const API_VERSION = '2013-04-16';
|
||||||
|
const DATE = 'd-m-y h:i:sa T';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public properties
|
||||||
|
*/
|
||||||
|
public $authentication = null;
|
||||||
|
|
||||||
|
public $callbacks = null;
|
||||||
|
|
||||||
|
public $client = null;
|
||||||
|
|
||||||
|
public $chat = null;
|
||||||
|
|
||||||
|
public $config = null;
|
||||||
|
|
||||||
|
public $commands = null;
|
||||||
|
|
||||||
|
public $database = null;
|
||||||
|
|
||||||
|
public $debug = false;
|
||||||
|
|
||||||
|
public $server = null;
|
||||||
|
|
||||||
|
public $startTime = -1;
|
||||||
|
|
||||||
|
public $stats = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $plugins = array();
|
||||||
|
|
||||||
|
private $shutdownRequested = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct iControl
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
// Load core
|
||||||
|
$this->config = Tools::loadConfig('core.iControl.xml');
|
||||||
|
$this->startTime = time();
|
||||||
|
|
||||||
|
// Load chat tool
|
||||||
|
$this->chat = new Chat($this);
|
||||||
|
|
||||||
|
// Load callbacks handler
|
||||||
|
$this->callbacks = new Callbacks($this);
|
||||||
|
|
||||||
|
// Load database
|
||||||
|
$this->database = new Database($this);
|
||||||
|
|
||||||
|
// Load server
|
||||||
|
$this->server = new Server($this);
|
||||||
|
|
||||||
|
// Load authentication
|
||||||
|
$this->authentication = new Authentication($this);
|
||||||
|
|
||||||
|
// Load commands handler
|
||||||
|
$this->commands = new Commands($this);
|
||||||
|
|
||||||
|
// Load stats manager
|
||||||
|
$this->stats = new Stats($this);
|
||||||
|
|
||||||
|
// Register for core callbacks
|
||||||
|
$this->callbacks->registerCallbackHandler(Callbacks::CB_MP_ENDMAP, $this, 'handleEndMap');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return message composed of client error message and error code
|
||||||
|
*
|
||||||
|
* @param object $client
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getClientErrorText($client = null) {
|
||||||
|
if (is_object($client)) {
|
||||||
|
return $client->getErrorMessage() . ' (' . $client->getErrorCode() . ')';
|
||||||
|
}
|
||||||
|
return $this->client->getErrorMessage() . ' (' . $this->client->getErrorCode() . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quit iControl and log the given message
|
||||||
|
*/
|
||||||
|
public function quit($message = false) {
|
||||||
|
if ($this->shutdownRequested) return;
|
||||||
|
|
||||||
|
if ($this->client) {
|
||||||
|
// Announce quit
|
||||||
|
$this->chat->sendInformation('iControl shutting down.');
|
||||||
|
|
||||||
|
// Hide manialinks
|
||||||
|
$this->client->query('SendHideManialinkPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log quit reason
|
||||||
|
if ($message) {
|
||||||
|
error_log($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown
|
||||||
|
if ($this->client) $this->client->Terminate();
|
||||||
|
|
||||||
|
error_log("Quitting iControl!");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run iControl
|
||||||
|
*/
|
||||||
|
public function run($debug = false) {
|
||||||
|
error_log('Starting iControl v' . self::VERSION . '!');
|
||||||
|
$this->debug = (bool) $debug;
|
||||||
|
|
||||||
|
// Load plugins
|
||||||
|
$this->loadPlugins();
|
||||||
|
|
||||||
|
// Connect to server
|
||||||
|
$this->connect();
|
||||||
|
|
||||||
|
// Loading finished
|
||||||
|
error_log("Loading completed!");
|
||||||
|
|
||||||
|
// Announce iControl
|
||||||
|
if (!$this->chat->sendInformation('iControl v' . self::VERSION . ' successfully started!')) {
|
||||||
|
trigger_error("Couldn't announce iControl. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnInit
|
||||||
|
$this->callbacks->onInit();
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
while (!$this->shutdownRequested) {
|
||||||
|
$loopStart = microtime(true);
|
||||||
|
|
||||||
|
// Disable script timeout
|
||||||
|
set_time_limit(30);
|
||||||
|
|
||||||
|
// Handle server callbacks
|
||||||
|
$this->callbacks->handleCallbacks();
|
||||||
|
|
||||||
|
// Loop plugins
|
||||||
|
foreach ($this->plugins as $plugin) {
|
||||||
|
if (!method_exists($plugin, 'loop')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$plugin->loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yield for next tick
|
||||||
|
$loopEnd = microtime(true);
|
||||||
|
$sleepTime = 300000 - $loopEnd + $loopStart;
|
||||||
|
if ($sleepTime > 0) {
|
||||||
|
usleep($sleepTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown
|
||||||
|
$this->client->Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to ManiaPlanet server
|
||||||
|
*/
|
||||||
|
private function connect() {
|
||||||
|
$enable = $this->server->config->xpath('enable');
|
||||||
|
$enable = Tools::toBool($enable[0]);
|
||||||
|
if (!$enable) return;
|
||||||
|
|
||||||
|
// Load remote client
|
||||||
|
$this->client = new \IXR_ClientMulticall_Gbx();
|
||||||
|
|
||||||
|
$host = $this->server->config->xpath('host');
|
||||||
|
if (!$host) trigger_error("Invalid server configuration (host).", E_USER_ERROR);
|
||||||
|
$host = (string) $host[0];
|
||||||
|
$port = $this->server->config->xpath('port');
|
||||||
|
if (!$host) trigger_error("Invalid server configuration (port).", E_USER_ERROR);
|
||||||
|
$port = (string) $port[0];
|
||||||
|
$timeout = $this->config->xpath('timeout');
|
||||||
|
if (!$timeout) trigger_error("Invalid core configuration (timeout).", E_USER_ERROR);
|
||||||
|
$timeout = (int) $timeout[0];
|
||||||
|
|
||||||
|
error_log("Connecting to server at " . $host . ":" . $port . "...");
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
if (!$this->client->InitWithIp($host, $port, $timeout)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't connect to server! " . $this->client->getErrorMessage() . "(" . $this->client->getErrorCode() . ")",
|
||||||
|
E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
$login = $this->server->config->xpath('login');
|
||||||
|
if (!$login) trigger_error("Invalid server configuration (login).", E_USER_ERROR);
|
||||||
|
$login = (string) $login[0];
|
||||||
|
$pass = $this->server->config->xpath('pass');
|
||||||
|
if (!$pass) trigger_error("Invalid server configuration (password).", E_USER_ERROR);
|
||||||
|
$pass = (string) $pass[0];
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
if (!$this->client->query('Authenticate', $login, $pass)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't authenticate on server with user '" . $login . "'! " . $this->client->getErrorMessage() . "(" .
|
||||||
|
$this->client->getErrorCode() . ")", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable callback system
|
||||||
|
if (!$this->client->query('EnableCallbacks', true)) {
|
||||||
|
trigger_error("Couldn't enable callbacks! " . $this->client->getErrorMessage() . "(" . $this->client->getErrorCode() . ")",
|
||||||
|
E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for server to be ready
|
||||||
|
if (!$this->server->waitForStatus($this->client, 4)) {
|
||||||
|
trigger_error("Server couldn't get ready!", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set api version
|
||||||
|
if (!$this->client->query('SetApiVersion', self::API_VERSION)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't set API version '" . self::API_VERSION . "'! This might cause problems. " .
|
||||||
|
$this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect finished
|
||||||
|
error_log("Server connection succesfully established!");
|
||||||
|
|
||||||
|
// Enable service announces
|
||||||
|
if (!$this->client->query("DisableServiceAnnounces", false)) {
|
||||||
|
trigger_error("Couldn't enable service announces. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable script callbacks if needed
|
||||||
|
if ($this->server->getGameMode() === 0) {
|
||||||
|
if (!$this->client->query('GetModeScriptSettings')) {
|
||||||
|
trigger_error("Couldn't get mode script settings. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scriptSettings = $this->client->getResponse();
|
||||||
|
if (array_key_exists('S_UseScriptCallbacks', $scriptSettings)) {
|
||||||
|
$scriptSettings['S_UseScriptCallbacks'] = true;
|
||||||
|
if (!$this->client->query('SetModeScriptSettings', $scriptSettings)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't set mode script settings to enable script callbacks. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_log("Script callbacks successfully enabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load iControl plugins
|
||||||
|
*/
|
||||||
|
private function loadPlugins() {
|
||||||
|
$pluginsConfig = Tools::loadConfig('plugins.iControl.xml');
|
||||||
|
if (!$pluginsConfig || !isset($pluginsConfig->plugin)) {
|
||||||
|
trigger_error('Invalid plugins config.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load plugin classes
|
||||||
|
$classes = get_declared_classes();
|
||||||
|
foreach ($pluginsConfig->xpath('plugin') as $plugin) {
|
||||||
|
$fileName = ICONTROL . '/plugins/' . $plugin;
|
||||||
|
if (!file_exists($fileName)) {
|
||||||
|
trigger_error("Couldn't load plugin '" . $plugin . "'! File doesn't exist. (/plugins/" . $plugin . ")");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
require_once $fileName;
|
||||||
|
error_log("Loading plugin: " . $plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$plugins = array_diff(get_declared_classes(), $classes);
|
||||||
|
|
||||||
|
// Create plugins
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
|
$nameIndex = stripos($plugin, 'plugin');
|
||||||
|
if ($nameIndex === false) continue;
|
||||||
|
array_push($this->plugins, new $plugin($this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle EndMap callback
|
||||||
|
*/
|
||||||
|
public function handleEndMap($callback) {
|
||||||
|
// Autosave match settings
|
||||||
|
$autosaveMatchsettings = $this->config->xpath('autosave_matchsettings');
|
||||||
|
if ($autosaveMatchsettings) {
|
||||||
|
$autosaveMatchsettings = (string) $autosaveMatchsettings[0];
|
||||||
|
if ($autosaveMatchsettings) {
|
||||||
|
if (!$this->client->query('SaveMatchSettings', 'MatchSettings/' . $autosaveMatchsettings)) {
|
||||||
|
trigger_error("Couldn't autosave match settings. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check config settings
|
||||||
|
*/
|
||||||
|
public function checkConfig($config, $settings, $name = 'Config XML') {
|
||||||
|
if (!is_array($settings)) $settings = array($settings);
|
||||||
|
foreach ($settings as $setting) {
|
||||||
|
$settingTags = $config->xpath('//' . $setting);
|
||||||
|
if (empty($settingTags)) {
|
||||||
|
trigger_error("Missing property '" . $setting . "' in config '" . $name . "'!", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
401
application/core/database.iControl.php
Normal file
401
application/core/database.iControl.php
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for database connection
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Database {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const TABLE_PLAYERS = 'ic_players';
|
||||||
|
const TABLE_MAPS = 'ic_maps';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public properties
|
||||||
|
*/
|
||||||
|
public $mysqli = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
private $multiQueries = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct database connection
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('database.iControl.xml');
|
||||||
|
$this->iControl->checkConfig($this->config, array("host", "user"), 'database.iControl.xml');
|
||||||
|
|
||||||
|
// Get mysql server information
|
||||||
|
$host = $this->config->xpath('host');
|
||||||
|
if (!$host) trigger_error("Invalid database configuration (host).", E_USER_ERROR);
|
||||||
|
$host = (string) $host[0];
|
||||||
|
|
||||||
|
$port = $this->config->xpath('port');
|
||||||
|
if (!$port) trigger_error("Invalid database configuration (port).", E_USER_ERROR);
|
||||||
|
$port = (int) $port[0];
|
||||||
|
|
||||||
|
$user = $this->config->xpath('user');
|
||||||
|
if (!$user) trigger_error("Invalid database configuration (user).", E_USER_ERROR);
|
||||||
|
$user = (string) $user[0];
|
||||||
|
|
||||||
|
$pass = $this->config->xpath('pass');
|
||||||
|
if (!$pass) trigger_error("Invalid database configuration (pass).", E_USER_ERROR);
|
||||||
|
$pass = (string) $pass[0];
|
||||||
|
|
||||||
|
// Open database connection
|
||||||
|
$this->mysqli = new \mysqli($host, $user, $pass, null, $port);
|
||||||
|
if ($this->mysqli->connect_error) {
|
||||||
|
// Connection error
|
||||||
|
throw new \Exception(
|
||||||
|
"Error on connecting to mysql server. " . $this->mysqli->connect_error . " (" . $this->mysqli->connect_errno . ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set charset
|
||||||
|
$this->mysqli->set_charset("utf8");
|
||||||
|
|
||||||
|
// Create/Connect database
|
||||||
|
$this->initDatabase();
|
||||||
|
|
||||||
|
// Init tables
|
||||||
|
$this->initTables();
|
||||||
|
|
||||||
|
// Register for callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'handle5Second');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_BEGINMAP, $this, 'handleBeginMap');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destruct database connection
|
||||||
|
*/
|
||||||
|
public function __destruct() {
|
||||||
|
$this->mysqli->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the defined database (create it if needed)
|
||||||
|
*/
|
||||||
|
private function initDatabase() {
|
||||||
|
$dbname = $this->config->xpath('database');
|
||||||
|
if (!$dbname) trigger_error("Invalid database configuration (database).", E_USER_ERROR);
|
||||||
|
$dbname = (string) $dbname[0];
|
||||||
|
|
||||||
|
// Try to connect
|
||||||
|
$result = $this->mysqli->select_db($dbname);
|
||||||
|
if (!$result) {
|
||||||
|
// Create database
|
||||||
|
$query = "CREATE DATABASE `" . $this->escape($dbname) . "`;";
|
||||||
|
$result = $this->mysqli->query($query);
|
||||||
|
if (!$result) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't create database '" . $dbname . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')',
|
||||||
|
E_USER_ERROR);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Connect to database
|
||||||
|
$result = $this->mysqli->select_db($dbname);
|
||||||
|
if (!$result) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't select database '" . $dbname . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')',
|
||||||
|
E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the needed tables
|
||||||
|
*/
|
||||||
|
private function initTables() {
|
||||||
|
$query = "";
|
||||||
|
|
||||||
|
// Players table
|
||||||
|
$query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_PLAYERS . "` (
|
||||||
|
`index` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`Login` varchar(100) NOT NULL,
|
||||||
|
`NickName` varchar(250) NOT NULL,
|
||||||
|
`PlayerId` int(11) NOT NULL,
|
||||||
|
`LadderRanking` int(11) NOT NULL,
|
||||||
|
`Flags` varchar(50) NOT NULL,
|
||||||
|
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`index`),
|
||||||
|
UNIQUE KEY `Login` (`Login`)
|
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Store player metadata' AUTO_INCREMENT=1;";
|
||||||
|
|
||||||
|
// Maps table
|
||||||
|
$query .= "CREATE TABLE IF NOT EXISTS `ic_maps` (
|
||||||
|
`index` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`UId` varchar(100) NOT NULL,
|
||||||
|
`Name` varchar(100) NOT NULL,
|
||||||
|
`FileName` varchar(200) NOT NULL,
|
||||||
|
`Author` varchar(150) NOT NULL,
|
||||||
|
`Environnement` varchar(50) NOT NULL,
|
||||||
|
`Mood` varchar(50) NOT NULL,
|
||||||
|
`BronzeTime` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`SilverTime` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`GoldTime` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`AuthorTime` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`CopperPrice` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`LapRace` tinyint(1) NOT NULL,
|
||||||
|
`NbLaps` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`NbCheckpoints` int(11) NOT NULL DEFAULT '-1',
|
||||||
|
`MapType` varchar(100) NOT NULL,
|
||||||
|
`MapStyle` varchar(100) 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 COMMENT='Store map metadata' AUTO_INCREMENT=1;";
|
||||||
|
|
||||||
|
// Perform queries
|
||||||
|
if (!$this->multiQuery($query)) {
|
||||||
|
trigger_error("Creating basic tables failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')', E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize all existing tables
|
||||||
|
$query = "SHOW TABLES;";
|
||||||
|
$result = $this->query($query);
|
||||||
|
if (!$result || !is_object($result)) {
|
||||||
|
trigger_error("Couldn't select tables. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$query = "OPTIMIZE TABLE ";
|
||||||
|
$count = $result->num_rows;
|
||||||
|
$index = 0;
|
||||||
|
while ($row = $result->fetch_row()) {
|
||||||
|
$query .= "`" . $row[0] . "`";
|
||||||
|
if ($index < $count - 1) $query .= ", ";
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
$query .= ";";
|
||||||
|
if (!$this->query($query)) {
|
||||||
|
trigger_error("Couldn't optimize tables. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for performing a simple query
|
||||||
|
*
|
||||||
|
* @param string $query
|
||||||
|
* @return mixed query result
|
||||||
|
*/
|
||||||
|
public function query($query) {
|
||||||
|
if (!is_string($query)) return false;
|
||||||
|
if (strlen($query) <= 0) return true;
|
||||||
|
return $this->mysqli->query($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform multi query
|
||||||
|
*
|
||||||
|
* @param
|
||||||
|
* string multi_query
|
||||||
|
* @return bool whether no error occured during executing the multi query
|
||||||
|
*/
|
||||||
|
public function multiQuery($query) {
|
||||||
|
if (!is_string($query)) return false;
|
||||||
|
if (strlen($query) <= 0) return true;
|
||||||
|
$noError = true;
|
||||||
|
$this->mysqli->multi_query($query);
|
||||||
|
if ($this->mysqli->error) {
|
||||||
|
trigger_error("Executing multi query failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
|
||||||
|
$noError = false;
|
||||||
|
}
|
||||||
|
while ($this->mysqli->more_results() && $this->mysqli->next_result()) {
|
||||||
|
if ($this->mysqli->error) {
|
||||||
|
trigger_error("Executing multi query failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
|
||||||
|
$noError = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $noError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle 5Second callback
|
||||||
|
*/
|
||||||
|
public function handle5Second($callback = null) {
|
||||||
|
// Save current players in database
|
||||||
|
$players = $this->iControl->server->getPlayers();
|
||||||
|
if ($players) {
|
||||||
|
$query = "";
|
||||||
|
foreach ($players as $player) {
|
||||||
|
if (!Tools::isPlayer($player)) continue;
|
||||||
|
$query .= $this->composeInsertPlayer($player);
|
||||||
|
}
|
||||||
|
$this->multiQuery($query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle BeginMap callback
|
||||||
|
*/
|
||||||
|
public function handleBeginMap($callback) {
|
||||||
|
$map = $callback[1][0];
|
||||||
|
$query = $this->composeInsertMap($map);
|
||||||
|
$result = $this->query($query);
|
||||||
|
if ($this->mysqli->error) {
|
||||||
|
trigger_error("Couldn't save map. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player index for the given login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @return int null
|
||||||
|
*/
|
||||||
|
public function getPlayerIndex($login) {
|
||||||
|
$query = "SELECT `index` FROM `" . self::TABLE_PLAYERS . "` WHERE `Login` = '" . $this->escape($login) . "';";
|
||||||
|
$result = $this->query($query);
|
||||||
|
$result = $result->fetch_assoc();
|
||||||
|
if ($result) {
|
||||||
|
return $result['index'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the map index for the given UId
|
||||||
|
*
|
||||||
|
* @param string $uid
|
||||||
|
* @return int null
|
||||||
|
*/
|
||||||
|
public function getMapIndex($uid) {
|
||||||
|
$query = "SELECT `index` FROM `" . self::TABLE_MAPS . "` WHERE `UId` = '" . $this->escape($uid) . "';";
|
||||||
|
$result = $this->query($query);
|
||||||
|
$result = $result->fetch_assoc();
|
||||||
|
if ($result) {
|
||||||
|
return $result['index'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a query string for inserting the given player
|
||||||
|
*
|
||||||
|
* @param array $player
|
||||||
|
*/
|
||||||
|
private function composeInsertPlayer($player) {
|
||||||
|
if (!Tools::isPlayer($player)) return "";
|
||||||
|
return "INSERT INTO `" . self::TABLE_PLAYERS . "` (
|
||||||
|
`Login`,
|
||||||
|
`NickName`,
|
||||||
|
`PlayerId`,
|
||||||
|
`LadderRanking`,
|
||||||
|
`Flags`
|
||||||
|
) VALUES (
|
||||||
|
'" . $this->escape($player['Login']) . "',
|
||||||
|
'" . $this->escape($player['NickName']) . "',
|
||||||
|
" . $player['PlayerId'] . ",
|
||||||
|
" . $player['LadderRanking'] . ",
|
||||||
|
'" . $this->escape($player['Flags']) . "'
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`NickName` = VALUES(`NickName`),
|
||||||
|
`PlayerId` = VALUES(`PlayerId`),
|
||||||
|
`LadderRanking` = VALUES(`LadderRanking`),
|
||||||
|
`Flags` = VALUES(`Flags`);";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a query string for inserting the given map
|
||||||
|
*
|
||||||
|
* @param array $map
|
||||||
|
*/
|
||||||
|
private function composeInsertMap($map) {
|
||||||
|
if (!$map) return "";
|
||||||
|
return "INSERT INTO `" . self::TABLE_MAPS . "` (
|
||||||
|
`UId`,
|
||||||
|
`Name`,
|
||||||
|
`FileName`,
|
||||||
|
`Author`,
|
||||||
|
`Environnement`,
|
||||||
|
`Mood`,
|
||||||
|
`BronzeTime`,
|
||||||
|
`SilverTime`,
|
||||||
|
`GoldTime`,
|
||||||
|
`AuthorTime`,
|
||||||
|
`CopperPrice`,
|
||||||
|
`LapRace`,
|
||||||
|
`NbLaps`,
|
||||||
|
`NbCheckpoints`,
|
||||||
|
`MapType`,
|
||||||
|
`MapStyle`
|
||||||
|
) VALUES (
|
||||||
|
'" . $this->escape($map['UId']) . "',
|
||||||
|
'" . $this->escape($map['Name']) . "',
|
||||||
|
'" . $this->escape($map['FileName']) . "',
|
||||||
|
'" . $this->escape($map['Author']) . "',
|
||||||
|
'" . $this->escape($map['Environnement']) . "',
|
||||||
|
'" . $this->escape($map['Mood']) . "',
|
||||||
|
" . $map['BronzeTime'] . ",
|
||||||
|
" . $map['SilverTime'] . ",
|
||||||
|
" . $map['GoldTime'] . ",
|
||||||
|
" . $map['AuthorTime'] . ",
|
||||||
|
" . $map['CopperPrice'] . ",
|
||||||
|
" . Tools::boolToInt($map['LapRace']) . ",
|
||||||
|
" . $map['NbLaps'] . ",
|
||||||
|
" . $map['NbCheckpoints'] . ",
|
||||||
|
'" . $this->escape($map['MapType']) . "',
|
||||||
|
'" . $this->escape($map['MapStyle']) . "'
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`Name` = VALUES(`Name`),
|
||||||
|
`FileName` = VALUES(`FileName`),
|
||||||
|
`Author` = VALUES(`Author`),
|
||||||
|
`Environnement` = VALUES(`Environnement`),
|
||||||
|
`Mood` = VALUES(`Mood`),
|
||||||
|
`BronzeTime` = VALUES(`BronzeTime`),
|
||||||
|
`SilverTime` = VALUES(`SilverTime`),
|
||||||
|
`GoldTime` = VALUES(`GoldTime`),
|
||||||
|
`AuthorTime` = VALUES(`AuthorTime`),
|
||||||
|
`CopperPrice` = VALUES(`CopperPrice`),
|
||||||
|
`LapRace` = VALUES(`LapRace`),
|
||||||
|
`NbLaps` = VALUES(`NbLaps`),
|
||||||
|
`NbCheckpoints` = VALUES(`NbCheckpoints`),
|
||||||
|
`MapType` = VALUES(`MapType`),
|
||||||
|
`MapStyle` = VALUES(`MapStyle`);";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all information about the player with the given login
|
||||||
|
*/
|
||||||
|
public function getPlayer($login) {
|
||||||
|
if (!$login) return null;
|
||||||
|
$query = "SELECT * FROM `" . self::TABLE_PLAYERS . "` WHERE `Login` = '" . $this->escape($login) . "';";
|
||||||
|
$result = $this->mysqli->query($query);
|
||||||
|
if ($this->mysqli->error || !$result) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't select player with login '" . $login . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
while ($player = $result->fetch_assoc()) {
|
||||||
|
return $player;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes the given string for a mysql query
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function escape($string) {
|
||||||
|
return $this->mysqli->escape_string($string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
381
application/core/server.iControl.php
Normal file
381
application/core/server.iControl.php
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class providing information and commands for the connected maniaplanet server
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Server {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const VALIDATIONREPLAYDIR = 'ValidationReplays/';
|
||||||
|
const GHOSTREPLAYDIR = 'GhostReplays/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public properties
|
||||||
|
*/
|
||||||
|
public $config = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct server
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('server.iControl.xml');
|
||||||
|
$this->iControl->checkConfig($this->config, array('host', 'port', 'login', 'pass'), 'server');
|
||||||
|
|
||||||
|
// Register for callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_1_SECOND, $this, 'eachSecond');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform actions every second
|
||||||
|
*/
|
||||||
|
public function eachSecond() {
|
||||||
|
// Delete cached information
|
||||||
|
$this->players = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch game directory of the server
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDataDirectory() {
|
||||||
|
if (!$this->iControl->client->query('GameDataDirectory')) {
|
||||||
|
trigger_error("Couldn't get data directory. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if iControl has access to the given directory (server data directory if no param)
|
||||||
|
*
|
||||||
|
* @param string $directory
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function checkAccess($directory = null) {
|
||||||
|
if (!$directory) {
|
||||||
|
$directory = $this->getDataDirectory();
|
||||||
|
}
|
||||||
|
return is_dir($directory) && is_writable($directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch server login
|
||||||
|
*/
|
||||||
|
public function getLogin($client = null) {
|
||||||
|
$systemInfo = $this->getSystemInfo(false, $client);
|
||||||
|
if (!$systemInfo) return null;
|
||||||
|
return $systemInfo['ServerLogin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get detailed server info
|
||||||
|
*/
|
||||||
|
public function getInfo($detailed = false) {
|
||||||
|
if ($detailed) {
|
||||||
|
$login = $this->getLogin();
|
||||||
|
if (!$this->iControl->client->query('GetDetailedPlayerInfo', $login)) {
|
||||||
|
trigger_error("Couldn't fetch detailed server player info. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!$this->iControl->client->query('GetMainServerPlayerInfo')) {
|
||||||
|
trigger_error("Couldn't fetch server player info. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server options
|
||||||
|
*/
|
||||||
|
public function getOptions() {
|
||||||
|
if (!$this->iControl->client->query('GetServerOptions')) {
|
||||||
|
trigger_error("Couldn't fetch server options. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch server name
|
||||||
|
*/
|
||||||
|
public function getName() {
|
||||||
|
if (!$this->iControl->client->query('GetServerName')) {
|
||||||
|
trigger_error("Couldn't fetch server name. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch server version
|
||||||
|
*/
|
||||||
|
public function getVersion($forceRefresh = false) {
|
||||||
|
if (isset($this->iControl->client->version) && !$forceRefresh) return $this->iControl->client->version;
|
||||||
|
if (!$this->iControl->client->query('GetVersion')) {
|
||||||
|
trigger_error("Couldn't fetch server version. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->iControl->client->version = $this->iControl->client->getResponse();
|
||||||
|
return $this->iControl->client->version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch server system info
|
||||||
|
*/
|
||||||
|
public function getSystemInfo($forceRefresh = false, &$client = null) {
|
||||||
|
if (!$this->iControl->client && !$client) return null;
|
||||||
|
if (!$client) $client = $this->iControl->client;
|
||||||
|
if (isset($client->systemInfo) && !$forceRefresh) return $client->systemInfo;
|
||||||
|
if (!$client->query('GetSystemInfo')) {
|
||||||
|
trigger_error("Couldn't fetch server system info. " . $this->iControl->getClientErrorText($client));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$client->systemInfo = $client->getResponse();
|
||||||
|
return $client->systemInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch network status
|
||||||
|
*/
|
||||||
|
public function getNetworkStats($forceRefresh = false) {
|
||||||
|
if (isset($this->iControl->client->networkStats) && !$forceRefresh) return $this->iControl->client->networkStats;
|
||||||
|
if (!$this->iControl->client->query('GetNetworkStats')) {
|
||||||
|
trigger_error("Couldn't fetch network stats. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->iControl->client->networkStats = $this->iControl->client->getResponse();
|
||||||
|
return $this->iControl->client->networkStats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch current game mode
|
||||||
|
*
|
||||||
|
* @param bool $stringValue
|
||||||
|
* @param int $parseValue
|
||||||
|
* @return int | string
|
||||||
|
*/
|
||||||
|
public function getGameMode($stringValue = false, $parseValue = null) {
|
||||||
|
if (is_int($parseValue)) {
|
||||||
|
$gameMode = $parseValue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!$this->iControl->client->query('GetGameMode')) {
|
||||||
|
trigger_error("Couldn't fetch current game mode. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$gameMode = $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
if ($stringValue) {
|
||||||
|
switch ($gameMode) {
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
return 'Script';
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
return 'Rounds';
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
return 'TimeAttack';
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
{
|
||||||
|
return 'Team';
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
return 'Laps';
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
{
|
||||||
|
return 'Cup';
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
{
|
||||||
|
return 'Stunts';
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $gameMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch player info
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @return struct
|
||||||
|
*/
|
||||||
|
public function getPlayer($login, $detailed = false) {
|
||||||
|
if (!$login) return null;
|
||||||
|
$command = ($detailed ? 'GetDetailedPlayerInfo' : 'GetPlayerInfo');
|
||||||
|
if (!$this->iControl->client->query($command, $login)) {
|
||||||
|
trigger_error("Couldn't player info for '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all players
|
||||||
|
*/
|
||||||
|
public function getPlayers(&$client = null, &$purePlayers = null, &$pureSpectators = null) {
|
||||||
|
if (!$this->iControl->client && !$client) return null;
|
||||||
|
if (!$client) $client = $this->iControl->client;
|
||||||
|
$fetchLength = 30;
|
||||||
|
$offset = 0;
|
||||||
|
$players = array();
|
||||||
|
if (!is_array($purePlayers)) $purePlayers = array();
|
||||||
|
if (!is_array($pureSpectators)) $pureSpectators = array();
|
||||||
|
$tries = 0;
|
||||||
|
while ($tries < 10) {
|
||||||
|
if (!$client->query('GetPlayerList', $fetchLength, $offset)) {
|
||||||
|
trigger_error("Couldn't get player list. " . $this->iControl->getClientErrorText($client));
|
||||||
|
$tries++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$chunk = $client->getResponse();
|
||||||
|
$count = count($chunk);
|
||||||
|
$serverLogin = $this->getLogin($client);
|
||||||
|
for ($index = 0; $index < $count; $index++) {
|
||||||
|
$login = $chunk[$index]['Login'];
|
||||||
|
if ($login === $serverLogin) {
|
||||||
|
// Ignore server
|
||||||
|
unset($chunk[$index]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($chunk[$index]['SpectatorStatus'] > 0) {
|
||||||
|
// Pure spectator
|
||||||
|
array_push($pureSpectators, $chunk[$index]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Pure player
|
||||||
|
array_push($purePlayers, $chunk[$index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$players = array_merge($players, $chunk);
|
||||||
|
$offset += $count;
|
||||||
|
if ($count < $fetchLength) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $players;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve validation replay for given login
|
||||||
|
*
|
||||||
|
* @param string $login
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getValidationReplay($login) {
|
||||||
|
if (!$login) return null;
|
||||||
|
if (!$this->iControl->client->query('GetValidationReplay', $login)) {
|
||||||
|
trigger_error("Couldn't get validation replay of '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGhostReplay($login) {
|
||||||
|
$dataDir = $this->getDataDirectory();
|
||||||
|
if (!$this->checkAccess($dataDir)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build file name
|
||||||
|
$map = $this->getMap();
|
||||||
|
$gameMode = $this->getGameMode();
|
||||||
|
$time = time();
|
||||||
|
$fileName = 'Ghost.' . $login . '.' . $gameMode . '.' . $time . '.' . $map['UId'] . '.Replay.Gbx';
|
||||||
|
|
||||||
|
// Save ghost replay
|
||||||
|
if (!$this->iControl->client->query('SaveBestGhostsReplay', $login, self::GHOSTREPLAYDIR . $fileName)) {
|
||||||
|
trigger_error("Couldn't save ghost replay. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load replay file
|
||||||
|
$ghostReplay = file_get_contents($dataDir . 'Replays/' . self::GHOSTREPLAYDIR . $fileName);
|
||||||
|
if (!$ghostReplay) {
|
||||||
|
trigger_error("Couldn't retrieve saved ghost replay.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $ghostReplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch current map
|
||||||
|
*/
|
||||||
|
public function getMap() {
|
||||||
|
if (!$this->iControl->client) return null;
|
||||||
|
if (!$this->iControl->client->query('GetCurrentMapInfo')) {
|
||||||
|
trigger_error("Couldn't fetch map info. " . $this->iControl->getClientErrorText());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->iControl->client->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the server to have the given status
|
||||||
|
*/
|
||||||
|
public function waitForStatus($client, $statusCode = 4) {
|
||||||
|
$client->query('GetStatus');
|
||||||
|
$response = $client->getResponse();
|
||||||
|
// Check if server reached given status
|
||||||
|
if ($response['Code'] === 4) return true;
|
||||||
|
// Server not yet in given status -> Wait for it...
|
||||||
|
$waitBegin = time();
|
||||||
|
$timeoutTags = $this->iControl->config->xpath('timeout');
|
||||||
|
$maxWaitTime = (!empty($timeoutTags) ? (int) $timeoutTags[0] : 20);
|
||||||
|
$lastStatus = $response['Name'];
|
||||||
|
error_log("Waiting for server to reach status " . $statusCode . "...");
|
||||||
|
error_log("Current Status: " . $lastStatus);
|
||||||
|
while ($response['Code'] !== 4) {
|
||||||
|
sleep(1);
|
||||||
|
$client->query('GetStatus');
|
||||||
|
$response = $client->getResponse();
|
||||||
|
if ($lastStatus !== $response['Name']) {
|
||||||
|
error_log("New Status: " . $response['Name']);
|
||||||
|
$lastStatus = $response['Name'];
|
||||||
|
}
|
||||||
|
if (time() - $maxWaitTime > $waitBegin) {
|
||||||
|
// It took too long to reach the status
|
||||||
|
trigger_error(
|
||||||
|
"Server couldn't reach status " . $statusCode . " after " . $maxWaitTime . " seconds! " .
|
||||||
|
$this->iControl->getClientErrorText());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
297
application/core/stats.iControl.php
Normal file
297
application/core/stats.iControl.php
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stats class
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Stats {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const TABLE_STATS_SERVER = 'ic_stats_server';
|
||||||
|
const TABLE_STATS_PLAYERS = 'ic_stats_players';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constuct stats manager
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('stats.iControl.xml');
|
||||||
|
$this->loadSettings();
|
||||||
|
|
||||||
|
// Init database tables
|
||||||
|
$this->initTables();
|
||||||
|
|
||||||
|
// Register for needed callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_ENDMAP, $this, 'handleEndMap');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handlePlayerChat');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnect');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERDISCONNECT, $this, 'handlePlayerDisconnect');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_TM_PLAYERFINISH, $this, 'handlePlayerFinish');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the database tables
|
||||||
|
*/
|
||||||
|
private function initTables() {
|
||||||
|
$query = "";
|
||||||
|
|
||||||
|
// Server stats
|
||||||
|
$query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_STATS_SERVER . "` (
|
||||||
|
`index` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`day` date NOT NULL,
|
||||||
|
`connectCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`maxPlayerCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`playedMaps` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`finishCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`index`),
|
||||||
|
UNIQUE KEY `day` (`day`)
|
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores server stats' AUTO_INCREMENT=1;";
|
||||||
|
|
||||||
|
// Player stats
|
||||||
|
$query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_STATS_PLAYERS . "` (
|
||||||
|
`index` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`Login` varchar(100) NOT NULL,
|
||||||
|
`playTime` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`connectCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`chatCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`finishCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`hitCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`eliminationCount` int(11) NOT NULL DEFAULT '0',
|
||||||
|
`lastJoin` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
|
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`index`),
|
||||||
|
UNIQUE KEY `Login` (`Login`)
|
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Tracks player stats' AUTO_INCREMENT=1;";
|
||||||
|
|
||||||
|
// Perform queries
|
||||||
|
if (!$this->iControl->database->multiQuery($query)) {
|
||||||
|
trigger_error("Creating stats tables failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings from config
|
||||||
|
*/
|
||||||
|
private function loadSettings() {
|
||||||
|
$this->settings = new \stdClass();
|
||||||
|
|
||||||
|
$this->settings->track_server_connects = Tools::checkSetting($this->config, 'track_server_connects');
|
||||||
|
$this->settings->track_server_max_players = Tools::checkSetting($this->config, 'track_server_max_players');
|
||||||
|
$this->settings->track_server_played_maps = Tools::checkSetting($this->config, 'track_server_played_maps');
|
||||||
|
$this->settings->track_server_finishes = Tools::checkSetting($this->config, 'track_server_finishes');
|
||||||
|
|
||||||
|
$this->settings->track_player_connects = Tools::checkSetting($this->config, 'track_player_connects');
|
||||||
|
$this->settings->track_player_playtime = Tools::checkSetting($this->config, 'track_player_playtime');
|
||||||
|
$this->settings->track_player_chats = Tools::checkSetting($this->config, 'track_player_chats');
|
||||||
|
$this->settings->track_player_finishes = Tools::checkSetting($this->config, 'track_player_finishes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle EndMap callback
|
||||||
|
*/
|
||||||
|
public function handleEndMap($callback) {
|
||||||
|
$multiquery = "";
|
||||||
|
|
||||||
|
// Track played server maps
|
||||||
|
if ($this->settings->track_server_played_maps) {
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
|
||||||
|
`day`,
|
||||||
|
`playedMaps`
|
||||||
|
) VALUES (
|
||||||
|
CURDATE(),
|
||||||
|
1
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`playedMaps` = `playedMaps` + VALUES(`playedMaps`)
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform query
|
||||||
|
if (!$this->iControl->database->multiQuery($multiquery)) {
|
||||||
|
trigger_error("Perform queries on end map failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerChat callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerChat($callback) {
|
||||||
|
if ($callback[1][0] <= 0) return;
|
||||||
|
$multiquery = "";
|
||||||
|
$login = $callback[1][1];
|
||||||
|
|
||||||
|
// Track chats
|
||||||
|
if ($this->settings->track_player_chats) {
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
|
||||||
|
`Login`,
|
||||||
|
`chatCount`
|
||||||
|
) VALUES (
|
||||||
|
'" . $this->iControl->database->escape($login) . "',
|
||||||
|
1
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`chatCount` = `chatCount` + VALUES(`chatCount`)
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform query
|
||||||
|
if (!$this->iControl->database->multiQuery($multiquery)) {
|
||||||
|
trigger_error("Perform queries on player chat failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerConnect callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerConnect($callback) {
|
||||||
|
$multiquery = "";
|
||||||
|
$login = $callback[1][0];
|
||||||
|
|
||||||
|
// Track server connect
|
||||||
|
if ($this->settings->track_server_connects) {
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
|
||||||
|
`day`,
|
||||||
|
`connectCount`
|
||||||
|
) VALUES (
|
||||||
|
CURDATE(),
|
||||||
|
1
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`connectCount` = `connectCount` + VALUES(`connectCount`)
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track server max players
|
||||||
|
if ($this->settings->track_server_max_players) {
|
||||||
|
$players = $this->iControl->server->getPlayers();
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
|
||||||
|
`day`,
|
||||||
|
`maxPlayerCount`
|
||||||
|
) VALUES (
|
||||||
|
CURDATE(),
|
||||||
|
" . count($players) . "
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`maxPlayerCount` = GREATEST(`maxPlayerCount`, VALUES(`maxPlayerCount`))
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track player connect
|
||||||
|
if ($this->settings->track_player_connects) {
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
|
||||||
|
`Login`,
|
||||||
|
`lastJoin`,
|
||||||
|
`connectCount`
|
||||||
|
) VALUES (
|
||||||
|
'" . $this->iControl->database->escape($login) . "',
|
||||||
|
NOW(),
|
||||||
|
1
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`lastJoin` = VALUES(`lastJoin`),
|
||||||
|
`connectCount` = `connectCount` + VALUES(`connectCount`)
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform query
|
||||||
|
if (!$this->iControl->database->multiQuery($multiquery)) {
|
||||||
|
trigger_error("Perform queries on player connect failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerDisconnect callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerDisconnect($callback) {
|
||||||
|
$multiquery = "";
|
||||||
|
$login = $callback[1][0];
|
||||||
|
|
||||||
|
// Track player playtime
|
||||||
|
if ($this->settings->track_player_playtime) {
|
||||||
|
$query = "SELECT `lastJoin` FROM `" . self::TABLE_STATS_PLAYERS . "`
|
||||||
|
WHERE `Login` = '" . $this->iControl->database->escape($login) . "'
|
||||||
|
;";
|
||||||
|
$result = $this->iControl->database->query($query);
|
||||||
|
if (!$result) {
|
||||||
|
// Error
|
||||||
|
trigger_error("Error selecting player join time from '" . $login . "'.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add play time
|
||||||
|
while ($row = $result->fetch_object()) {
|
||||||
|
if (!property_exists($row, 'lastJoin')) continue;
|
||||||
|
$lastJoin = strtotime($row->lastJoin);
|
||||||
|
$lastJoin = ($lastJoin > $this->iControl->startTime ? $lastJoin : $this->iControl->startTime);
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
|
||||||
|
`Login`,
|
||||||
|
`playTime`
|
||||||
|
) VALUES (
|
||||||
|
'" . $this->iControl->database->escape($login) . "',
|
||||||
|
TIMESTAMPDIFF(SECOND, '" . Tools::timeToTimestamp($lastJoin) . "', NOW())
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`playTime` = `playTime` + VALUES(`playTime`)
|
||||||
|
;";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform query
|
||||||
|
if (!$this->iControl->database->multiQuery($multiquery)) {
|
||||||
|
trigger_error("Perform queries on player connect failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the PlayerFinish callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerFinish($callback) {
|
||||||
|
if ($callback[1][0] <= 0) return;
|
||||||
|
if ($callback[1][2] <= 0) return;
|
||||||
|
|
||||||
|
$multiquery = "";
|
||||||
|
$login = $callback[1][1];
|
||||||
|
|
||||||
|
// Track server finishes
|
||||||
|
if ($this->settings->track_server_finishes) {
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
|
||||||
|
`day`,
|
||||||
|
`finishCount`
|
||||||
|
) VALUES (
|
||||||
|
CURDATE(),
|
||||||
|
1
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`finishCount` = `finishCount` + VALUES(`finishCount`)
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track player finishes
|
||||||
|
if ($this->settings->track_player_finishes) {
|
||||||
|
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
|
||||||
|
`Login`,
|
||||||
|
`finishCount`
|
||||||
|
) VALUES (
|
||||||
|
'" . $this->iControl->database->escape($login) . "',
|
||||||
|
1
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`finishCount` = `finishCount` + VALUES(`finishCount`)
|
||||||
|
;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform query
|
||||||
|
if (!$this->iControl->database->multiQuery($multiquery)) {
|
||||||
|
trigger_error("Perform queries on player finish failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
240
application/core/tools.iControl.php
Normal file
240
application/core/tools.iControl.php
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for basic tools
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Tools {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given setting is enabled
|
||||||
|
*
|
||||||
|
* @param simple_xml_element $config
|
||||||
|
* @param string $setting
|
||||||
|
*/
|
||||||
|
public static function checkSetting($config, $setting) {
|
||||||
|
$settings = $config->xpath('//' . $setting);
|
||||||
|
if (empty($settings)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach ($settings as $setting) {
|
||||||
|
return self::toBool((string) $setting[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given data describes a player
|
||||||
|
*
|
||||||
|
* @param array $player
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isPlayer($player) {
|
||||||
|
if (!$player || !is_array($player)) return false;
|
||||||
|
if (!array_key_exists('PlayerId', $player) || !is_int($player['PlayerId']) || $player['PlayerId'] <= 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given time int to mysql timestamp
|
||||||
|
*
|
||||||
|
* @param int $time
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function timeToTimestamp($time) {
|
||||||
|
return date("Y-m-d H:i:s", $time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add alignment attributes to an xml element
|
||||||
|
*
|
||||||
|
* @param simple_xml_element $xml
|
||||||
|
* @param string $halign
|
||||||
|
* @param string $valign
|
||||||
|
*/
|
||||||
|
public static function addAlignment($xml, $halign = 'center', $valign = 'center2') {
|
||||||
|
if (!is_object($xml) || !method_exists($xml, 'addAttribute')) return;
|
||||||
|
if (!property_exists($xml, 'halign')) $xml->addAttribute('halign', $halign);
|
||||||
|
if (!property_exists($xml, 'valign')) $xml->addAttribute('valign', $valign);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add translate attribute to an xml element
|
||||||
|
*
|
||||||
|
* @param simple_xml_element $xml
|
||||||
|
* @param bool $translate
|
||||||
|
*/
|
||||||
|
public static function addTranslate($xml, $translate = true) {
|
||||||
|
if (!is_object($xml) || !method_exists($xml, 'addAttribute')) return;
|
||||||
|
if (!property_exists($xml, 'translate')) $xml->addAttribute('translate', ($translate ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a remote file
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @return string || null
|
||||||
|
*/
|
||||||
|
public static function loadFile($url) {
|
||||||
|
if (!$url) return false;
|
||||||
|
$urlData = parse_url($url);
|
||||||
|
$port = (isset($urlData['port']) ? $urlData['port'] : 80);
|
||||||
|
|
||||||
|
$fsock = fsockopen($urlData['host'], $port);
|
||||||
|
stream_set_timeout($fsock, 3);
|
||||||
|
|
||||||
|
$query = 'GET ' . $urlData['path'] . ' HTTP/1.0' . PHP_EOL;
|
||||||
|
$query .= 'Host: ' . $urlData['host'] . PHP_EOL;
|
||||||
|
$query .= 'Content-Type: UTF-8' . PHP_EOL;
|
||||||
|
$query .= 'User-Agent: iControl v' . iControl::VERSION . PHP_EOL;
|
||||||
|
$query .= PHP_EOL;
|
||||||
|
|
||||||
|
fwrite($fsock, $query);
|
||||||
|
|
||||||
|
$buffer = '';
|
||||||
|
$info = array('timed_out' => false);
|
||||||
|
while (!feof($fsock) && !$info['timed_out']) {
|
||||||
|
$buffer .= fread($fsock, 1024);
|
||||||
|
$info = stream_get_meta_data($fsock);
|
||||||
|
}
|
||||||
|
fclose($fsock);
|
||||||
|
|
||||||
|
if ($info['timed_out'] || !$buffer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (substr($buffer, 9, 3) != "200") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = explode("\r\n\r\n", $buffer, 2);
|
||||||
|
|
||||||
|
if (count($result) < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given time (milliseconds)
|
||||||
|
*
|
||||||
|
* @param int $time
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function formatTime($time) {
|
||||||
|
if (!is_int($time)) $time = (int) $time;
|
||||||
|
$milliseconds = $time % 1000;
|
||||||
|
$seconds = floor($time / 1000);
|
||||||
|
$minutes = floor($seconds / 60);
|
||||||
|
$hours = floor($minutes / 60);
|
||||||
|
$minutes -= $hours * 60;
|
||||||
|
$seconds -= $hours * 60 + $minutes * 60;
|
||||||
|
$format = ($hours > 0 ? $hours . ':' : '');
|
||||||
|
$format .= ($hours > 0 && $minutes < 10 ? '0' : '') . $minutes . ':';
|
||||||
|
$format .= ($seconds < 10 ? '0' : '') . $seconds . ':';
|
||||||
|
$format .= ($milliseconds < 100 ? '0' : '') . ($milliseconds < 10 ? '0' : '') . $milliseconds;
|
||||||
|
return $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert given data to real boolean
|
||||||
|
*
|
||||||
|
* @param
|
||||||
|
* mixed data
|
||||||
|
*/
|
||||||
|
public static function toBool($var) {
|
||||||
|
if ($var === true) return true;
|
||||||
|
if ($var === false) return false;
|
||||||
|
if ($var === null) return false;
|
||||||
|
if (is_object($var)) {
|
||||||
|
$var = (string) $var;
|
||||||
|
}
|
||||||
|
if (is_int($var)) {
|
||||||
|
return ($var > 0);
|
||||||
|
}
|
||||||
|
else if (is_string($var)) {
|
||||||
|
$text = strtolower($var);
|
||||||
|
if ($text === 'true' || $text === 'yes') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ($text === 'false' || $text === 'no') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ((int) $text > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (bool) $var;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given boolean to an int representation
|
||||||
|
*
|
||||||
|
* @param bool $bool
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function boolToInt($bool) {
|
||||||
|
return ($bool ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build new simple xml element
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $id
|
||||||
|
* @return \SimpleXMLElement
|
||||||
|
*/
|
||||||
|
public static function newManialinkXml($id = null) {
|
||||||
|
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><manialink/>');
|
||||||
|
$xml->addAttribute('version', '1');
|
||||||
|
if ($id) $xml->addAttribute('id', $id);
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load config xml-file
|
||||||
|
*
|
||||||
|
* @param string $fileName
|
||||||
|
* @return \SimpleXMLElement
|
||||||
|
*/
|
||||||
|
public static function loadConfig($fileName) {
|
||||||
|
// Load config file from configs folder
|
||||||
|
$fileLocation = ICONTROL . '/configs/' . $fileName;
|
||||||
|
if (!file_exists($fileLocation)) {
|
||||||
|
trigger_error("Config file doesn't exist! (" . $fileName . ")", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
return simplexml_load_file($fileLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the given manialink to players
|
||||||
|
*
|
||||||
|
* @param string $manialink
|
||||||
|
* @param array $logins
|
||||||
|
*/
|
||||||
|
public static function sendManialinkPage($client, $manialink, $logins = null, $timeout = 0, $hideOnClick = false) {
|
||||||
|
if (!$client || !$manialink) return;
|
||||||
|
if (!$logins) {
|
||||||
|
// Send manialink to all players
|
||||||
|
$client->query('SendDisplayManialinkPage', $manialink, $timeout, $hideOnClick);
|
||||||
|
}
|
||||||
|
else if (is_array($logins)) {
|
||||||
|
// Send manialink to players
|
||||||
|
foreach ($logins as $login) {
|
||||||
|
$client->query('SendDisplayManialinkPageToLogin', $login, $manialink, $timeout, $hideOnClick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (is_string($logins)) {
|
||||||
|
// Send manialink to player
|
||||||
|
$client->query('SendDisplayManialinkPageToLogin', $logins, $manialink, $timeout, $hideOnClick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
2
application/iControl.bat
Normal file
2
application/iControl.bat
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
REM set the path to your php.exe here
|
||||||
|
START "" /B "D:\Programme\xampp\php\php.exe" -f "iControl.php" 2>&1
|
26
application/iControl.php
Normal file
26
application/iControl.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
define('ICONTROL', __DIR__);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/core/core.iControl.php';
|
||||||
|
|
||||||
|
// Set process settings
|
||||||
|
ini_set('memory_limit', '128M');
|
||||||
|
if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set')) {
|
||||||
|
date_default_timezone_set(@date_default_timezone_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_reporting', -1);
|
||||||
|
ini_set('error_log', 'iControl_' . getmypid() . '.log');
|
||||||
|
|
||||||
|
// Start iControl
|
||||||
|
error_log('Loading iControl v' . iControl::VERSION . '!');
|
||||||
|
|
||||||
|
$iControl = new iControl();
|
||||||
|
$iControl->run(true);
|
||||||
|
|
||||||
|
?>
|
3
application/iControl.sh
Normal file
3
application/iControl.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
php iControl.php 2>&1 &
|
||||||
|
echo $! > iControl.pid
|
85
application/plugins/chatlog.plugin.php
Normal file
85
application/plugins/chatlog.plugin.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iControl Chatlog Plugin
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Plugin_Chatlog {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const VERSION = '1.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
private $settings = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constuct chatlog plugin
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('chatlog.plugin.xml');
|
||||||
|
|
||||||
|
// Check for enabled setting
|
||||||
|
if (!Tools::toBool($this->config->enabled)) return;
|
||||||
|
|
||||||
|
// Load settings
|
||||||
|
$this->loadSettings();
|
||||||
|
|
||||||
|
// Register for callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handlePlayerChatCallback');
|
||||||
|
|
||||||
|
error_log('Chatlog Pugin v' . self::VERSION . ' ready!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings from config
|
||||||
|
*/
|
||||||
|
private function loadSettings() {
|
||||||
|
$this->settings = new \stdClass();
|
||||||
|
|
||||||
|
// File name
|
||||||
|
$fileName = (string) $this->config->filename;
|
||||||
|
$this->settings->fileName = ICONTROL . '/' . $fileName;
|
||||||
|
|
||||||
|
// log_server_messages
|
||||||
|
$log_server_messages = $this->config->xpath('log_server_messages');
|
||||||
|
$this->settings->log_server_messages = ($log_server_messages ? (Tools::toBool($log_server_messages[0])) : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerChat callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerChatCallback($callback) {
|
||||||
|
$data = $callback[1];
|
||||||
|
if ($data[0] <= 0 && !$this->settings->log_server_messages) {
|
||||||
|
// Skip server message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->logText($data[2], $data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given message
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param string $login
|
||||||
|
*/
|
||||||
|
private function logText($text, $login = null) {
|
||||||
|
$message = date(iControl::DATE) . '>> ' . ($login ? $login . ': ' : '') . $text . PHP_EOL;
|
||||||
|
file_put_contents($this->settings->fileName, $message, FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
305
application/plugins/karma.plugin.php
Normal file
305
application/plugins/karma.plugin.php
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iControl Karma Plugin
|
||||||
|
*
|
||||||
|
* @author : steeffeen
|
||||||
|
*/
|
||||||
|
class Plugin_Karma {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const VERSION = '1.0';
|
||||||
|
const MLID_KARMA = 'KarmaPlugin.MLID';
|
||||||
|
const TABLE_KARMA = 'ic_karma';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
private $sendManialinkRequested = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct plugin
|
||||||
|
*
|
||||||
|
* @param object $iControl
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('karma.plugin.xml');
|
||||||
|
if (!Tools::toBool($this->config->enabled)) return;
|
||||||
|
|
||||||
|
// Init database
|
||||||
|
$this->initDatabase();
|
||||||
|
|
||||||
|
// Register for callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_ONINIT, $this, 'handleOnInitCallback');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_BEGINMAP, $this, 'handleBeginMapCallback');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnectCallback');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERMANIALINKPAGEANSWER, $this,
|
||||||
|
'handleManialinkPageAnswerCallback');
|
||||||
|
|
||||||
|
error_log('Karma Pugin v' . self::VERSION . ' ready!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repetitive actions
|
||||||
|
*/
|
||||||
|
public function loop() {
|
||||||
|
if ($this->sendManialinkRequested > 0 && $this->sendManialinkRequested <= time()) {
|
||||||
|
$this->sendManialinkRequested = -1;
|
||||||
|
|
||||||
|
// Send manialink to all players
|
||||||
|
$players = $this->iControl->server->getPlayers();
|
||||||
|
foreach ($players as $player) {
|
||||||
|
$login = $player['Login'];
|
||||||
|
$manialink = $this->buildManialink($login);
|
||||||
|
if (!$manialink) {
|
||||||
|
// Skip and retry
|
||||||
|
$this->sendManialinkRequested = time() + 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Tools::sendManialinkPage($this->iControl->client, $manialink->asXml(), $login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle OnInit iControl callback
|
||||||
|
*
|
||||||
|
* @param array $callback
|
||||||
|
*/
|
||||||
|
public function handleOnInitCallback($callback) {
|
||||||
|
// Send manialink to all players once
|
||||||
|
$this->sendManialinkRequested = time() + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle iControl BeginMap callback
|
||||||
|
*
|
||||||
|
* @param array $callback
|
||||||
|
*/
|
||||||
|
public function handleBeginMapCallback($callback) {
|
||||||
|
// Send manialink to all players once
|
||||||
|
$this->sendManialinkRequested = time() + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerConnect callback
|
||||||
|
*
|
||||||
|
* @param array $callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerConnectCallback($callback) {
|
||||||
|
$login = $callback[1][0];
|
||||||
|
$manialink = $this->buildManialink($login);
|
||||||
|
if (!$manialink) return;
|
||||||
|
Tools::sendManialinkPage($this->iControl->client, $manialink->asXml(), $login);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create necessary tables
|
||||||
|
*/
|
||||||
|
private function initDatabase() {
|
||||||
|
$query = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_KARMA . "` (
|
||||||
|
`index` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`mapIndex` int(11) NOT NULL,
|
||||||
|
`playerIndex` int(11) NOT NULL,
|
||||||
|
`vote` tinyint(1) NOT NULL,
|
||||||
|
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`index`),
|
||||||
|
UNIQUE KEY `player_map_vote` (`mapIndex`, `playerIndex`)
|
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Save players map votes' AUTO_INCREMENT=1;";
|
||||||
|
$result = $this->iControl->database->query($query);
|
||||||
|
if ($this->iControl->database->mysqli->error) {
|
||||||
|
trigger_error('MySQL Error on creating karma table. ' . $this->iControl->database->mysqli->error, E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle ManialinkPageAnswer callback
|
||||||
|
*
|
||||||
|
* @param array $callback
|
||||||
|
*/
|
||||||
|
public function handleManialinkPageAnswerCallback($callback) {
|
||||||
|
$action = $callback[1][2];
|
||||||
|
if (substr($action, 0, strlen(self::MLID_KARMA)) !== self::MLID_KARMA) return;
|
||||||
|
|
||||||
|
// Get vote
|
||||||
|
$action = substr($action, -4);
|
||||||
|
$vote = null;
|
||||||
|
switch ($action) {
|
||||||
|
case '.pos':
|
||||||
|
{
|
||||||
|
$vote = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '.neu':
|
||||||
|
{
|
||||||
|
$vote = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '.neg':
|
||||||
|
{
|
||||||
|
$vote = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save vote
|
||||||
|
$login = $callback[1][1];
|
||||||
|
$playerIndex = $this->iControl->database->getPlayerIndex($login);
|
||||||
|
$map = $this->iControl->server->getMap();
|
||||||
|
$mapIndex = $this->iControl->database->getMapIndex($map['UId']);
|
||||||
|
$query = "INSERT INTO `" . self::TABLE_KARMA . "` (
|
||||||
|
`mapIndex`,
|
||||||
|
`playerIndex`,
|
||||||
|
`vote`
|
||||||
|
) VALUES (
|
||||||
|
" . $mapIndex . ",
|
||||||
|
" . $playerIndex . ",
|
||||||
|
" . $vote . "
|
||||||
|
) ON DUPLICATE KEY UPDATE
|
||||||
|
`vote` = VALUES(`vote`);";
|
||||||
|
$result = $this->iControl->database->query($query);
|
||||||
|
if (!$result) return;
|
||||||
|
|
||||||
|
// Send success message
|
||||||
|
$this->iControl->chat->sendSuccess('Vote successfully updated!', $login);
|
||||||
|
|
||||||
|
// Send updated manialink
|
||||||
|
$this->sendManialinkRequested = time() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build karma voting manialink xml for the given login
|
||||||
|
*/
|
||||||
|
private function buildManialink($login) {
|
||||||
|
// Get config
|
||||||
|
$title = (string) $this->config->title;
|
||||||
|
$pos_x = (float) $this->config->pos_x;
|
||||||
|
$pos_y = (float) $this->config->pos_y;
|
||||||
|
|
||||||
|
$mysqli = $this->iControl->database->mysqli;
|
||||||
|
|
||||||
|
// Get indezes
|
||||||
|
$playerIndex = $this->iControl->database->getPlayerIndex($login);
|
||||||
|
if ($playerIndex === null) return null;
|
||||||
|
$map = $this->iControl->server->getMap();
|
||||||
|
if (!$map) return null;
|
||||||
|
$mapIndex = $this->iControl->database->getMapIndex($map['UId']);
|
||||||
|
if ($mapIndex === null) return null;
|
||||||
|
|
||||||
|
// Get votings
|
||||||
|
$query = "SELECT
|
||||||
|
(SELECT `vote` FROM `" .
|
||||||
|
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `playerIndex` = " . $playerIndex . ") as `playerVote`,
|
||||||
|
(SELECT COUNT(`vote`) FROM `" .
|
||||||
|
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `vote` = 1) AS `positiveVotes`,
|
||||||
|
(SELECT COUNT(`vote`) FROM `" .
|
||||||
|
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `vote` = 0) AS `neutralVotes`,
|
||||||
|
(SELECT COUNT(`vote`) FROM `" .
|
||||||
|
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `vote` = -1) AS `negativeVotes`
|
||||||
|
FROM `" . self::TABLE_KARMA . "`;";
|
||||||
|
$result = $mysqli->query($query);
|
||||||
|
if ($mysqli->error) {
|
||||||
|
trigger_error('MySQL ERROR: ' . $mysqli->error);
|
||||||
|
}
|
||||||
|
$votes = $result->fetch_assoc();
|
||||||
|
if (!$votes) {
|
||||||
|
$votes = array('playerVote' => null, 'positiveVotes' => 0, 'neutralVotes' => 0, 'negativeVotes' => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build manialink
|
||||||
|
$xml = Tools::newManialinkXml(self::MLID_KARMA);
|
||||||
|
|
||||||
|
$frameXml = $xml->addChild('frame');
|
||||||
|
$frameXml->addAttribute('posn', $pos_x . ' ' . $pos_y);
|
||||||
|
|
||||||
|
// Title
|
||||||
|
$labelXml = $frameXml->addChild('label');
|
||||||
|
Tools::addAlignment($labelXml);
|
||||||
|
$labelXml->addAttribute('posn', '0 4.5 -1');
|
||||||
|
$labelXml->addAttribute('sizen', '22 0');
|
||||||
|
$labelXml->addAttribute('style', 'TextTitle1');
|
||||||
|
$labelXml->addAttribute('textsize', '1');
|
||||||
|
$labelXml->addAttribute('text', $title);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
$quadXml = $frameXml->addChild('quad');
|
||||||
|
Tools::addAlignment($quadXml);
|
||||||
|
$quadXml->addAttribute('sizen', '23 15 -2');
|
||||||
|
$quadXml->addAttribute('style', 'Bgs1InRace');
|
||||||
|
$quadXml->addAttribute('substyle', 'BgTitleShadow');
|
||||||
|
|
||||||
|
// Votes
|
||||||
|
for ($i = 1; $i >= -1; $i--) {
|
||||||
|
$x = $i * 7.;
|
||||||
|
|
||||||
|
// Vote button
|
||||||
|
$quadXml = $frameXml->addChild('quad');
|
||||||
|
Tools::addAlignment($quadXml);
|
||||||
|
$quadXml->addAttribute('posn', $x . ' 0 0');
|
||||||
|
$quadXml->addAttribute('sizen', '6 6');
|
||||||
|
$quadXml->addAttribute('style', 'Icons64x64_1');
|
||||||
|
|
||||||
|
// Vote count
|
||||||
|
$labelXml = $frameXml->addChild('label');
|
||||||
|
Tools::addAlignment($labelXml);
|
||||||
|
$labelXml->addAttribute('posn', $x . ' -4.5 0');
|
||||||
|
$labelXml->addAttribute('style', 'TextTitle1');
|
||||||
|
$labelXml->addAttribute('textsize', '2');
|
||||||
|
|
||||||
|
if ((string) $i === $votes['playerVote']) {
|
||||||
|
// Player vote X
|
||||||
|
$voteQuadXml = $frameXml->addChild('quad');
|
||||||
|
Tools::addAlignment($voteQuadXml);
|
||||||
|
$voteQuadXml->addAttribute('posn', $x . ' 0 1');
|
||||||
|
$voteQuadXml->addAttribute('sizen', '6 6');
|
||||||
|
$voteQuadXml->addAttribute('style', 'Icons64x64_1');
|
||||||
|
$voteQuadXml->addAttribute('substyle', 'Close');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($i) {
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
// Positive
|
||||||
|
$quadXml->addAttribute('substyle', 'LvlGreen');
|
||||||
|
$quadXml->addAttribute('action', self::MLID_KARMA . '.pos');
|
||||||
|
$labelXml->addAttribute('text', $votes['positiveVotes']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
// Neutral
|
||||||
|
$quadXml->addAttribute('substyle', 'LvlYellow');
|
||||||
|
$quadXml->addAttribute('action', self::MLID_KARMA . '.neu');
|
||||||
|
$labelXml->addAttribute('text', $votes['neutralVotes']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case -1:
|
||||||
|
{
|
||||||
|
// Negative
|
||||||
|
$quadXml->addAttribute('substyle', 'LvlRed');
|
||||||
|
$quadXml->addAttribute('action', self::MLID_KARMA . '.neg');
|
||||||
|
$labelXml->addAttribute('text', $votes['negativeVotes']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
63
application/plugins/obstacle.plugin.php
Normal file
63
application/plugins/obstacle.plugin.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iControl Obstacle Plugin
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Plugin_Obstacle extends Plugin {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const CB_JUMPTO = 'Obstacle.JumpTo';
|
||||||
|
const VERSION = '1.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constuct obstacle plugin
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('obstacle.plugin.xml');
|
||||||
|
|
||||||
|
// Check for enabled setting
|
||||||
|
if (!Tools::toBool($this->config->enabled)) return;
|
||||||
|
|
||||||
|
// Register for jump command
|
||||||
|
$this->iControl->commands->registerCommandHandler('jumpto', $this, 'command_jumpto');
|
||||||
|
|
||||||
|
error_log('Obstacle Pugin v' . self::VERSION . ' ready!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle jumpto command
|
||||||
|
*/
|
||||||
|
public function command_jumpto($chat) {
|
||||||
|
$login = $chat[1][1];
|
||||||
|
$rightLevel = (string) $this->config->jumps_rightlevel;
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, $rightLevel)) {
|
||||||
|
// Not allowed
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Send jump callback
|
||||||
|
$params = explode(' ', $chat[1][2], 2);
|
||||||
|
$param = $login . ";" . $params[1] . ";";
|
||||||
|
if (!$this->iControl->client->query('TriggerModeScriptEvent', self::CB_JUMPTO, $param)) {
|
||||||
|
trigger_error("Couldn't send jump callback for '" . $login . "'. " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
37
application/plugins/plugin.iControl.php
Normal file
37
application/plugins/plugin.iControl.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract iControl plugin class
|
||||||
|
*/
|
||||||
|
abstract class Plugin_Name {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const VERSION = '0.1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct plugin
|
||||||
|
*
|
||||||
|
* @param object $iControl
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
error_log('Pugin v' . self::VERSION . ' ready!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform actions during each loop
|
||||||
|
*/
|
||||||
|
public function loop() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
1216
application/plugins/records.plugin.php
Normal file
1216
application/plugins/records.plugin.php
Normal file
File diff suppressed because it is too large
Load Diff
677
application/plugins/united.plugin.php
Normal file
677
application/plugins/united.plugin.php
Normal file
@ -0,0 +1,677 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace iControl;
|
||||||
|
|
||||||
|
// TODO: Jump message "now playing stadium"
|
||||||
|
// TODO: put inactive server in idle (keeping same map)
|
||||||
|
// TODO: let next server wait for the first player
|
||||||
|
// TODO: check compatibility with other modes (laps, ...)
|
||||||
|
// TODO: max players setting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iControl United Plugin
|
||||||
|
*
|
||||||
|
* @author steeffeen
|
||||||
|
*/
|
||||||
|
class Plugin_United {
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
const VERSION = '1.0';
|
||||||
|
const ML_ADDFAVORITE = 'MLID_UnitedPlugin.AddFavorite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private properties
|
||||||
|
*/
|
||||||
|
private $iControl = null;
|
||||||
|
|
||||||
|
private $config = null;
|
||||||
|
|
||||||
|
private $settings = null;
|
||||||
|
|
||||||
|
private $gameServer = array();
|
||||||
|
|
||||||
|
private $lobbies = array();
|
||||||
|
|
||||||
|
private $currentClientIndex = 0;
|
||||||
|
|
||||||
|
private $lastStatusCheck = 0;
|
||||||
|
|
||||||
|
private $finishedBegin = -1;
|
||||||
|
|
||||||
|
private $switchServerRequested = -1;
|
||||||
|
|
||||||
|
private $manialinks = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constuct plugin
|
||||||
|
*/
|
||||||
|
public function __construct($iControl) {
|
||||||
|
$this->iControl = $iControl;
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
$this->config = Tools::loadConfig('united.plugin.xml');
|
||||||
|
$this->loadSettings();
|
||||||
|
|
||||||
|
// Check for enabled setting
|
||||||
|
if (!$this->settings->enabled) return;
|
||||||
|
|
||||||
|
// Load clients
|
||||||
|
$this->loadClients();
|
||||||
|
|
||||||
|
// Register for callbacks
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_ONINIT, $this, 'handleOnInitCallback');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'handle5Seconds');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnect');
|
||||||
|
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERMANIALINKPAGEANSWER, $this,
|
||||||
|
'handleManialinkPageAnswer');
|
||||||
|
|
||||||
|
// Register for commands
|
||||||
|
$this->iControl->commands->registerCommandHandler('nextserver', $this, 'handleNextServerCommand');
|
||||||
|
|
||||||
|
if ($this->settings->widgets_enabled) {
|
||||||
|
// Build addfavorite manialink
|
||||||
|
$this->buildFavoriteManialink();
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log('United Pugin v' . self::VERSION . ' ready!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle iControl OnInit callback
|
||||||
|
*
|
||||||
|
* @param array $callback
|
||||||
|
*/
|
||||||
|
public function handleOnInitCallback($callback) {
|
||||||
|
if ($this->settings->widgets_enabled) {
|
||||||
|
// Send widgets to all players
|
||||||
|
|
||||||
|
if (Tools::toBool($this->config->widgets->addfavorite->enabled)) {
|
||||||
|
// Send favorite widget
|
||||||
|
if (!$this->iControl->client->query('SendDisplayManialinkPage', $this->manialinks[self::ML_ADDFAVORITE]->asXml(), 0,
|
||||||
|
false)) {
|
||||||
|
trigger_error("Couldn't send favorite widget! " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings from config
|
||||||
|
*/
|
||||||
|
private function loadSettings() {
|
||||||
|
$this->settings = new \stdClass();
|
||||||
|
|
||||||
|
// Enabled
|
||||||
|
$this->settings->enabled = Tools::toBool($this->config->enabled);
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
$timeout = $this->iControl->server->config->xpath('timeout');
|
||||||
|
if ($timeout) {
|
||||||
|
$this->settings->timeout = (int) $timeout[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->settings->timeout = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game mode
|
||||||
|
$mode = $this->config->xpath('mode');
|
||||||
|
if ($mode) {
|
||||||
|
$mode = (int) $mode[0];
|
||||||
|
if ($mode < 1 || $mode > 6) {
|
||||||
|
$this->settings->gamemode = 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->settings->gamemode = $mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server status
|
||||||
|
$hide_game_server = $this->config->xpath('hide_game_server');
|
||||||
|
if ($hide_game_server) {
|
||||||
|
$this->settings->hide_game_server = Tools::toBool($hide_game_server[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->settings->hide_game_server = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passwords
|
||||||
|
$lobbyPassword = $this->config->xpath('lobbies/password');
|
||||||
|
if ($lobbyPassword) {
|
||||||
|
$this->settings->lobbyPassword = (string) $lobbyPassword[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->settings->lobbyPassword = '';
|
||||||
|
}
|
||||||
|
$gamePassword = $this->config->xpath('gameserver/password');
|
||||||
|
if ($gamePassword) {
|
||||||
|
$this->settings->gamePassword = (string) $gamePassword[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->settings->gamePassword = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widgets
|
||||||
|
$this->settings->widgets_enabled = Tools::toBool($this->config->widgets->enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop events on clients
|
||||||
|
*/
|
||||||
|
public function loop() {
|
||||||
|
if (!$this->settings->enabled) return;
|
||||||
|
|
||||||
|
// Check callbacks all clients
|
||||||
|
$clients = array_merge($this->gameServer, $this->lobbies);
|
||||||
|
$currentServer = $this->gameServer[$this->currentClientIndex];
|
||||||
|
foreach ($clients as $index => $client) {
|
||||||
|
$client->resetError();
|
||||||
|
$client->readCB();
|
||||||
|
$callbacks = $client->getCBResponses();
|
||||||
|
if (!is_array($callbacks) || $client->isError()) {
|
||||||
|
trigger_error("Error reading server callbacks! " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($client == $currentServer) {
|
||||||
|
// Currently active game server
|
||||||
|
foreach ($callbacks as $index => $callback) {
|
||||||
|
$callbackName = $callback[0];
|
||||||
|
switch ($callbackName) {
|
||||||
|
case Callbacks::CB_MP_ENDMAP:
|
||||||
|
{
|
||||||
|
$this->switchToNextServer(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lastStatusCheck + 2 > time()) continue;
|
||||||
|
$this->lastStatusCheck = time();
|
||||||
|
|
||||||
|
if (!$client->query('CheckEndMatchCondition')) {
|
||||||
|
trigger_error("Couldn't get game server status. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$response = $client->getResponse();
|
||||||
|
switch ($response) {
|
||||||
|
case 'Finished':
|
||||||
|
{
|
||||||
|
if ($this->finishedBegin < 0) {
|
||||||
|
$this->finishedBegin = time();
|
||||||
|
}
|
||||||
|
else if ($this->finishedBegin + 13 <= time()) {
|
||||||
|
$this->switchToNextServer(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
$this->finishedBegin = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Lobby or inactive game server -> Redirect players
|
||||||
|
foreach ($callbacks as $callback) {
|
||||||
|
switch ($callback[0]) {
|
||||||
|
case Callbacks::CB_MP_PLAYERCONNECT:
|
||||||
|
{
|
||||||
|
$this->playerJoinedLobby($client, $callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for switch server request
|
||||||
|
if ($this->switchServerRequested > 0 && $this->switchServerRequested <= time()) {
|
||||||
|
$this->switchServerRequested = -1;
|
||||||
|
|
||||||
|
// Switch server
|
||||||
|
$this->switchToNextServer(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle 5 seconds callback
|
||||||
|
*/
|
||||||
|
public function handle5Seconds($callback = null) {
|
||||||
|
// Update lobby infos
|
||||||
|
$players = $this->iControl->server->getPlayers();
|
||||||
|
if (is_array($players)) {
|
||||||
|
$playerCount = count($players);
|
||||||
|
$playerLevel = 0.;
|
||||||
|
if ($playerCount > 0) {
|
||||||
|
foreach ($players as $player) {
|
||||||
|
$playerLevel += $player['LadderRanking'];
|
||||||
|
}
|
||||||
|
$playerLevel /= $playerCount;
|
||||||
|
}
|
||||||
|
foreach ($this->lobbies as $lobby) {
|
||||||
|
if (!$lobby->query('SetLobbyInfo', true, $playerCount, 255, $playerLevel)) {
|
||||||
|
trigger_error("Couldn't update lobby info. " . $this->iControl->getClientErrorText($lobby));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for not-redirected players
|
||||||
|
$clients = array_merge($this->gameServer, $this->lobbies);
|
||||||
|
$joinLink = $this->getJoinLink();
|
||||||
|
foreach ($clients as $client) {
|
||||||
|
if ($client == $this->gameServer[$this->currentClientIndex]) continue;
|
||||||
|
$players = $this->iControl->server->getPlayers($client);
|
||||||
|
if (!is_array($players)) continue;
|
||||||
|
foreach ($players as $player) {
|
||||||
|
$login = $player['Login'];
|
||||||
|
if (!$client->query('SendOpenLinkToLogin', $login, $joinLink, 1)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't redirect player '" . $login . "' to active game server. " .
|
||||||
|
$this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle player manialink page answer callback
|
||||||
|
*/
|
||||||
|
public function handleManialinkPageAnswer($callback) {
|
||||||
|
$login = $callback[1][1];
|
||||||
|
$action = $callback[1][2];
|
||||||
|
switch ($action) {
|
||||||
|
case self::ML_ADDFAVORITE:
|
||||||
|
{
|
||||||
|
// Open manialink to add server logins to favorite
|
||||||
|
$serverLogins = array();
|
||||||
|
$add_all = Tools::toBool($this->config->widgets->addfavorite->add_all);
|
||||||
|
if ($add_all) {
|
||||||
|
// Add all server
|
||||||
|
foreach ($this->gameServer as $serverClient) {
|
||||||
|
array_push($serverLogins, $this->iControl->server->getLogin($serverClient));
|
||||||
|
}
|
||||||
|
foreach ($this->lobbies as $serverClient) {
|
||||||
|
array_push($serverLogins, $this->iControl->server->getLogin($serverClient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add only current server
|
||||||
|
array_push($serverLogins, $this->iControl->server->getLogin());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build manialink url
|
||||||
|
$manialink = 'icontrol?favorite';
|
||||||
|
foreach ($serverLogins as $serverLogin) {
|
||||||
|
$manialink .= '&' . $serverLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send url to player
|
||||||
|
if (!$this->iControl->client->query('SendOpenLinkToLogin', $login, $manialink, 1)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't open manialink to add server to favorite for '" . $login . "'! " .
|
||||||
|
$this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to the next server
|
||||||
|
*
|
||||||
|
* @param bool $simulateMapEnd
|
||||||
|
* Simulate end of the map by sending callbacks
|
||||||
|
*/
|
||||||
|
private function switchToNextServer($simulateMapEnd) {
|
||||||
|
$this->finishedBegin = -1;
|
||||||
|
$oldClient = $this->gameServer[$this->currentClientIndex];
|
||||||
|
|
||||||
|
$random_order = Tools::toBool($this->config->random_order);
|
||||||
|
if ($random_order) {
|
||||||
|
// Random next server
|
||||||
|
$this->currentClientIndex = rand(0, count($this->gameServer) - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Next server in list
|
||||||
|
$this->currentClientIndex++;
|
||||||
|
}
|
||||||
|
if ($this->currentClientIndex >= count($this->gameServer)) $this->currentClientIndex = 0;
|
||||||
|
|
||||||
|
$newClient = $this->gameServer[$this->currentClientIndex];
|
||||||
|
if ($newClient == $oldClient) return;
|
||||||
|
|
||||||
|
// Restart map on next game server
|
||||||
|
if (!$newClient->query('RestartMap')) {
|
||||||
|
trigger_error("Couldn't restart map on next game server. " . $this->iControl->getClientErrorText($newClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($simulateMapEnd) {
|
||||||
|
// Simulate EndMap on old client
|
||||||
|
$this->iControl->callbacks->triggerCallback(Callbacks::CB_IC_ENDMAP, array(Callbacks::CB_IC_ENDMAP));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer players to next server
|
||||||
|
$joinLink = $this->getJoinLink($newClient);
|
||||||
|
if (!$oldClient->query('GetPlayerList', 255, 0)) {
|
||||||
|
trigger_error("Couldn't get player list. " . $this->iControl->getClientErrorText($oldClient));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$playerList = $oldClient->getResponse();
|
||||||
|
foreach ($playerList as $player) {
|
||||||
|
$login = $player['Login'];
|
||||||
|
if (!$oldClient->query('SendOpenLinkToLogin', $login, $joinLink, 1)) {
|
||||||
|
trigger_error("Couldn't redirect player to next game server. " . $this->iControl->getClientErrorText($oldClient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->iControl->client = $newClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger client updated callback
|
||||||
|
$this->iControl->callbacks->triggerCallback(Callbacks::CB_IC_CLIENTUPDATED, "Plugin_United.SwitchedServer");
|
||||||
|
|
||||||
|
if ($simulateMapEnd) {
|
||||||
|
// Simulate BeginMap on new client
|
||||||
|
$map = $this->iControl->server->getMap();
|
||||||
|
if ($map) {
|
||||||
|
$this->iControl->callbacks->triggerCallback(Callbacks::CB_IC_BEGINMAP, array(Callbacks::CB_IC_BEGINMAP, array($map)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle nextserver command
|
||||||
|
*
|
||||||
|
* @param mixed $command
|
||||||
|
*/
|
||||||
|
public function handleNextServerCommand($command) {
|
||||||
|
if (!$command) return;
|
||||||
|
$login = $command[1][1];
|
||||||
|
|
||||||
|
if (!$this->iControl->authentication->checkRight($login, 'operator')) {
|
||||||
|
// Not allowed
|
||||||
|
$this->iControl->authentication->sendNotAllowed($login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request skip to next server
|
||||||
|
$this->switchServerRequested = time() + 3;
|
||||||
|
|
||||||
|
// Send chat message
|
||||||
|
$this->iControl->chat->sendInformation("Switching to next server in 3 seconds...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerConnect callback
|
||||||
|
*/
|
||||||
|
public function playerJoinedLobby($client, $callback) {
|
||||||
|
if (!$client) return;
|
||||||
|
|
||||||
|
$data = $callback[1];
|
||||||
|
$login = $data[0];
|
||||||
|
|
||||||
|
// Redirect player to current game server
|
||||||
|
$gameserver = $this->gameServer[$this->currentClientIndex];
|
||||||
|
$joinLink = $this->getJoinLink($gameserver, !$data[1]);
|
||||||
|
if (!$client->query('SendOpenLinkToLogin', $login, $joinLink, 1)) {
|
||||||
|
trigger_error(
|
||||||
|
"United Plugin: Couldn't redirect player to current game server. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the game server defined in the config
|
||||||
|
*/
|
||||||
|
private function loadClients() {
|
||||||
|
$gameserver = $this->config->xpath('gameserver/server');
|
||||||
|
$lobbies = $this->config->xpath('lobbies/server');
|
||||||
|
|
||||||
|
$clientsConfig = array_merge($gameserver, $lobbies);
|
||||||
|
foreach ($clientsConfig as $index => $serv) {
|
||||||
|
$isGameServer = (in_array($serv, $gameserver));
|
||||||
|
|
||||||
|
$host = $serv->xpath('host');
|
||||||
|
$port = $serv->xpath('port');
|
||||||
|
if (!$host || !$port) {
|
||||||
|
trigger_error("Invalid configuration!", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
$host = (string) $host[0];
|
||||||
|
$port = (string) $port[0];
|
||||||
|
|
||||||
|
error_log("Connecting to united " . ($isGameServer ? 'game' : 'lobby') . " server at " . $host . ":" . $port . "...");
|
||||||
|
$client = new \IXR_ClientMulticall_Gbx();
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
if (!$client->InitWithIp($host, $port, $this->settings->timeout)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't connect to united " . ($isGameServer ? 'game' : lobby) . " server! " . $client->getErrorMessage() .
|
||||||
|
"(" . $client->getErrorCode() . ")", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
$login = $serv->xpath('login');
|
||||||
|
$pass = $serv->xpath('pass');
|
||||||
|
if (!$login || !$pass) {
|
||||||
|
trigger_error("Invalid configuration!", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
$login = (string) $login[0];
|
||||||
|
$pass = (string) $pass[0];
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
if (!$client->query('Authenticate', $login, $pass)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't authenticate on united " . ($isGameServer ? 'game' : 'lobby') . " server with user '" . $login . "'! " .
|
||||||
|
$client->getErrorMessage() . "(" . $client->getErrorCode() . ")", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable callback system
|
||||||
|
if (!$client->query('EnableCallbacks', true)) {
|
||||||
|
trigger_error("Couldn't enable callbacks! " . $client->getErrorMessage() . "(" . $client->getErrorCode() . ")",
|
||||||
|
E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for server to be ready
|
||||||
|
if (!$this->iControl->server->waitForStatus($client, 4)) {
|
||||||
|
trigger_error("Server couldn't get ready!", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set api version
|
||||||
|
if (!$client->query('SetApiVersion', iControl::API_VERSION)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't set API version '" . iControl::API_VERSION . "'! This might cause problems. " .
|
||||||
|
$this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set server settings
|
||||||
|
$password = ($isGameServer ? $this->settings->gamePassword : $this->settings->lobbyPassword);
|
||||||
|
$hideServer = ($isGameServer && $this->settings->hide_game_server ? 1 : 0);
|
||||||
|
// Passwords
|
||||||
|
if (!$client->query('SetServerPassword', $password)) {
|
||||||
|
trigger_error("Couldn't set server join password. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
if (!$client->query('SetServerPasswordForSpectator', $password)) {
|
||||||
|
trigger_error("Couldn't set server spec password. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
// Show/Hide server
|
||||||
|
if (!$client->query('SetHideServer', $hideServer)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't set server '" . ($hideServer == 0 ? 'shown' : 'hidden') . "'. " .
|
||||||
|
$this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable service announces
|
||||||
|
if (!$client->query("DisableServiceAnnounces", false)) {
|
||||||
|
trigger_error("Couldn't enable service announces. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set game mode
|
||||||
|
if (!$client->query('SetGameMode', $this->settings->gamemode)) {
|
||||||
|
trigger_error(
|
||||||
|
"Couldn't set game mode (" . $this->settings->gamemode . "). " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
else if (!$client->query('RestartMap')) {
|
||||||
|
trigger_error("Couldn't restart map to change game mode. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save client
|
||||||
|
$client->index = $index;
|
||||||
|
if ($isGameServer) {
|
||||||
|
array_push($this->gameServer, $client);
|
||||||
|
if (count($this->gameServer) === 1) {
|
||||||
|
$this->iControl->client = $client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
array_push($this->lobbies, $client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("United Plugin: Connected to all game and lobby server!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle PlayerConnect callback
|
||||||
|
*
|
||||||
|
* @param array $callback
|
||||||
|
*/
|
||||||
|
public function handlePlayerConnect($callback) {
|
||||||
|
if ($this->settings->widgets_enabled) {
|
||||||
|
// Send manialinks to the client
|
||||||
|
$login = $callback[1][0];
|
||||||
|
|
||||||
|
if (Tools::toBool($this->config->widgets->addfavorite->enabled)) {
|
||||||
|
// Send favorite widget
|
||||||
|
if (!$this->iControl->client->query('SendDisplayManialinkPageToLogin', $login,
|
||||||
|
$this->manialinks[self::ML_ADDFAVORITE]->asXml(), 0, false)) {
|
||||||
|
trigger_error("Couldn't send favorite widget to player '" . $login . "'! " . $this->iControl->getClientErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build join link for the given client
|
||||||
|
*/
|
||||||
|
private function getJoinLink(&$client = null, $play = true) {
|
||||||
|
if (!$client) {
|
||||||
|
$client = $this->gameServer[$this->currentClientIndex];
|
||||||
|
}
|
||||||
|
if (!$client->query('GetSystemInfo')) {
|
||||||
|
trigger_error("Couldn't fetch server system info. " . $this->iControl->getClientErrorText($client));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$systemInfo = $client->getResponse();
|
||||||
|
$password = '';
|
||||||
|
if (!$client->query('GetServerPassword')) {
|
||||||
|
trigger_error("Couldn't get server password. " . $this->iControl->getClientErrorText($client));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$password = $client->getResponse();
|
||||||
|
}
|
||||||
|
return '#q' . ($play ? 'join' : 'spectate') . '=' . $systemInfo['ServerLogin'] .
|
||||||
|
(strlen($password) > 0 ? ':' . $password : '') . '@' . $systemInfo['TitleId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build manialink for addfavorite button
|
||||||
|
*/
|
||||||
|
private function buildFavoriteManialink() {
|
||||||
|
// Load configs
|
||||||
|
$config = $this->config->widgets->addfavorite;
|
||||||
|
if (!Tools::toBool($config->enabled)) return;
|
||||||
|
|
||||||
|
$pos_x = (float) $config->pos_x;
|
||||||
|
$pos_y = (float) $config->pos_y;
|
||||||
|
$height = (float) $config->height;
|
||||||
|
$width = (float) $config->width;
|
||||||
|
$add_all = Tools::toBool($config->add_all);
|
||||||
|
|
||||||
|
// Build manialink
|
||||||
|
$xml = Tools::newManialinkXml(self::ML_ADDFAVORITE);
|
||||||
|
|
||||||
|
$frameXml = $xml->addChild('frame');
|
||||||
|
$frameXml->addAttribute('posn', $pos_x . ' ' . $pos_y);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
$quadXml = $frameXml->addChild('quad');
|
||||||
|
Tools::addAlignment($quadXml);
|
||||||
|
$quadXml->addAttribute('posn', '0 0 0');
|
||||||
|
$quadXml->addAttribute('sizen', $width . ' ' . $height);
|
||||||
|
$quadXml->addAttribute('style', 'Bgs1InRace');
|
||||||
|
$quadXml->addAttribute('substyle', 'BgTitleShadow');
|
||||||
|
$quadXml->addAttribute('action', self::ML_ADDFAVORITE);
|
||||||
|
|
||||||
|
// Heart
|
||||||
|
$quadXml = $frameXml->addChild('quad');
|
||||||
|
Tools::addAlignment($quadXml);
|
||||||
|
$quadXml->addAttribute('id', 'Quad_AddFavorite');
|
||||||
|
$quadXml->addAttribute('posn', '0 0 1');
|
||||||
|
$quadXml->addAttribute('sizen', ($width - 1.) . ' ' . ($height - 0.8));
|
||||||
|
$quadXml->addAttribute('style', 'Icons64x64_1');
|
||||||
|
$quadXml->addAttribute('substyle', 'StateFavourite');
|
||||||
|
$quadXml->addAttribute('scriptevents', '1');
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
$tooltipFrameXml = $frameXml->addChild('frame');
|
||||||
|
$tooltipFrameXml->addAttribute('id', 'Frame_FavoriteTooltip');
|
||||||
|
$tooltipFrameXml->addAttribute('posn', '0 ' . ($pos_y >= 0 ? '-' : '') . '13');
|
||||||
|
$tooltipFrameXml->addAttribute('hidden', '1');
|
||||||
|
|
||||||
|
$quadXml = $tooltipFrameXml->addChild('quad');
|
||||||
|
Tools::addAlignment($quadXml);
|
||||||
|
$quadXml->addAttribute('posn', '0 0 2');
|
||||||
|
$quadXml->addAttribute('sizen', '28 16');
|
||||||
|
$quadXml->addAttribute('style', 'Bgs1InRace');
|
||||||
|
$quadXml->addAttribute('substyle', 'BgTitleShadow');
|
||||||
|
|
||||||
|
$labelXml = $tooltipFrameXml->addChild('label');
|
||||||
|
Tools::addAlignment($labelXml);
|
||||||
|
Tools::addTranslate($labelXml);
|
||||||
|
$labelXml->addAttribute('posn', '0 0 3');
|
||||||
|
$labelXml->addAttribute('sizen', '26 0');
|
||||||
|
$labelXml->addAttribute('style', 'TextTitle1');
|
||||||
|
$labelXml->addAttribute('textsize', '2');
|
||||||
|
$labelXml->addAttribute('autonewline', '1');
|
||||||
|
$countText = '';
|
||||||
|
if ($add_all) {
|
||||||
|
$count = count($this->gameServer) + count($this->lobbies);
|
||||||
|
$countText = 'all ' . $count . ' ';
|
||||||
|
}
|
||||||
|
$labelXml->addAttribute('text', 'Add ' . $countText . 'server to Favorite!');
|
||||||
|
|
||||||
|
// Script for tooltip
|
||||||
|
$script = '
|
||||||
|
declare Frame_FavoriteTooltip <=> (Page.GetFirstChild("Frame_FavoriteTooltip") as CMlFrame);
|
||||||
|
while (True) {
|
||||||
|
yield;
|
||||||
|
foreach (Event in PendingEvents) {
|
||||||
|
switch (Event.Type) {
|
||||||
|
case CMlEvent::Type::MouseOver: {
|
||||||
|
switch (Event.ControlId) {
|
||||||
|
case "Quad_AddFavorite": {
|
||||||
|
Frame_FavoriteTooltip.Visible = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case CMlEvent::Type::MouseOut: {
|
||||||
|
switch (Event.ControlId) {
|
||||||
|
case "Quad_AddFavorite": {
|
||||||
|
Frame_FavoriteTooltip.Visible = False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
$xml->addChild('script', $script);
|
||||||
|
|
||||||
|
$this->manialinks[self::ML_ADDFAVORITE] = $xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
44
application/readme.txt
Normal file
44
application/readme.txt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
**********************************************
|
||||||
|
* *
|
||||||
|
* iControl ManiaPlanet Server Control *
|
||||||
|
* Written by steeffeen *
|
||||||
|
* Contact: mail@steeffeen.com *
|
||||||
|
* *
|
||||||
|
**********************************************
|
||||||
|
|
||||||
|
SETUP:
|
||||||
|
|
||||||
|
1. Copy all files into your desired directory.
|
||||||
|
|
||||||
|
2. Configure the needed settings:
|
||||||
|
|
||||||
|
2.1 Open the file 'configs/server.iControl.xml'.
|
||||||
|
Enter your maniaplanet server information.
|
||||||
|
|
||||||
|
2.2 Open the file 'configs/database.iControl.xml'
|
||||||
|
Enter your mysql server information or disable database usage if you don't have a mysql server available.
|
||||||
|
|
||||||
|
2.3 Open the file 'configs/authentication.iControl.xml'.
|
||||||
|
Add the player logins who should have access to the commands of iControl.
|
||||||
|
|
||||||
|
3. (Optional) Enable or disable the available plugins in the file 'configs/plugins.iControl.xml'.
|
||||||
|
|
||||||
|
4. (Optional) Edit the other config files in 'configs/' in order to customize your iControl to fit your needs.
|
||||||
|
|
||||||
|
5. Run the tool via the shell script 'iControl.sh' (UNIX) or the batch file 'iControl.bat' (Windows)
|
||||||
|
|
||||||
|
6. Enjoy!
|
||||||
|
|
||||||
|
|
||||||
|
INFORMATION:
|
||||||
|
|
||||||
|
- iControl is only tested on UNIX machines
|
||||||
|
- even though it might run properly on Windows I can't promise it will work all the time
|
||||||
|
- furthermore I can't promise that there won't be a feature in the future that makes it impossible to run iControl under Windows
|
||||||
|
- in order to run iControl under Windows you have to alter the file iControl.bat and enter the path to your php.exe
|
||||||
|
|
||||||
|
- Tests were performed using PHP Version 5.4
|
||||||
|
- If you notice problems with other version please let me know
|
||||||
|
|
||||||
|
- Please report bugs by writing a mail to mail@steeffeen.com
|
||||||
|
|
Loading…
Reference in New Issue
Block a user