├── .gitignore
├── README.md
├── client
├── router.php
└── support
│ ├── cacert.pem
│ ├── cli.php
│ ├── crc32_stream.php
│ ├── deflate_stream.php
│ ├── http.php
│ ├── sdk_drc_client.php
│ ├── utf_utils.php
│ ├── web_browser.php
│ └── websocket.php
└── server
├── index.php
└── support
├── admin.css
├── admin.js
├── admin_bulkedit.css
├── admin_bulkedit.js
├── admin_print.css
├── admin_view.css
├── admin_view_print.css
├── cacert.pem
├── crc32_stream.php
├── deflate_stream.php
├── en_us_lite.json
├── flex_forms.php
├── flex_forms_error.png
├── http.php
├── jquery-3.5.0.min.js
├── page_basics.php
├── random.php
├── sdk_drc_client.php
├── str_basics.php
├── utf_utils.php
├── view_print_layout.php
├── web_browser.php
└── websocket.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /server/config.php
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Open Broadcaster (OBS) Remote Control Manager
2 | =============================================
3 |
4 | This project allows [Open Broadcaster (OBS)](https://obsproject.com/) scene selection to be remotely controlled via a standard web browser. Now you can control the active scene in OBS from a phone, tablet, or other device anywhere in the world from any web browser without having to open any ports on a firewall.
5 |
6 | 
7 |
8 | [Open Broadcaster](https://obsproject.com/) is a very popular piece of software for recording and live streaming. It has a powerful scene system which allows for multiple scenes to be setup and can be switched between with a single click. But what if the computer running OBS to switch scenes is not on the same network, on a restrictive network such as a corporate environment or some public WiFi networks, or you want someone else to control the active scene remotely? That's where this Remote Control Manager software comes in to enable remotely switching scenes in OBS.
9 |
10 | [](https://cubiclesoft.com/donate/) [](https://cubiclesoft.com/product-support/github/)
11 |
12 | Fetaures
13 | --------
14 |
15 | * Easy to use web browser interface for switching scenes.
16 | * Responsive client software. Scene switching is very fast with minimal lag.
17 | * Client software works with the built-in OBS WebSocket plugin.
18 | * Self-hosted server software for total control and peace of mind.
19 | * Great for switching between multiple cameras during a livestream event.
20 | * Has a liberal open source license. MIT or LGPL, your choice.
21 | * Designed for relatively painless integration into your environment.
22 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively.
23 |
24 | Getting Started
25 | ---------------
26 |
27 | Either 'git clone' this repository or use the green "Code" button and "Download ZIP" above to obtain the latest release of this software.
28 |
29 | This software is written in PHP and therefore depends on PHP being installed on the system. Installing PHP can be a little tricky on Windows. Here's a [portable version of PHP](https://github.com/cubiclesoft/portable-apache-maria-db-php-for-windows). Other OSes have their own quirky method of installing PHP. Consult your favorite search engine.
30 |
31 | The ideal setup involves installing the self-hosted server on your own web host (e.g. a VPS). However, there may be a number of public installs of the server software that you can freely use with the client software. Public servers may not work as well as a privately hosted server.
32 |
33 | Start OBS. Go to Tools -> WebSocket Server Settings. Enable the WebSocket server on the default port 4455, set a password, and then "Apply" the changes.
34 |
35 | Start a Command Prompt or terminal, `cd` into the Remote Control Manager "client" directory and run:
36 |
37 | ```
38 | php router.php -p YourOBSWebSocketPasswordHere https://grilledapps.com/obsmanager/ "Live Presentation"
39 | ```
40 |
41 | If successful, OBS will now be connected to a public facing installation of the server software and will output a URL that looks like this:
42 |
43 | ```
44 | Joined DRC channel 1 as client ID 2.
45 | Channel name: katun-tireh-letih
46 | Users may join via: https://grilledapps.com/obsmanager/?channel=katun-tireh-letih
47 | ```
48 |
49 | Visit the URL on the device and web browser of your choice. The web browser will list the scenes in OBS. Simply select a scene to switch to it in OBS.
50 |
51 | While anyone with the URL can switch scenes, the server software randomly generates a new, unique channel name every time the client software starts.
52 |
53 | To stop the client software, press Ctrl + C in the Command Prompt or terminal. Alternatively, just close OBS.
54 |
55 | Server Setup
56 | ------------
57 |
58 | The Getting Started section above uses a public Remote Control Manager server. To setup your own self-hosted server, you will first need a server running Nginx, PHP FPM, PHP CLI, [PHP Data Relay Center (DRC)](https://github.com/cubiclesoft/php-drc), and the server software from this repository.
59 |
60 | If that sounds like a lot, fortunately there's a faster way to get everything installed via [Server Instant Start](https://github.com/cubiclesoft/server-instant-start). Server Instant Start can spin up a fully ready system on most VPS providers in a matter of minutes with everything installed and configured except this server software.
61 |
62 | Note: Purchasing a domain, setting up DNS, and enabling SSL/TLS certificates is left as an exercise.
63 |
64 | Upload the contents of the "server" directory to a directory on your server. Create a file called "config.php" in the same directory as "index.php" and apply corrected paths to PHP DRC on your system:
65 |
66 | ```php
67 | array(
20 | "h" => "host",
21 | "p" => "password",
22 | "?" => "help"
23 | ),
24 | "rules" => array(
25 | "host" => array("arg" => true),
26 | "password" => array("arg" => true),
27 | "help" => array("arg" => false)
28 | ),
29 | "allow_opts_after_param" => false
30 | );
31 | $args = CLI::ParseCommandLine($options);
32 |
33 | if (isset($args["opts"]["help"]) || count($args["params"]) < 2)
34 | {
35 | echo "The OBS Manager client\n";
36 | echo "Purpose: Connect to a remote OBS Manager instance and Palakis obs-websocket from the command-line.\n";
37 | echo "\n";
38 | echo "Syntax: " . $args["file"] . " [options] OBSManagerURL LocationName\n";
39 | echo "Options:\n";
40 | echo "\t-h Host and port to connect to (Default is \"ws://127.0.0.1:4455/\").\n";
41 | echo "\t-p Password for obs-websocket.\n";
42 | echo "\n";
43 | echo "Examples:\n";
44 | echo "\tphp " . $args["file"] . "\n";
45 | echo "\tphp " . $args["file"] . " -p supersecret https://yourdomain.com/obsmanager/ Home\n";
46 |
47 | exit();
48 | }
49 |
50 | $host = (isset($args["opts"]["host"]) ? $args["opts"]["host"] : "ws://127.0.0.1:4455/");
51 |
52 | require_once $rootpath . "/support/web_browser.php";
53 | require_once $rootpath . "/support/websocket.php";
54 | require_once $rootpath . "/support/sdk_drc_client.php";
55 |
56 | // Connect to OBS.
57 | // OBS Websocket Protocol specification: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md
58 | echo "Initializing...\n";
59 | $ws = new WebSocket();
60 | $result = $ws->Connect($host, "http://127.0.0.1");
61 | if (!$result["success"]) CLI::DisplayError("Unable to connect to '" . $host . "'.", $result);
62 |
63 | $nextobsmsg = 1;
64 | $obsmessages = array();
65 | $obsstate = "connect";
66 |
67 | function SendOBSRequest($type, $data = array(), $drcclient = false)
68 | {
69 | global $nextobsmsg, $ws, $obsmessages;
70 |
71 | $reqid = "router-" . $nextobsmsg;
72 | $nextobsmsg++;
73 |
74 | // Request (Opcode 6).
75 | $payload = array(
76 | "op" => 6,
77 | "d" => array(
78 | "requestType" => $type,
79 | "requestId" => $reqid,
80 | "requestData" => $data
81 | )
82 | );
83 |
84 | $result = $ws->Write(json_encode($payload, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
85 | if (!$result["success"]) return $result;
86 |
87 | $obsmessages[$reqid] = array("type" => $type, "reqid" => $reqid, "origmsg" => $data, "drcclient" => $drcclient);
88 |
89 | return array("success" => true);
90 | }
91 |
92 | // Get the DRC connection and token information.
93 | $web = new WebBrowser();
94 |
95 | $url = $args["params"][0];
96 | if (substr($url, -1) != "/") $url .= "/";
97 | $url .= "?api=init&location=" . urlencode($args["params"][1]);
98 | $result = $web->Process($url);
99 | if (!$result["success"]) CLI::DisplayError("Unable to retrieve '" . $url . "'.", $result);
100 |
101 | $drcinfo = json_decode($result["body"], true);
102 | if (!is_array($drcinfo)) CLI::DisplayError("Expected a JSON response from the OBS Manager.", $result);
103 | if (!$drcinfo["success"]) CLI::DisplayError("A failure occurred while attempting to get access to the remote OBS Manager.", $drcinfo);
104 | if ($drcinfo["data"]["protocol"] !== "obs-websocket-remote-api") CLI::DisplayError("The server returned an unknown/unsupported DRC protocol.", $result);
105 |
106 | $drc = false;
107 | $drcstate = "connect";
108 |
109 | echo "Ready.\n";
110 |
111 | do
112 | {
113 | if ($drc === false)
114 | {
115 | $drc = new DRCClient();
116 |
117 | // Connect to the DRC server.
118 | $result = $drc->Connect($drcinfo["drc_url"], $drcinfo["drc_origin"]);
119 | if (!$result["success"])
120 | {
121 | CLI::DisplayError("Unable to connect to the DRC server.", $result, false);
122 |
123 | $drc = false;
124 | }
125 | else
126 | {
127 | // Join the channel.
128 | $result = $drc->JoinChannel($drcinfo["data"]["channelname"], $drcinfo["data"]["protocol"], $drcinfo["data"]["token"], true);
129 | if (!$result["success"]) CLI::DisplayError("Unable to join the DRC channel '" . $drcinfo["data"]["channelname"] . "'.", $result);
130 |
131 | $drcchannelinfo = $result["data"];
132 |
133 | echo "Joined DRC channel " . $drcchannelinfo["channel"] . " as client ID " . $drcchannelinfo["id"] . ".\n";
134 | echo "Channel name: " . $drcinfo["user_channel"] . "\n";
135 | echo "Users may join via: " . $drcinfo["user_url"] . "\n";
136 | }
137 | }
138 |
139 | // Implement stream_select() directly since multiple clients are involved.
140 | $timeout = 30;
141 | $readfps = array();
142 | $writefps = array();
143 | $exceptfps = NULL;
144 |
145 | $fp = $ws->GetStream();
146 | $readfps[] = $fp;
147 | if ($ws->NeedsWrite()) $writefps[] = $fp;
148 |
149 | if ($drc === false) $timeout = 3;
150 | else
151 | {
152 | $fp = $drc->GetStream();
153 | $readfps[] = $fp;
154 | if ($drc->NeedsWrite()) $writefps[] = $fp;
155 | }
156 |
157 | $result = @stream_select($readfps, $writefps, $exceptfps, $timeout);
158 | if ($result === false) break;
159 |
160 | // Process WebSocket data.
161 | $result = $ws->Wait(0);
162 | if (!$result["success"]) CLI::DisplayError("WebSocket connection failure.", $result);
163 |
164 | do
165 | {
166 | $result = $ws->Read();
167 | if (!$result["success"]) CLI::DisplayError("WebSocket connection failure.", $result);
168 |
169 | if ($result["data"] !== false)
170 | {
171 | // echo "Raw message from server:\n";
172 | // var_dump($result["data"]);
173 | // echo "\n";
174 |
175 | $data = json_decode($result["data"]["payload"], true);
176 | if ($obsstate === "connect")
177 | {
178 | if ($data["op"] == 0)
179 | {
180 | // Identify (Opcode 1).
181 | $payload = array(
182 | "op" => 1,
183 | "d" => array(
184 | "rpcVersion" => 1
185 | )
186 | );
187 |
188 | if (isset($data["d"]["authentication"]))
189 | {
190 | if (!isset($args["opts"]["password"])) CLI::DisplayError("The OBS websocket host '" . $host . "' requires a password.");
191 |
192 | echo "Authenticating with OBS...\n";
193 | $hash = base64_encode(hash("sha256", base64_encode(hash("sha256", $args["opts"]["password"] . $data["d"]["authentication"]["salt"], true)) . $data["d"]["authentication"]["challenge"], true));
194 |
195 | $payload["d"]["authentication"] = $hash;
196 | }
197 |
198 | $result2 = $ws->Write(json_encode($payload, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
199 | if (!$result2["success"]) return $result2;
200 |
201 | $obsstate = "identify";
202 | }
203 | else
204 | {
205 | echo "Expected Hello (Opcode 0). Received unknown message:\n";
206 | var_dump($data);
207 | echo "\n";
208 | }
209 | }
210 | else if ($obsstate === "identify")
211 | {
212 | if ($data["op"] == 2)
213 | {
214 | $obsstate = "main";
215 | }
216 | else
217 | {
218 | echo "Expected Identified (Opcode 2). Received unknown message:\n";
219 | var_dump($data);
220 | echo "\n";
221 | }
222 | }
223 | else if ($obsstate === "main")
224 | {
225 | // RequestResponse (Opcode 7).
226 | if ($data["op"] == 7)
227 | {
228 | if (!isset($data["d"]["requestId"]) || !isset($obsmessages[$data["d"]["requestId"]]))
229 | {
230 | echo "Unknown message:\n";
231 | var_dump($data);
232 | echo "\n";
233 | }
234 | else
235 | {
236 | $origreq = $obsmessages[$data["d"]["requestId"]];
237 | unset($obsmessages[$data["d"]["requestId"]]);
238 |
239 | // Send the results back to the client.
240 | unset($data["d"]["requestId"]);
241 | if ($drc !== false)
242 | {
243 | $result2 = $drc->SendCommand($origreq["drcclient"]["channel"], $origreq["drcclient"]["cmd"], $origreq["drcclient"]["from"], array("result" => $data["d"]));
244 | if (!$result2["success"]) CLI::DisplayError("Unable to send response to the request.", $result2, false);
245 | }
246 | }
247 | }
248 | }
249 |
250 |
251 | /*
252 | if (!isset($data["message-id"]) || !isset($obsmessages[$data["message-id"]]))
253 | {
254 | // Skip all events.
255 | if (!isset($data["update-type"]))
256 | {
257 | echo "Unknown message:\n";
258 | var_dump($data);
259 | echo "\n";
260 | }
261 | }
262 | else
263 | {
264 | $origreq = $obsmessages[$data["message-id"]];
265 | unset($obsmessages[$data["message-id"]]);
266 |
267 | if ($origreq["type"] === "GetAuthRequired")
268 | {
269 | // Send password if required.
270 | if ($data["authRequired"])
271 | {
272 | if (!isset($args["opts"]["password"])) CLI::DisplayError("The OBS websocket host '" . $host . "' requires a password.");
273 |
274 | echo "Authenticating with OBS...\n";
275 | $hash = base64_encode(hash("sha256", base64_encode(hash("sha256", $args["opts"]["password"] . $data["salt"], true)) . $data["challenge"], true));
276 |
277 | SendOBSMessage("Authenticate", array("auth" => $hash));
278 | }
279 | }
280 | else if ($origreq["type"] === "Authenticate")
281 | {
282 | if ($data["status"] !== "ok") CLI::DisplayError("Unable to authenticate with OBS websocket. Incorrect password? " . $data["error"]);
283 | else echo "Authentication successful.\n";
284 | }
285 | else if (isset($origreq["drcclient"]))
286 | {
287 | // Send the results back to the client.
288 | unset($data["message-id"]);
289 | if ($drc !== false)
290 | {
291 | $result2 = $drc->SendCommand($origreq["drcclient"]["channel"], $origreq["drcclient"]["cmd"], $origreq["drcclient"]["from"], array("result" => $data));
292 | if (!$result2["success"]) CLI::DisplayError("Unable to send response to the request.", $result2, false);
293 | }
294 | }
295 | else
296 | {
297 | echo "Other!\n";
298 | var_dump($origreq);
299 | var_dump($data);
300 | }
301 | }
302 | */
303 | }
304 | } while ($result["data"] !== false);
305 |
306 |
307 | // Process DRC channel data.
308 | if ($drc !== false)
309 | {
310 | $result = $drc->Wait(0);
311 | if (!$result["success"])
312 | {
313 | CLI::DisplayError("DRC connection failure.", $result, false);
314 |
315 | $drc = false;
316 | }
317 | else
318 | {
319 | do
320 | {
321 | $result = $drc->Read();
322 | if (!$result["success"])
323 | {
324 | CLI::DisplayError("DRC connection failure.", $result, false);
325 |
326 | $drc = false;
327 |
328 | break;
329 | }
330 |
331 | if ($result["data"] !== false)
332 | {
333 | $data = $result["data"];
334 |
335 | if (strncmp($data["cmd"], "OBS-", 4) == 0 && $data["cmd"] !== "OBS-GetAuthRequired" && $data["cmd"] !== "OBS-Authenticate")
336 | {
337 | $data2 = $data;
338 | unset($data2["channel"]);
339 | unset($data2["success"]);
340 | unset($data2["from"]);
341 | unset($data2["cmd"]);
342 |
343 | echo "Client " . $data["from"] . " requested " . substr($data["cmd"], 4) . "." . (count($data2) ? "\n " . json_encode($data2, JSON_UNESCAPED_SLASHES) : "") . "\n";
344 |
345 | SendOBSRequest(substr($data["cmd"], 4), $data2, $data);
346 | }
347 | }
348 | } while ($result["data"] !== false);
349 | }
350 | }
351 |
352 | } while (1);
353 | ?>
--------------------------------------------------------------------------------
/client/support/cli.php:
--------------------------------------------------------------------------------
1 | $val)
15 | {
16 | if (!isset($options["rules"][$val])) unset($options["shortmap"][$key]);
17 | }
18 | foreach ($options["rules"] as $key => $val)
19 | {
20 | if (!isset($val["arg"])) $options["rules"][$key]["arg"] = false;
21 | if (!isset($val["multiple"])) $options["rules"][$key]["multiple"] = false;
22 | }
23 |
24 | if ($args === false) $args = $_SERVER["argv"];
25 | else if (is_string($args))
26 | {
27 | $args2 = $args;
28 | $args = array();
29 | $inside = false;
30 | $currarg = "";
31 | $y = strlen($args2);
32 | for ($x = 0; $x < $y; $x++)
33 | {
34 | $currchr = substr($args2, $x, 1);
35 |
36 | if ($inside === false && $currchr == " " && $currarg != "")
37 | {
38 | $args[] = $currarg;
39 | $currarg = "";
40 | }
41 | else if ($currchr == "\"" || $currchr == "'")
42 | {
43 | if ($inside === false) $inside = $currchr;
44 | else if ($inside === $currchr) $inside = false;
45 | else $currarg .= $currchr;
46 | }
47 | else if ($currchr == "\\" && $x < $y - 1)
48 | {
49 | $x++;
50 | $currarg .= substr($args2, $x, 1);
51 | }
52 | else if ($inside !== false || $currchr != " ")
53 | {
54 | $currarg .= $currchr;
55 | }
56 | }
57 |
58 | if ($currarg != "") $args[] = $currarg;
59 | }
60 |
61 | $result = array("success" => true, "file" => array_shift($args), "opts" => array(), "params" => array());
62 |
63 | // Look over shortmap to determine if options exist that are one byte (flags) and don't have arguments.
64 | $chrs = array();
65 | foreach ($options["shortmap"] as $key => $val)
66 | {
67 | if (isset($options["rules"][$val]) && !$options["rules"][$val]["arg"]) $chrs[$key] = true;
68 | }
69 |
70 | $allowopt = true;
71 | $y = count($args);
72 | for ($x = 0; $x < $y; $x++)
73 | {
74 | $arg = $args[$x];
75 |
76 | // Attempt to process an option.
77 | $opt = false;
78 | $optval = false;
79 | if ($allowopt && substr($arg, 0, 1) == "-")
80 | {
81 | $pos = strpos($arg, "=");
82 | if ($pos === false) $pos = strlen($arg);
83 | else $optval = substr($arg, $pos + 1);
84 | $arg2 = substr($arg, 1, $pos - 1);
85 |
86 | if (isset($options["rules"][$arg2])) $opt = $arg2;
87 | else if (isset($options["shortmap"][$arg2])) $opt = $options["shortmap"][$arg2];
88 | else if ($x == 0)
89 | {
90 | // Attempt to process as a set of flags.
91 | $y2 = strlen($arg2);
92 | if ($y2 > 0)
93 | {
94 | for ($x2 = 0; $x2 < $y2; $x2++)
95 | {
96 | $currchr = substr($arg2, $x2, 1);
97 |
98 | if (!isset($chrs[$currchr])) break;
99 | }
100 |
101 | if ($x2 == $y2)
102 | {
103 | for ($x2 = 0; $x2 < $y2; $x2++)
104 | {
105 | $opt = $options["shortmap"][substr($arg2, $x2, 1)];
106 |
107 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true;
108 | else
109 | {
110 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0;
111 | $result["opts"][$opt]++;
112 | }
113 | }
114 |
115 | continue;
116 | }
117 | }
118 | }
119 | }
120 |
121 | if ($opt === false)
122 | {
123 | // Is a parameter.
124 | if (substr($arg, 0, 1) === "\"" || substr($arg, 0, 1) === "'") $arg = substr($arg, 1);
125 | if (substr($arg, -1) === "\"" || substr($arg, -1) === "'") $arg = substr($arg, 0, -1);
126 |
127 | $result["params"][] = $arg;
128 |
129 | if (!$options["allow_opts_after_param"]) $allowopt = false;
130 | }
131 | else if (!$options["rules"][$opt]["arg"])
132 | {
133 | // Is a flag by itself.
134 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true;
135 | else
136 | {
137 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0;
138 | $result["opts"][$opt]++;
139 | }
140 | }
141 | else
142 | {
143 | // Is an option.
144 | if ($optval === false)
145 | {
146 | $x++;
147 | if ($x == $y) break;
148 | $optval = $args[$x];
149 | }
150 |
151 | if (substr($optval, 0, 1) === "\"" || substr($optval, 0, 1) === "'") $optval = substr($optval, 1);
152 | if (substr($optval, -1) === "\"" || substr($optval, -1) === "'") $optval = substr($optval, 0, -1);
153 |
154 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = $optval;
155 | else
156 | {
157 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = array();
158 | $result["opts"][$opt][] = $optval;
159 | }
160 | }
161 | }
162 |
163 | return $result;
164 | }
165 |
166 | public static function CanGetUserInputWithArgs(&$args, $prefix)
167 | {
168 | return (($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) || count($args["params"]));
169 | }
170 |
171 | // Gets a line of input from the user. If the user supplies all information via the command-line, this could be entirely automated.
172 | public static function GetUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false, $callback = false, $callbackopts = false)
173 | {
174 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "")
175 | {
176 | echo "\n" . rtrim($noparamsoutput) . "\n\n";
177 |
178 | $suppressoutput = false;
179 | $noparamsoutput = "";
180 | }
181 |
182 | do
183 | {
184 | $prompt = ($suppressoutput ? "" : $question . ($default !== false ? " [" . $default . "]" : "") . ": ");
185 |
186 | if ($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix]))
187 | {
188 | $line = array_shift($args["opts"][$prefix]);
189 | if ($line === "") $line = $default;
190 | if (!$suppressoutput) echo $prompt . $line . "\n";
191 | }
192 | else if (count($args["params"]))
193 | {
194 | $line = array_shift($args["params"]);
195 | if ($line === "") $line = $default;
196 | if (!$suppressoutput) echo $prompt . $line . "\n";
197 | }
198 | else if (strtoupper(substr(php_uname("s"), 0, 3)) != "WIN" && function_exists("readline") && function_exists("readline_add_history"))
199 | {
200 | $line = readline($prompt);
201 | if ($line === false) exit();
202 |
203 | $line = trim($line);
204 | if ($line === "") $line = $default;
205 | if ($line !== false && $line !== "") readline_add_history($line);
206 | }
207 | else
208 | {
209 | echo $prompt;
210 | fflush(STDOUT);
211 | $line = fgets(STDIN);
212 | if ($line === false || ($line === "" && feof(STDIN))) exit();
213 |
214 | $line = trim($line);
215 | if ($line === "") $line = $default;
216 | }
217 |
218 | if ($line === false || (is_callable($callback) && !call_user_func_array($callback, array($line, &$callbackopts))))
219 | {
220 | if ($line !== false) $line = false;
221 | else echo "Please enter a value.\n";
222 |
223 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "")
224 | {
225 | echo "\n" . $noparamsoutput . "\n";
226 |
227 | $noparamsoutput = "";
228 | }
229 |
230 | $suppressoutput = false;
231 | }
232 | } while ($line === false);
233 |
234 | return $line;
235 | }
236 |
237 | // Obtains a valid line of input.
238 | public static function GetLimitedUserInputWithArgs(&$args, $prefix, $question, $default, $allowedoptionsprefix, $allowedoptions, $loop = true, $suppressoutput = false, $multipleuntil = false)
239 | {
240 | $noparamsoutput = $allowedoptionsprefix . "\n\n";
241 | $size = 0;
242 | foreach ($allowedoptions as $key => $val)
243 | {
244 | if ($size < strlen($key)) $size = strlen($key);
245 | }
246 |
247 | foreach ($allowedoptions as $key => $val)
248 | {
249 | $newtab = str_repeat(" ", 2 + $size + 3);
250 | $noparamsoutput .= " " . $key . ":" . str_repeat(" ", $size - strlen($key)) . " " . str_replace("\n\t", "\n" . $newtab, $val) . "\n";
251 | }
252 |
253 | $noparamsoutput .= "\n";
254 |
255 | if ($default === false && count($allowedoptions) == 1)
256 | {
257 | reset($allowedoptions);
258 | $default = key($allowedoptions);
259 | }
260 |
261 | $results = array();
262 | do
263 | {
264 | $displayed = (!count($args["params"]));
265 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput);
266 | if (is_array($multipleuntil) && $multipleuntil["exit"] === $result) break;
267 | $result2 = false;
268 | if (!count($allowedoptions)) break;
269 | foreach ($allowedoptions as $key => $val)
270 | {
271 | if (!strcasecmp($key, $result) || !strcasecmp($val, $result)) $result2 = $key;
272 | }
273 | if ($loop)
274 | {
275 | if ($result2 === false)
276 | {
277 | echo "Please select an option from the list.\n";
278 |
279 | $suppressoutput = false;
280 | }
281 | else if (is_array($multipleuntil))
282 | {
283 | $results[$result2] = $result2;
284 |
285 | $question = $multipleuntil["nextquestion"];
286 | $default = $multipleuntil["nextdefault"];
287 | }
288 | }
289 |
290 | if ($displayed) $noparamsoutput = "";
291 | } while ($loop && ($result2 === false || is_array($multipleuntil)));
292 |
293 | return (is_array($multipleuntil) ? $results : $result2);
294 | }
295 |
296 | // Obtains Yes/No style input.
297 | public static function GetYesNoUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false)
298 | {
299 | $default = (substr(strtoupper(trim($default)), 0, 1) === "Y" ? "Y" : "N");
300 |
301 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput);
302 | $result = (substr(strtoupper(trim($result)), 0, 1) === "Y");
303 |
304 | return $result;
305 | }
306 |
307 | public static function GetHexDump($data)
308 | {
309 | $result = "";
310 |
311 | $x = 0;
312 | $y = strlen($data);
313 | if ($y <= 256) $padwidth = 2;
314 | else if ($y <= 65536) $padwidth = 4;
315 | else if ($y <= 16777216) $padwidth = 6;
316 | else $padwidth = 8;
317 |
318 | $pad = str_repeat(" ", $padwidth);
319 |
320 | $data2 = str_split(strtoupper(bin2hex($data)), 32);
321 | foreach ($data2 as $line)
322 | {
323 | $result .= sprintf("%0" . $padwidth . "X", $x) . " | ";
324 |
325 | $line = str_split($line, 2);
326 | array_splice($line, 8, 0, "");
327 | $result .= implode(" ", $line) . "\n";
328 |
329 | $result .= $pad . " |";
330 | $y2 = $x + 16;
331 | for ($x2 = 0; $x2 < 16 && $x < $y; $x2++)
332 | {
333 | $result .= " ";
334 | if ($x2 === 8) $result .= " ";
335 |
336 | $tempchr = ord($data[$x]);
337 | if ($tempchr === 0x09) $result .= "\\t";
338 | else if ($tempchr === 0x0D) $result .= "\\r";
339 | else if ($tempchr === 0x0A) $result .= "\\n";
340 | else if ($tempchr === 0x00) $result .= "\\0";
341 | else if ($tempchr < 32 || $tempchr > 126) $result .= " ";
342 | else $result .= " " . $data[$x];
343 |
344 | $x++;
345 | }
346 |
347 | $result .= "\n";
348 | }
349 |
350 | return $result;
351 | }
352 |
353 | // Outputs a JSON array (useful for captured output).
354 | public static function DisplayResult($result, $exit = true)
355 | {
356 | if (is_array($result)) echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
357 | else echo $result . "\n";
358 |
359 | if ($exit) exit();
360 | }
361 |
362 | // Useful for reparsing remaining parameters as new arguments.
363 | public static function ReinitArgs(&$args, $newargs)
364 | {
365 | // Process the parameters.
366 | $options = array(
367 | "shortmap" => array(
368 | "?" => "help"
369 | ),
370 | "rules" => array(
371 | )
372 | );
373 |
374 | foreach ($newargs as $arg) $options["rules"][$arg] = array("arg" => true, "multiple" => true);
375 | $options["rules"]["help"] = array("arg" => false);
376 |
377 | $args = self::ParseCommandLine($options, array_merge(array(""), $args["params"]));
378 |
379 | if (isset($args["opts"]["help"])) self::DisplayResult(array("success" => true, "options" => array_keys($options["rules"])));
380 | }
381 |
382 | // Tracks messages for a command-line interface app.
383 | private static $messages = array();
384 |
385 | public static function LogMessage($msg, $data = null)
386 | {
387 | if (isset($data)) $msg .= "\n\t" . trim(str_replace("\n", "\n\t", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)));
388 |
389 | self::$messages[] = $msg;
390 |
391 | fwrite(STDERR, $msg . "\n");
392 | }
393 |
394 | public static function DisplayError($msg, $result = false, $exit = true)
395 | {
396 | self::LogMessage(($exit ? "[Error] " : "") . $msg);
397 |
398 | if ($result !== false && is_array($result) && isset($result["error"]) && isset($result["errorcode"])) self::LogMessage("[Error] " . $result["error"] . " (" . $result["errorcode"] . ")", (isset($result["info"]) ? $result["info"] : null));
399 |
400 | if ($exit) exit();
401 | }
402 |
403 | public static function GetLogMessages($filters = array())
404 | {
405 | if (is_string($filters)) $filters = array($filters);
406 |
407 | $result = array();
408 | foreach (self::$messages as $message)
409 | {
410 | $found = (!count($filters));
411 | foreach ($filters as $filter)
412 | {
413 | if (preg_match($filter, $message)) $found = true;
414 | }
415 |
416 | if ($found) $result[] = $message;
417 | }
418 |
419 | return $result;
420 | }
421 |
422 | public static function ResetLogMessages()
423 | {
424 | self::$messages = array();
425 | }
426 |
427 |
428 | private static $timerinfo = array();
429 |
430 | public static function StartTimer()
431 | {
432 | $ts = microtime(true);
433 |
434 | self::$timerinfo = array(
435 | "start" => $ts,
436 | "diff" => $ts
437 | );
438 | }
439 |
440 | public static function UpdateTimer()
441 | {
442 | $ts = microtime(true);
443 | $diff = $ts - self::$timerinfo["diff"];
444 | self::$timerinfo["diff"] = $ts;
445 |
446 | $result = array(
447 | "success" => true,
448 | "diff" => sprintf("%.2f", $diff),
449 | "total" => sprintf("%.2f", $ts - self::$timerinfo["start"])
450 | );
451 |
452 | return $result;
453 | }
454 | }
455 | ?>
--------------------------------------------------------------------------------
/client/support/crc32_stream.php:
--------------------------------------------------------------------------------
1 | 0x04C11DB7, "start" => 0xFFFFFFFF, "xor" => 0xFFFFFFFF, "refdata" => 1, "refcrc" => 1);
14 |
15 | public function __construct()
16 | {
17 | $this->open = false;
18 | }
19 |
20 | public function Init($options = false)
21 | {
22 | if ($options === false && function_exists("hash_init")) $this->hash = hash_init("crc32b");
23 | else
24 | {
25 | if ($options === false) $options = self::$default;
26 |
27 | $this->hash = false;
28 | $this->crctable = array();
29 | $poly = $this->LIM32($options["poly"]);
30 | for ($x = 0; $x < 256; $x++)
31 | {
32 | $c = $this->SHL32($x, 24);
33 | for ($y = 0; $y < 8; $y++) $c = $this->SHL32($c, 1) ^ ($c & 0x80000000 ? $poly : 0);
34 | $this->crctable[$x] = $c;
35 | }
36 |
37 | $this->datareflect = $options["refdata"];
38 | $this->crcreflect = $options["refcrc"];
39 | $this->firstcrc = $options["start"];
40 | $this->currcrc = $options["start"];
41 | $this->finalxor = $options["xor"];
42 | }
43 |
44 | $this->open = true;
45 | }
46 |
47 | public function AddData($data)
48 | {
49 | if (!$this->open) return false;
50 |
51 | if ($this->hash !== false) hash_update($this->hash, $data);
52 | else
53 | {
54 | $y = strlen($data);
55 |
56 | for ($x = 0; $x < $y; $x++)
57 | {
58 | if ($this->datareflect) $this->currcrc = $this->SHL32($this->currcrc, 8) ^ $this->crctable[$this->SHR32($this->currcrc, 24) ^ self::$revlookup[ord($data[$x])]];
59 | else $this->currcrc = $this->SHL32($this->currcrc, 8) ^ $this->crctable[$this->SHR32($this->currcrc, 24) ^ ord($data[$x])];
60 | }
61 | }
62 |
63 | return true;
64 | }
65 |
66 | public function Finalize()
67 | {
68 | if (!$this->open) return false;
69 |
70 | if ($this->hash !== false)
71 | {
72 | $result = hexdec(hash_final($this->hash));
73 |
74 | $this->hash = hash_init("crc32b");
75 | }
76 | else
77 | {
78 | if ($this->crcreflect)
79 | {
80 | $tempcrc = $this->currcrc;
81 | $this->currcrc = self::$revlookup[$this->SHR32($tempcrc, 24)] | $this->SHL32(self::$revlookup[$this->SHR32($tempcrc, 16) & 0xFF], 8) | $this->SHL32(self::$revlookup[$this->SHR32($tempcrc, 8) & 0xFF], 16) | $this->SHL32(self::$revlookup[$this->LIM32($tempcrc & 0xFF)], 24);
82 | }
83 | $result = $this->currcrc ^ $this->finalxor;
84 |
85 | $this->currcrc = $this->firstcrc;
86 | }
87 |
88 | return $result;
89 | }
90 |
91 | // These functions are a hacky, but effective way of enforcing unsigned 32-bit integers onto a generic signed int.
92 | // Allow bitwise operations to work across platforms. Minimum integer size must be 32-bit.
93 | private function SHR32($num, $bits)
94 | {
95 | $num = (int)$num;
96 | if ($bits < 0) $bits = 0;
97 |
98 | if ($num < 0 && $bits)
99 | {
100 | $num = ($num >> 1) & 0x7FFFFFFF;
101 | $bits--;
102 | }
103 |
104 | return $this->LIM32($num >> $bits);
105 | }
106 |
107 | private function SHL32($num, $bits)
108 | {
109 | if ($bits < 0) $bits = 0;
110 |
111 | return $this->LIM32((int)$num << $bits);
112 | }
113 |
114 | private function LIM32($num)
115 | {
116 | return (int)((int)$num & 0xFFFFFFFF);
117 | }
118 | }
119 | ?>
--------------------------------------------------------------------------------
/client/support/deflate_stream.php:
--------------------------------------------------------------------------------
1 | open = false;
13 | }
14 |
15 | public function __destruct()
16 | {
17 | $this->Finalize();
18 | }
19 |
20 | public static function IsSupported()
21 | {
22 | if (!is_bool(self::$supported))
23 | {
24 | self::$supported = function_exists("stream_filter_append") && function_exists("stream_filter_remove") && function_exists("gzcompress");
25 | if (self::$supported)
26 | {
27 | $data = self::Compress("test");
28 | if ($data === false || $data === "") self::$supported = false;
29 | else
30 | {
31 | $data = self::Uncompress($data);
32 | if ($data === false || $data !== "test") self::$supported = false;
33 | }
34 | }
35 | }
36 |
37 | return self::$supported;
38 | }
39 |
40 | public static function Compress($data, $compresslevel = -1, $options = array())
41 | {
42 | $ds = new DeflateStream;
43 | if (!$ds->Init("wb", $compresslevel, $options)) return false;
44 | if (!$ds->Write($data)) return false;
45 | if (!$ds->Finalize()) return false;
46 | $data = $ds->Read();
47 |
48 | return $data;
49 | }
50 |
51 | public static function Uncompress($data, $options = array("type" => "auto"))
52 | {
53 | $ds = new DeflateStream;
54 | if (!$ds->Init("rb", -1, $options)) return false;
55 | if (!$ds->Write($data)) return false;
56 | if (!$ds->Finalize()) return false;
57 | $data = $ds->Read();
58 |
59 | return $data;
60 | }
61 |
62 | public function Init($mode, $compresslevel = -1, $options = array())
63 | {
64 | if ($mode !== "rb" && $mode !== "wb") return false;
65 | if ($this->open) $this->Finalize();
66 |
67 | $this->fp = fopen("php://memory", "w+b");
68 | if ($this->fp === false) return false;
69 | $this->compress = ($mode == "wb");
70 | if (!isset($options["type"])) $options["type"] = "rfc1951";
71 |
72 | if ($options["type"] == "rfc1950") $options["type"] = "zlib";
73 | else if ($options["type"] == "rfc1952") $options["type"] = "gzip";
74 |
75 | if ($options["type"] != "zlib" && $options["type"] != "gzip" && ($this->compress || $options["type"] != "auto")) $options["type"] = "raw";
76 | $this->options = $options;
77 |
78 | // Add the deflate filter.
79 | if ($this->compress) $this->filter = stream_filter_append($this->fp, "zlib.deflate", STREAM_FILTER_WRITE, $compresslevel);
80 | else $this->filter = stream_filter_append($this->fp, "zlib.inflate", STREAM_FILTER_READ);
81 |
82 | $this->open = true;
83 | $this->indata = "";
84 | $this->outdata = "";
85 |
86 | if ($this->compress)
87 | {
88 | if ($this->options["type"] == "zlib")
89 | {
90 | $this->outdata .= "\x78\x9C";
91 | $this->options["a"] = 1;
92 | $this->options["b"] = 0;
93 | }
94 | else if ($this->options["type"] == "gzip")
95 | {
96 | if (!class_exists("CRC32Stream", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/crc32_stream.php";
97 |
98 | $this->options["crc32"] = new CRC32Stream();
99 | $this->options["crc32"]->Init();
100 | $this->options["bytes"] = 0;
101 |
102 | $this->outdata .= "\x1F\x8B\x08";
103 | $flags = 0;
104 | if (isset($this->options["filename"])) $flags |= 0x08;
105 | if (isset($this->options["comment"])) $flags |= 0x10;
106 | $this->outdata .= chr($flags);
107 | $this->outdata .= "\x00\x00\x00\x00";
108 | $this->outdata .= "\x00";
109 | $this->outdata .= "\x03";
110 |
111 | if (isset($this->options["filename"])) $this->outdata .= str_replace("\x00", " ", $this->options["filename"]) . "\x00";
112 | if (isset($this->options["comment"])) $this->outdata .= str_replace("\x00", " ", $this->options["comment"]) . "\x00";
113 | }
114 | }
115 | else
116 | {
117 | $this->options["header"] = false;
118 | }
119 |
120 | return true;
121 | }
122 |
123 | public function Read()
124 | {
125 | $result = $this->outdata;
126 | $this->outdata = "";
127 |
128 | return $result;
129 | }
130 |
131 | public function Write($data)
132 | {
133 | if (!$this->open) return false;
134 |
135 | if ($this->compress)
136 | {
137 | if ($this->options["type"] == "zlib")
138 | {
139 | // Adler-32.
140 | $y = strlen($data);
141 | for ($x = 0; $x < $y; $x++)
142 | {
143 | $this->options["a"] = ($this->options["a"] + ord($data[$x])) % 65521;
144 | $this->options["b"] = ($this->options["b"] + $this->options["a"]) % 65521;
145 | }
146 | }
147 | else if ($this->options["type"] == "gzip")
148 | {
149 | $this->options["crc32"]->AddData($data);
150 | $this->options["bytes"] = $this->ADD32($this->options["bytes"], strlen($data));
151 | }
152 |
153 | $this->indata .= $data;
154 | while (strlen($this->indata) >= 65536)
155 | {
156 | fwrite($this->fp, substr($this->indata, 0, 65536));
157 | $this->indata = substr($this->indata, 65536);
158 |
159 | $this->ProcessOutput();
160 | }
161 | }
162 | else
163 | {
164 | $this->indata .= $data;
165 | $this->ProcessInput();
166 | }
167 |
168 | return true;
169 | }
170 |
171 | // Finalizes the stream.
172 | public function Finalize()
173 | {
174 | if (!$this->open) return false;
175 |
176 | if (!$this->compress) $this->ProcessInput(true);
177 |
178 | if (strlen($this->indata) > 0)
179 | {
180 | fwrite($this->fp, $this->indata);
181 | $this->indata = "";
182 | }
183 |
184 | // Removing the filter pushes the last buffer into the stream.
185 | stream_filter_remove($this->filter);
186 | $this->filter = false;
187 |
188 | $this->ProcessOutput();
189 |
190 | fclose($this->fp);
191 |
192 | if ($this->compress)
193 | {
194 | if ($this->options["type"] == "zlib") $this->outdata .= pack("N", $this->SHL32($this->options["b"], 16) | $this->options["a"]);
195 | else if ($this->options["type"] == "gzip") $this->outdata .= pack("V", $this->options["crc32"]->Finalize()) . pack("V", $this->options["bytes"]);
196 | }
197 |
198 | $this->open = false;
199 |
200 | return true;
201 | }
202 |
203 | private function ProcessOutput()
204 | {
205 | rewind($this->fp);
206 |
207 | // Hack! Because ftell() on a stream with a filter is still broken even under the latest PHP a mere 11 years later.
208 | // See: https://bugs.php.net/bug.php?id=49874
209 | ob_start();
210 | fpassthru($this->fp);
211 | $this->outdata .= ob_get_contents();
212 | ob_end_clean();
213 |
214 | rewind($this->fp);
215 | ftruncate($this->fp, 0);
216 | }
217 |
218 | private function ProcessInput($final = false)
219 | {
220 | // Automatically determine the type of data based on the header signature.
221 | if ($this->options["type"] == "auto")
222 | {
223 | if (strlen($this->indata) >= 3)
224 | {
225 | $zlibtest = unpack("n", substr($this->indata, 0, 2));
226 |
227 | if (substr($this->indata, 0, 3) === "\x1F\x8B\x08") $this->options["type"] = "gzip";
228 | else if ((ord($this->indata[0]) & 0x0F) == 8 && ((ord($this->indata[0]) & 0xF0) >> 4) < 8 && $zlibtest[1] % 31 == 0) $this->options["type"] = "zlib";
229 | else $this->options["type"] = "raw";
230 | }
231 | else if ($final) $this->options["type"] = "raw";
232 | }
233 |
234 | if ($this->options["type"] == "gzip")
235 | {
236 | if (!$this->options["header"])
237 | {
238 | if (strlen($this->indata) >= 10)
239 | {
240 | $idcm = substr($this->indata, 0, 3);
241 | $flg = ord($this->indata[3]);
242 |
243 | if ($idcm !== "\x1F\x8B\x08") $this->options["type"] = "ignore";
244 | else
245 | {
246 | // Calculate the number of bytes to skip. If flags are set, the size can be dynamic.
247 | $size = 10;
248 | $y = strlen($this->indata);
249 |
250 | // FLG.FEXTRA
251 | if ($size && ($flg & 0x04))
252 | {
253 | if ($size + 2 >= $y) $size = 0;
254 | else
255 | {
256 | $xlen = unpack("v", substr($this->indata, $size, 2));
257 | $size = ($size + 2 + $xlen <= $y ? $size + 2 + $xlen : 0);
258 | }
259 | }
260 |
261 | // FLG.FNAME
262 | if ($size && ($flg & 0x08))
263 | {
264 | $pos = strpos($this->indata, "\x00", $size);
265 | $size = ($pos !== false ? $pos + 1 : 0);
266 | }
267 |
268 | // FLG.FCOMMENT
269 | if ($size && ($flg & 0x10))
270 | {
271 | $pos = strpos($this->indata, "\x00", $size);
272 | $size = ($pos !== false ? $pos + 1 : 0);
273 | }
274 |
275 | // FLG.FHCRC
276 | if ($size && ($flg & 0x02)) $size = ($size + 2 <= $y ? $size + 2 : 0);
277 |
278 | if ($size)
279 | {
280 | $this->indata = substr($this->indata, $size);
281 | $this->options["header"] = true;
282 | }
283 | }
284 | }
285 | }
286 |
287 | if ($this->options["header"] && strlen($this->indata) > 8)
288 | {
289 | fwrite($this->fp, substr($this->indata, 0, -8));
290 | $this->indata = substr($this->indata, -8);
291 |
292 | $this->ProcessOutput();
293 | }
294 |
295 | if ($final) $this->indata = "";
296 | }
297 | else if ($this->options["type"] == "zlib")
298 | {
299 | if (!$this->options["header"])
300 | {
301 | if (strlen($this->indata) >= 2)
302 | {
303 | $cmf = ord($this->indata[0]);
304 | $flg = ord($this->indata[1]);
305 | $cm = $cmf & 0x0F;
306 | $cinfo = ($cmf & 0xF0) >> 4;
307 |
308 | // Compression method 'deflate' ($cm = 8), window size - 8 ($cinfo < 8), no preset dictionaries ($flg bit 5), checksum validates.
309 | if ($cm != 8 || $cinfo > 7 || ($flg & 0x20) || (($cmf << 8 | $flg) % 31) != 0) $this->options["type"] = "ignore";
310 | else
311 | {
312 | $this->indata = substr($this->indata, 2);
313 | $this->options["header"] = true;
314 | }
315 | }
316 | }
317 |
318 | if ($this->options["header"] && strlen($this->indata) > 4)
319 | {
320 | fwrite($this->fp, substr($this->indata, 0, -4));
321 | $this->indata = substr($this->indata, -4);
322 |
323 | $this->ProcessOutput();
324 | }
325 |
326 | if ($final) $this->indata = "";
327 | }
328 |
329 | if ($this->options["type"] == "raw")
330 | {
331 | fwrite($this->fp, $this->indata);
332 | $this->indata = "";
333 |
334 | $this->ProcessOutput();
335 | }
336 |
337 | // Only set when an unrecoverable header error has occurred for gzip or zlib.
338 | if ($this->options["type"] == "ignore") $this->indata = "";
339 | }
340 |
341 | private function SHL32($num, $bits)
342 | {
343 | if ($bits < 0) $bits = 0;
344 |
345 | return $this->LIM32((int)$num << $bits);
346 | }
347 |
348 | private function LIM32($num)
349 | {
350 | return (int)((int)$num & 0xFFFFFFFF);
351 | }
352 |
353 | private function ADD32($num, $num2)
354 | {
355 | $num = (int)$num;
356 | $num2 = (int)$num2;
357 | $add = ((($num >> 30) & 0x03) + (($num2 >> 30) & 0x03));
358 | $num = ((int)($num & 0x3FFFFFFF) + (int)($num2 & 0x3FFFFFFF));
359 | if ($num & 0x40000000) $add++;
360 | $num = (int)(($num & 0x3FFFFFFF) | (($add & 0x03) << 30));
361 |
362 | return $num;
363 | }
364 | }
365 | ?>
--------------------------------------------------------------------------------
/client/support/sdk_drc_client.php:
--------------------------------------------------------------------------------
1 | drc_channels = array();
20 | $this->drc_client_id = false;
21 | }
22 |
23 | public function Read($finished = true, $wait = false)
24 | {
25 | $result = parent::Read();
26 | if (!$result["success"] || $result["data"] === false) return $result;
27 |
28 | $result["data"] = @json_decode($result["data"]["payload"], true);
29 |
30 | if (!is_array($result["data"])) $result["data"] = array("success" => false, "error" => "Invalid packet.", "errorcode" => "invalid_packet");
31 | else if (!$result["data"]["success"]) return $result;
32 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "JOINED")
33 | {
34 | if (isset($result["data"]["channelname"]) && isset($result["data"]["protocol"]) && isset($result["data"]["clients"]))
35 | {
36 | $this->drc_channels[$result["data"]["channel"]] = $result["data"];
37 | $this->drc_client_id = $result["data"]["id"];
38 | }
39 | else if (isset($this->drc_channels[$result["data"]["channel"]]))
40 | {
41 | $this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]] = $result["data"]["info"];
42 | }
43 | }
44 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "SET_EXTRA" && isset($this->drc_channels[$result["data"]["channel"]]) && isset($this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]))
45 | {
46 | $this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]["extra"] = $result["data"]["extra"];
47 | }
48 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "LEFT" && isset($this->drc_channels[$result["data"]["channel"]]))
49 | {
50 | if ($result["data"]["id"] === $this->drc_client_id) unset($this->drc_channels[$result["data"]["channel"]]);
51 | else unset($this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]);
52 | }
53 |
54 | return $result;
55 | }
56 |
57 | public function CreateToken($authtoken, $channelname, $protocol, $clientmode, $extra = array(), $wait = false)
58 | {
59 | $data = array(
60 | "cmd" => "GRANT",
61 | "channel" => $channelname,
62 | "protocol" => $protocol,
63 | "clientmode" => $clientmode,
64 | "extra" => $extra
65 | );
66 |
67 | if ($authtoken !== false) $data["token"] = $authtoken;
68 |
69 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
70 | if (!$result["success"]) return $result;
71 |
72 | if ($wait)
73 | {
74 | $result = $this->Wait();
75 | while ($result["success"])
76 | {
77 | do
78 | {
79 | $result = $this->Read();
80 | if (!$result["success"]) return $result;
81 |
82 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "GRANTED" && isset($result["data"]["channelname"]) && $result["data"]["channelname"] === $channelname && isset($result["data"]["protocol"]) && $result["data"]["protocol"] === $protocol && isset($result["data"]["token"]))
83 | {
84 | return $result;
85 | }
86 | } while ($result["data"] !== false);
87 |
88 | $result = $this->Wait();
89 | }
90 | }
91 |
92 | return $result;
93 | }
94 |
95 | public function JoinChannel($channelname, $protocol, $token, $wait = false, $allowipauth = true)
96 | {
97 | $data = array(
98 | "cmd" => "JOIN",
99 | "channel" => $channelname,
100 | "protocol" => $protocol
101 | );
102 |
103 | if ($token !== false) $data["token"] = $token;
104 | if ($allowipauth === false) $data["ipauth"] = false;
105 |
106 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
107 | if (!$result["success"]) return $result;
108 |
109 | if ($wait)
110 | {
111 | $result = $this->Wait();
112 | while ($result["success"])
113 | {
114 | do
115 | {
116 | $result = $this->Read();
117 | if (!$result["success"]) return $result;
118 |
119 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "JOINED" && isset($result["data"]["channelname"]) && $result["data"]["channelname"] === $channelname && isset($result["data"]["protocol"]) && $result["data"]["protocol"] === $protocol && isset($result["data"]["clients"]))
120 | {
121 | return $result;
122 | }
123 | } while ($result["data"] !== false);
124 |
125 | $result = $this->Wait();
126 | }
127 | }
128 |
129 | return $result;
130 | }
131 |
132 | public function GetChannels()
133 | {
134 | return $this->drc_channels;
135 | }
136 |
137 | public function GetChannel($channel)
138 | {
139 | return (isset($this->drc_channels[$channel]) ? $this->drc_channels[$channel] : false);
140 | }
141 |
142 | public function GetClientID()
143 | {
144 | return $this->drc_client_id;
145 | }
146 |
147 | public function SetExtra($channel, $id, $extra = array(), $wait = false)
148 | {
149 | $data = array(
150 | "channel" => $channel,
151 | "cmd" => "SET_EXTRA",
152 | "id" => $id,
153 | "extra" => $extra
154 | );
155 |
156 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
157 | if (!$result["success"]) return $result;
158 |
159 | if ($wait)
160 | {
161 | $result = $this->Wait();
162 | while ($result["success"])
163 | {
164 | do
165 | {
166 | $result = $this->Read();
167 | if (!$result["success"]) return $result;
168 |
169 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "SET_EXTRA" && $result["data"]["id"] === $id) return $result;
170 | } while ($result["data"] !== false);
171 |
172 | $result = $this->Wait();
173 | }
174 | }
175 |
176 | return $result;
177 | }
178 |
179 | public function SendCommand($channel, $cmd, $to, $options = array(), $wait = false)
180 | {
181 | $data = array(
182 | "channel" => $channel,
183 | "cmd" => $cmd,
184 | "to" => $to
185 | );
186 |
187 | $data = $data + $options;
188 |
189 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
190 | if (!$result["success"]) return $result;
191 |
192 | if (is_string($wait))
193 | {
194 | $result = $this->Wait();
195 | while ($result["success"])
196 | {
197 | do
198 | {
199 | $result = $this->Read();
200 | if (!$result["success"]) return $result;
201 |
202 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === $wait && ($to < 0 || $result["data"]["from"] === $to)) return $result;
203 | } while ($result["data"] !== false);
204 |
205 | $result = $this->Wait();
206 | }
207 | }
208 | else if ($wait)
209 | {
210 | do
211 | {
212 | $result = $this->Wait();
213 | } while ($result["success"] && $this->NeedsWrite());
214 | }
215 |
216 | return $result;
217 | }
218 |
219 | public function GetRandomAuthClientID($channel)
220 | {
221 | if (!isset($this->drc_channels[$channel])) return false;
222 |
223 | $idmap = array();
224 | foreach ($this->drc_channels[$channel]["clients"] as $id => $info)
225 | {
226 | if ($info["auth"]) $idmap[] = $id;
227 | }
228 |
229 | $y = count($idmap);
230 | if (!$y) return false;
231 |
232 | return $idmap[random_int(0, $y - 1)];
233 | }
234 |
235 | public function SendCommandToAuthClients($channel, $cmd, $options = array(), $wait = false)
236 | {
237 | if (!isset($this->drc_channels[$channel])) return array("success" => false, "error" => "Invalid channel.", "errorcode" => "invalid_channel");
238 |
239 | foreach ($this->drc_channels[$channel]["clients"] as $id => $info)
240 | {
241 | if ($info["auth"])
242 | {
243 | $result = $this->SendCommand($channel, $cmd, $id, $options, $wait);
244 | if (!$result["success"]) return $result;
245 | }
246 | }
247 |
248 | return array("success" => true);
249 | }
250 |
251 | public function LeaveChannel($channel, $wait = false)
252 | {
253 | unset($this->drc_channels[$channel]);
254 |
255 | $data = array(
256 | "cmd" => "LEAVE",
257 | "channel" => $channel
258 | );
259 |
260 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT);
261 | if (!$result["success"]) return $result;
262 |
263 | if ($wait)
264 | {
265 | $result = $this->Wait();
266 | while ($result["success"])
267 | {
268 | do
269 | {
270 | $result = $this->Read();
271 | if (!$result["success"]) return $result;
272 |
273 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "LEFT" && !isset($this->drc_channels[$channel])) return $result;
274 | } while ($result["data"] !== false);
275 |
276 | $result = $this->Wait();
277 | }
278 | }
279 |
280 | return $result;
281 | }
282 | }
283 | ?>
--------------------------------------------------------------------------------
/client/support/utf_utils.php:
--------------------------------------------------------------------------------
1 | = 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F));
21 | }
22 |
23 | public static function Convert($data, $srctype, $desttype)
24 | {
25 | $arr = is_array($data);
26 | if ($arr) $srctype = self::UTF32_ARRAY;
27 | $x = 0;
28 | $y = ($arr ? count($data) : strlen($data));
29 | $result = ($desttype === self::UTF32_ARRAY ? array() : "");
30 | if (!$arr && $srctype === self::UTF32_ARRAY) return $result;
31 |
32 | $first = true;
33 |
34 | if ($srctype === self::UTF8_BOM)
35 | {
36 | if (substr($data, 0, 3) === "\xEF\xBB\xBF") $x = 3;
37 |
38 | $srctype = self::UTF8;
39 | }
40 |
41 | if ($srctype === self::UTF16_BOM)
42 | {
43 | if (substr($data, 0, 2) === "\xFE\xFF")
44 | {
45 | $srctype = self::UTF16_BE;
46 | $x = 2;
47 | }
48 | else if (substr($data, 0, 2) === "\xFF\xFE")
49 | {
50 | $srctype = self::UTF16_LE;
51 | $x = 2;
52 | }
53 | else
54 | {
55 | $srctype = self::UTF16_LE;
56 | }
57 | }
58 |
59 | if ($srctype === self::UTF32_BOM)
60 | {
61 | if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
62 | {
63 | $srctype = self::UTF32_BE;
64 | $x = 4;
65 | }
66 | else if (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
67 | {
68 | $srctype = self::UTF32_LE;
69 | $x = 4;
70 | }
71 | else
72 | {
73 | $srctype = self::UTF32_LE;
74 | }
75 | }
76 |
77 | while ($x < $y)
78 | {
79 | // Read the next valid code point.
80 | $val = false;
81 |
82 | switch ($srctype)
83 | {
84 | case self::UTF8:
85 | {
86 | $tempchr = ord($data[$x]);
87 | if ($tempchr <= 0x7F)
88 | {
89 | $val = $tempchr;
90 | $x++;
91 | }
92 | else if ($tempchr < 0xC2) $x++;
93 | else
94 | {
95 | $left = $y - $x;
96 | if ($left < 2) $x++;
97 | else
98 | {
99 | $tempchr2 = ord($data[$x + 1]);
100 |
101 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF))
102 | {
103 | $val = (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F);
104 | $x += 2;
105 | }
106 | else if ($left < 3) $x++;
107 | else
108 | {
109 | $tempchr3 = ord($data[$x + 2]);
110 |
111 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) $x++;
112 | else
113 | {
114 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)))
115 | {
116 | $val = (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F);
117 | $x += 3;
118 | }
119 | else if ($left < 4) $x++;
120 | else
121 | {
122 | $tempchr4 = ord($data[$x + 3]);
123 |
124 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) $x++;
125 | else if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)))
126 | {
127 | $val = (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F);
128 | $x += 4;
129 | }
130 | else
131 | {
132 | $x++;
133 | }
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 | break;
141 | }
142 | case self::UTF16_LE:
143 | {
144 | if ($x + 1 >= $y) $x = $y;
145 | else
146 | {
147 | $val = unpack("v", substr($data, $x, 2))[1];
148 | $x += 2;
149 |
150 | if ($val >= 0xD800 && $val <= 0xDBFF)
151 | {
152 | if ($x + 1 >= $y)
153 | {
154 | $x = $y;
155 | $val = false;
156 | }
157 | else
158 | {
159 | $val2 = unpack("v", substr($data, $x, 2))[1];
160 |
161 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false;
162 | else
163 | {
164 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000;
165 | $x += 2;
166 | }
167 | }
168 | }
169 | }
170 |
171 | break;
172 | }
173 | case self::UTF16_BE:
174 | {
175 | if ($x + 1 >= $y) $x = $y;
176 | else
177 | {
178 | $val = unpack("n", substr($data, $x, 2))[1];
179 | $x += 2;
180 |
181 | if ($val >= 0xD800 && $val <= 0xDBFF)
182 | {
183 | if ($x + 1 >= $y)
184 | {
185 | $x = $y;
186 | $val = false;
187 | }
188 | else
189 | {
190 | $val2 = unpack("n", substr($data, $x, 2))[1];
191 |
192 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false;
193 | else
194 | {
195 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000;
196 | $x += 2;
197 | }
198 | }
199 | }
200 | }
201 |
202 | break;
203 | }
204 | case self::UTF32_LE:
205 | {
206 | if ($x + 3 >= $y) $x = $y;
207 | else
208 | {
209 | $val = unpack("V", substr($data, $x, 4))[1];
210 | $x += 4;
211 | }
212 |
213 | break;
214 | }
215 | case self::UTF32_BE:
216 | {
217 | if ($x + 3 >= $y) $x = $y;
218 | else
219 | {
220 | $val = unpack("N", substr($data, $x, 4))[1];
221 | $x += 4;
222 | }
223 |
224 | break;
225 | }
226 | case self::UTF32_ARRAY:
227 | {
228 | $val = (int)$data[$x];
229 | $x++;
230 |
231 | break;
232 | }
233 | default: $x = $y; break;
234 | }
235 |
236 | // Make sure it is a valid Unicode value.
237 | // 0xD800-0xDFFF are for UTF-16 surrogate pairs. Invalid characters.
238 | // 0xFDD0-0xFDEF are non-characters.
239 | // 0x*FFFE and 0x*FFFF are reserved.
240 | // The largest possible character is 0x10FFFF.
241 | // First character can't be a combining code point.
242 | if ($val !== false && !($val < 0 || ($val >= 0xD800 && $val <= 0xDFFF) || ($val >= 0xFDD0 && $val <= 0xFDEF) || ($val & 0xFFFE) == 0xFFFE || $val > 0x10FFFF || ($first && self::IsCombiningCodePoint($val))))
243 | {
244 | if ($first)
245 | {
246 | if ($desttype === self::UTF8_BOM)
247 | {
248 | $result .= "\xEF\xBB\xBF";
249 |
250 | $desttype = self::UTF8;
251 | }
252 |
253 | if ($desttype === self::UTF16_BOM)
254 | {
255 | $result .= "\xFF\xFE";
256 |
257 | $desttype = self::UTF16_LE;
258 | }
259 |
260 | if ($srctype === self::UTF32_BOM)
261 | {
262 | $result .= "\xFF\xFE\x00\x00";
263 |
264 | $desttype = self::UTF32_LE;
265 | }
266 |
267 | $first = false;
268 | }
269 |
270 | switch ($desttype)
271 | {
272 | case self::UTF8:
273 | {
274 | if ($val <= 0x7F) $result .= chr($val);
275 | else if ($val <= 0x7FF) $result .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F));
276 | else if ($val <= 0xFFFF) $result .= chr(0xE0 | ($val >> 12)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F));
277 | else if ($val <= 0x10FFFF) $result .= chr(0xF0 | ($val >> 18)) . chr(0x80 | (($val >> 12) & 0x3F)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F));
278 |
279 | break;
280 | }
281 | case self::UTF16_LE:
282 | {
283 | if ($val <= 0xFFFF) $result .= pack("v", $val);
284 | else
285 | {
286 | $val -= 0x10000;
287 | $result .= pack("v", ((($val >> 10) & 0x3FF) + 0xD800));
288 | $result .= pack("v", (($val & 0x3FF) + 0xDC00));
289 | }
290 |
291 | break;
292 | }
293 | case self::UTF16_BE:
294 | {
295 | if ($val <= 0xFFFF) $result .= pack("n", $val);
296 | else
297 | {
298 | $val -= 0x10000;
299 | $result .= pack("n", ((($val >> 10) & 0x3FF) + 0xD800));
300 | $result .= pack("n", (($val & 0x3FF) + 0xDC00));
301 | }
302 |
303 | break;
304 | }
305 | case self::UTF32_LE:
306 | {
307 | $result .= pack("V", $val);
308 |
309 | break;
310 | }
311 | case self::UTF32_BE:
312 | {
313 | $result .= pack("N", $val);
314 |
315 | break;
316 | }
317 | case self::UTF32_ARRAY:
318 | {
319 | $result[] = $val;
320 |
321 | break;
322 | }
323 | default: $x = $y; break;
324 | }
325 | }
326 | }
327 |
328 | return $result;
329 | }
330 |
331 |
332 | protected const PUNYCODE_BASE = 36;
333 | protected const PUNYCODE_TMIN = 1;
334 | protected const PUNYCODE_TMAX = 26;
335 | protected const PUNYCODE_SKEW = 38;
336 | protected const PUNYCODE_DAMP = 700;
337 | protected const PUNYCODE_INITIAL_BIAS = 72;
338 | protected const PUNYCODE_INITIAL_N = 0x80;
339 | protected const PUNYCODE_DIGIT_MAP = "abcdefghijklmnopqrstuvwxyz0123456789";
340 |
341 | public static function ConvertToPunycode($domain)
342 | {
343 | // Reject invalid domain name lengths.
344 | if (strlen($domain) > 255) return false;
345 |
346 | $parts = explode(".", $domain);
347 |
348 | foreach ($parts as $num => $part)
349 | {
350 | // Reject invalid label lengths.
351 | $y = strlen($part);
352 | if ($y > 63) return false;
353 |
354 | // Skip already encoded portions.
355 | if (substr($part, 0, 4) === "xn--") continue;
356 |
357 | // Convert UTF-8 to UTF-32 code points.
358 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY);
359 |
360 | // Handle ASCII code points.
361 | $part2 = "";
362 | foreach ($data as $cp)
363 | {
364 | if ($cp <= 0x7F) $part2 .= strtolower(chr($cp));
365 | }
366 |
367 | $numhandled = strlen($part2);
368 | $y = count($data);
369 |
370 | if ($numhandled >= $y)
371 | {
372 | $parts[$num] = $part2;
373 |
374 | continue;
375 | }
376 |
377 | if ($numhandled) $part2 .= "-";
378 |
379 | $part2 = "xn--" . $part2;
380 |
381 | if (strlen($part2) > 63) return false;
382 |
383 | $bias = self::PUNYCODE_INITIAL_BIAS;
384 | $n = self::PUNYCODE_INITIAL_N;
385 | $delta = 0;
386 | $first = true;
387 |
388 | while ($numhandled < $y)
389 | {
390 | // Find the next largest unhandled code point.
391 | $cp2 = 0x01000000;
392 | foreach ($data as $cp)
393 | {
394 | if ($cp >= $n && $cp2 > $cp) $cp2 = $cp;
395 | }
396 |
397 | // Increase delta but prevent overflow.
398 | $delta += ($cp2 - $n) * ($numhandled + 1);
399 | if ($delta < 0) return false;
400 | $n = $cp2;
401 |
402 | foreach ($data as $cp)
403 | {
404 | if ($cp < $n)
405 | {
406 | $delta++;
407 |
408 | if ($delta < 0) return false;
409 | }
410 | else if ($cp === $n)
411 | {
412 | // Calculate and encode a variable length integer from the delta.
413 | $q = $delta;
414 | $x = 0;
415 | do
416 | {
417 | $x += self::PUNYCODE_BASE;
418 |
419 | if ($x <= $bias) $t = self::PUNYCODE_TMIN;
420 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX;
421 | else $t = $x - $bias;
422 |
423 | if ($q < $t) break;
424 |
425 | $part2 .= self::PUNYCODE_DIGIT_MAP[$t + (($q - $t) % (self::PUNYCODE_BASE - $t))];
426 |
427 | $q = (int)(($q - $t) / (self::PUNYCODE_BASE - $t));
428 |
429 | if (strlen($part2) > 63) return false;
430 | } while (1);
431 |
432 | $part2 .= self::PUNYCODE_DIGIT_MAP[$q];
433 | if (strlen($part2) > 63) return false;
434 |
435 | // Adapt bias.
436 | $numhandled++;
437 | $bias = self::InternalPunycodeAdapt($delta, $numhandled, $first);
438 | $delta = 0;
439 | $first = false;
440 | }
441 | }
442 |
443 | $delta++;
444 | $n++;
445 | }
446 |
447 | $parts[$num] = $part2;
448 | }
449 |
450 | return implode(".", $parts);
451 | }
452 |
453 | public static function ConvertFromPunycode($domain)
454 | {
455 | // Reject invalid domain name lengths.
456 | if (strlen($domain) > 255) return false;
457 |
458 | $parts = explode(".", $domain);
459 |
460 | foreach ($parts as $num => $part)
461 | {
462 | // Reject invalid label lengths.
463 | $y = strlen($part);
464 | if ($y > 63) return false;
465 |
466 | // Skip unencoded portions.
467 | if (substr($part, 0, 4) !== "xn--") continue;
468 |
469 | $part = substr($part, 4);
470 |
471 | // Convert UTF-8 to UTF-32 code points.
472 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY);
473 |
474 | // Handle ASCII code points.
475 | $hyphen = ord("-");
476 | for ($x = count($data); $x && $data[$x - 1] !== $hyphen; $x--);
477 | if (!$x) $data2 = array();
478 | else
479 | {
480 | $data2 = array_splice($data, 0, $x - 1);
481 |
482 | array_shift($data);
483 | }
484 |
485 | $numhandled = count($data2);
486 |
487 | $bias = self::PUNYCODE_INITIAL_BIAS;
488 | $n = self::PUNYCODE_INITIAL_N;
489 | $delta = 0;
490 | $first = true;
491 |
492 | $pos = 0;
493 | $y = count($data);
494 | while ($pos < $y)
495 | {
496 | // Calculate and decode a delta from the variable length integer.
497 | $olddelta = $delta;
498 | $w = 1;
499 | $x = 0;
500 | do
501 | {
502 | $x += self::PUNYCODE_BASE;
503 |
504 | $cp = $data[$pos];
505 | $pos++;
506 |
507 | if ($cp >= ord("a") && $cp <= ord("z")) $digit = $cp - ord("a");
508 | else if ($cp >= ord("A") && $cp <= ord("Z")) $digit = $cp - ord("A");
509 | else if ($cp >= ord("0") && $cp <= ord("9")) $digit = $cp - ord("0") + 26;
510 | else return false;
511 |
512 | $delta += $digit * $w;
513 | if ($delta < 0) return false;
514 |
515 | if ($x <= $bias) $t = self::PUNYCODE_TMIN;
516 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX;
517 | else $t = $x - $bias;
518 |
519 | if ($digit < $t) break;
520 |
521 | $w *= (self::PUNYCODE_BASE - $t);
522 | if ($w < 0) return false;
523 | } while (1);
524 |
525 | // Adapt bias.
526 | $numhandled++;
527 | $bias = self::InternalPunycodeAdapt($delta - $olddelta, $numhandled, $first);
528 | $first = false;
529 |
530 | // Delta was supposed to wrap around from $numhandled to 0, incrementing $n each time, so fix that now.
531 | $n += (int)($delta / $numhandled);
532 | $delta %= $numhandled;
533 |
534 | // Insert $n (the code point) at the delta position.
535 | array_splice($data2, $delta, 0, array($n));
536 | $delta++;
537 | }
538 |
539 | $parts[$num] = self::Convert($data2, self::UTF32_ARRAY, self::UTF8);
540 | }
541 |
542 | return implode(".", $parts);
543 | }
544 |
545 | // RFC3492 adapt() function.
546 | protected static function InternalPunycodeAdapt($delta, $numpoints, $first)
547 | {
548 | $delta = ($first ? (int)($delta / self::PUNYCODE_DAMP) : $delta >> 1);
549 | $delta += (int)($delta / $numpoints);
550 |
551 | $y = self::PUNYCODE_BASE - self::PUNYCODE_TMIN;
552 |
553 | $condval = (int)(($y * self::PUNYCODE_TMAX) / 2);
554 | for ($x = 0; $delta > $condval; $x += self::PUNYCODE_BASE) $delta = (int)($delta / $y);
555 |
556 | return (int)($x + ((($y + 1) * $delta) / ($delta + self::PUNYCODE_SKEW)));
557 | }
558 | }
559 | ?>
--------------------------------------------------------------------------------
/server/index.php:
--------------------------------------------------------------------------------
1 | false,
45 | "error" => $msg,
46 | "errorcode" => $msgcode
47 | );
48 |
49 | if ($info !== false) $result["info"] = $info;
50 |
51 | OutputAPIResult($result);
52 | }
53 |
54 | // Handle API calls.
55 | if (isset($_REQUEST["api"]))
56 | {
57 | if ($_REQUEST["api"] == "init")
58 | {
59 | if (!isset($_REQUEST["location"]) || !is_string($_REQUEST["location"])) OutputAPIError("Missing or invalid 'location'.", "missing_location");
60 |
61 | $rng = new CSPRNG();
62 |
63 | $freqmap = json_decode(file_get_contents("support/en_us_lite.json"), true);
64 |
65 | $words = array();
66 | for ($x = 0; $x < 3; $x++) $words[] = $rng->GenerateWordLite($freqmap, $rng->GetInt(4, 5));
67 | $words = implode("-", $words);
68 | $channelname = "OBSManager-" . $words;
69 |
70 | $drc = new DRCClient();
71 |
72 | // Connect to the server.
73 | $result = $drc->Connect($drc_private_url, $drc_private_origin);
74 | if (!$result["success"]) OutputAPIError("Unable to connect to DRC.", "drc_connect_failed", $result);
75 |
76 | // Create a grant token.
77 | $result = $drc->CreateToken(false, $channelname, "obs-websocket-remote-api", DRCClient::CM_SEND_TO_ANY, array("location" => $_REQUEST["location"]), 10, true);
78 | if (!$result["success"]) OutputAPIError("Unable to create a DRC channel token.", "drc_create_token_failed", $result);
79 |
80 | $result["user_channel"] = $words;
81 | $result["user_url"] = BB_GetFullRequestURLBase() . "?channel=" . urlencode($words);
82 | $result["drc_url"] = $drc_public_url;
83 | $result["drc_origin"] = $drc_public_origin;
84 |
85 | OutputAPIResult($result);
86 | }
87 | else
88 | {
89 | OutputAPIError("Unknown API '" . $_REQUEST["api"] . "'.", "invalid_api");
90 | }
91 | }
92 |
93 | // Establish a regular session.
94 | session_start();
95 |
96 | if (!isset($_SESSION["ga_usertoken"]))
97 | {
98 | $rng = new CSPRNG();
99 |
100 | $_SESSION["ga_usertoken"] = $rng->GenerateToken(64);
101 | }
102 |
103 | $bb_usertoken = $_SESSION["ga_usertoken"];
104 |
105 |
106 | BB_ProcessPageToken("action");
107 |
108 | // Menu/Navigation options.
109 | $menuopts = array(
110 | );
111 |
112 | // Optional function to customize styles.
113 | function BB_InjectLayoutHead()
114 | {
115 | // Menu title underline: Colors with 60% saturation and 75% brightness generally look good.
116 | ?>
117 |
120 |
126 |
134 | Connect($drc_private_url, $drc_private_origin);
160 | if (!$result["success"]) OutputAPIError("Unable to connect to DRC.", "drc_connect_failed", $result);
161 |
162 | // Create a grant token.
163 | $result = $drc->CreateToken(false, $channelname, "obs-websocket-remote-api", DRCClient::CM_SEND_TO_AUTHS, array(), 10);
164 | if (!$result["success"]) OutputAPIError("Unable to create a DRC channel token.", "drc_create_token_failed", $result);
165 |
166 | $token = $result["data"]["token"];
167 |
168 | // Join the channel.
169 | $result = $drc->JoinChannel($channelname, "obs-websocket-remote-api", $token, 10, false);
170 | if (!$result["success"]) OutputAPIError("Unable to join DRC channel.", "drc_join_channel_failed", $result);
171 |
172 | $channelinfo = $result["data"];
173 | $channelid = $channelinfo["channel"];
174 |
175 | // Verify that an authority client is available.
176 | $authclientid = $drc->GetRandomAuthClientID($channelid);
177 | if ($authclientid === false) OutputAPIError("No authorities are available. Is OBS running?", "no_authorities");
178 |
179 | // Activate the scene.
180 | $result = $drc->SendCommand($channelid, "OBS-SetCurrentProgramScene", $authclientid, array("sceneName" => $_REQUEST["scene"]), 10, "OBS-SetCurrentProgramScene");
181 | if (!$result["success"]) OutputAPIError("Failed to activate the scene.", "obs_setcurrentscene_failed", $result);
182 |
183 | if (!$result["data"]["result"]["requestStatus"]["result"]) OutputAPIError("Failed to activate the scene.", "obs_setcurrentprogramscene_failed", $result);
184 |
185 | $result = array(
186 | "success" => true
187 | );
188 |
189 | OutputAPIResult($result);
190 | }
191 |
192 | if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "managechannel" && isset($_REQUEST["channel"]) && is_string($_REQUEST["channel"]) && $_REQUEST["channel"] !== "")
193 | {
194 | $location = "";
195 | $scenes = array();
196 | $channelname = "OBSManager-" . $_REQUEST["channel"];
197 |
198 | $drc = new DRCClient();
199 |
200 | // Connect to the server.
201 | $result = $drc->Connect($drc_private_url, $drc_private_origin);
202 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to connect to DRC. " . $result["error"] . " (" . $result["errorcode"] . ")");
203 | else
204 | {
205 | // Create a grant token.
206 | $result = $drc->CreateToken(false, $channelname, "obs-websocket-remote-api", DRCClient::CM_SEND_TO_AUTHS, array(), 10);
207 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to create a DRC channel token. " . $result["error"] . " (" . $result["errorcode"] . ")");
208 | else
209 | {
210 | $token = $result["data"]["token"];
211 |
212 | // Join the channel.
213 | $result = $drc->JoinChannel($channelname, "obs-websocket-remote-api", $token, 10, false);
214 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to join DRC channel. " . $result["error"] . " (" . $result["errorcode"] . ")");
215 | else
216 | {
217 | $channelinfo = $result["data"];
218 | $channelid = $channelinfo["channel"];
219 |
220 | // Verify that an authority client is available.
221 | $authclientid = $drc->GetRandomAuthClientID($channelid);
222 | if ($authclientid === false) BB_SetPageMessage("error", "No authorities are available. Is OBS running?");
223 | else
224 | {
225 | // Get the scene list.
226 | if (isset($channelinfo["clients"][$authclientid]["extra"]["location"])) $location = $channelinfo["clients"][$authclientid]["extra"]["location"];
227 |
228 | $result = $drc->SendCommand($channelid, "OBS-GetSceneList", $authclientid, array(), 10, "OBS-GetSceneList");
229 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to request scene list. " . $result["error"] . " (" . $result["errorcode"] . ")");
230 | else if (!$result["data"]["result"]["requestStatus"]["result"]) BB_SetPageMessage("error", "Request for the scene list resulted in an error. " . $result["data"]["result"]["requestStatus"]["comment"]);
231 | else
232 | {
233 | $scenes2 = array_reverse($result["data"]["result"]["responseData"]["scenes"]);
234 |
235 | foreach ($scenes2 as $scene)
236 | {
237 | $scenes[$scene["sceneName"]] = "" . htmlspecialchars($scene["sceneName"]) . "";
238 | }
239 | }
240 | }
241 | }
242 | }
243 | }
244 |
245 | $desc = "
";
246 | ob_start();
247 | ?>
248 |
253 |
254 |
271 | \n" . implode("\n", $scenes) . "\n";
275 |
276 | $contentopts = array(
277 | "desc" => "Select the current OBS scene" . ($location !== "" ? " for " . $location : "") . ".",
278 | "htmldesc" => $desc
279 | );
280 |
281 | BB_GeneratePage("Manage OBS Channel", array(), $contentopts);
282 | }
283 |
284 | if (isset($_REQUEST["channel"]))
285 | {
286 | if ($_REQUEST["channel"] == "") BB_SetPageMessage("error", "Please fill in 'Channel Name'.", "channel");
287 |
288 | if (BB_GetPageMessageType() != "error")
289 | {
290 | // Redirect to the channel.
291 | BB_RedirectPage("", "", array("action=managechannel&channel=" . urlencode($_REQUEST["channel"]) . "&sec_t=" . BB_CreateSecurityToken("managechannel")));
292 | }
293 | }
294 |
295 | $contentopts = array(
296 | "desc" => "Fill in the field below to remotely manage OBS.",
297 | "fields" => array(
298 | array(
299 | "title" => "Channel Name",
300 | "type" => "text",
301 | "name" => "channel",
302 | "default" => ""
303 | ),
304 | ),
305 | "submit" => "Go"
306 | );
307 |
308 | BB_GeneratePage("Select Channel", array(), $contentopts);
309 | ?>
--------------------------------------------------------------------------------
/server/support/admin.css:
--------------------------------------------------------------------------------
1 | html, body { height: 100%; margin: 0px; padding: 0px; overflow: hidden; background-color: #FFFFFF; }
2 | body { font-family: Verdana, Arial, Helvetica, sans-serif; position: relative; color: #222222; }
3 |
4 | #contentwrap { height: 100%; overflow: auto; outline: none; }
5 | #contentwrap .proptitlewrap { background-color: #222222; color: #FAFAFA; padding: 0.85em; box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); }
6 | #contentwrap .proptitle { max-width: 800px; margin-left: auto; margin-right: auto; font-weight: bold; font-size: 1.3em; }
7 | #contentwrap #navbutton { vertical-align: middle; display: none; padding: 0.23em 0; border: 1px solid #CCCCCC; border-radius: 5px; cursor: pointer; }
8 | #contentwrap #navbutton .navbuttonline { display: block; margin: 0.23em 0.4em; width: 1.0em; height: 2px; background-color: #FAFAFA; border-radius: 2px; }
9 | #contentwrap #navbutton:hover { color: #FFFFFF; border: 1px solid #E0E0E0; background-color: #2A2A2A; }
10 | #contentwrap #navbutton:hover .navbuttonline { background-color: #FFFFFF; }
11 |
12 | #contentwrap .propmessagewrap { border-top: 1px solid transparent; border-bottom: 1px solid transparent; padding: 0.5em 1.0em; box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); }
13 | #contentwrap .propmessage { max-width: 800px; margin-left: auto; margin-right: auto; }
14 | #contentwrap .propmessagesuccess { border-color: #B2DBA1; background-color: #DFF0D8; background-repeat: repeat-x; color: #3C763D; }
15 | #contentwrap .propmessageerror { border-color: #DCA7A7; background-color: #F2DEDE; background-repeat: repeat-x; color: #A94442; }
16 | #contentwrap .propmessageinfo { border-color: #9ACFEA; background-color: #D9EDF7; background-repeat: repeat-x; color: #31708F; }
17 |
18 | #contentwrap .propdescwrap { padding: 1.0em 1.0em 0; }
19 | #contentwrap .propdesc { max-width: 800px; margin-left: auto; margin-right: auto; }
20 | #contentwrap .propmainwrap { padding: 1.0em; }
21 | #contentwrap .propmain { max-width: 800px; margin-left: auto; margin-right: auto; }
22 |
23 | #menuwrap { float: left; width: 250px; min-width: 200px; max-width: 15%; height: 100%; overflow: auto; border-right: 1px solid #CCCCCC; font-size: 0.9em; background-color: #222222; color: #FAFAFA; display: flex; flex-direction: column; }
24 | #menuwrap .logo { text-align: center; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; cursor: default; }
25 | #menuwrap .logo:last-child { flex-grow: 1; display: flex; }
26 | #menuwrap .logo > * { display: inline-block; margin: auto auto 0 auto; padding-top: 1.0em; border-right: 1px solid transparent; }
27 | #menuwrap .logo img { max-width: 100%; display: inline-block; }
28 | #menuwrap .logo + .menu { margin-top: 0; }
29 | #menuwrap .menu { margin-top: 1.0em; border-top: 1px solid #444444; padding-top: 1.2em; }
30 | #menuwrap .menu:first-child { margin-top: 0; border-top: 0 none; padding-top: 1.0em; }
31 | #menuwrap .menu .titlewrap { margin: 0 1.0em 0.85em; }
32 | #menuwrap .menu .title { font-size: 1.2em; font-weight: bold; padding-bottom: 0.2em; border-bottom: 2px solid #888888; }
33 | #menuwrap .menu a { display: block; padding: 0.5em 1.0em; color: #FAFAFA; text-decoration: none; }
34 | #menuwrap .menu a:hover { background-color: #444444; color :#FFFFFF; }
35 |
36 | #contentwrap.showmenu { white-space: nowrap; }
37 | #contentwrap.showmenu #navbutton { display: table-cell; }
38 | #contentwrap.showmenu .proptitletext { padding-left: 1.0em; }
39 | #contentwrap #navoverflowwrap { display: none; }
40 | #contentwrap.showmenu { overflow: hidden; }
41 | #contentwrap.showmenu #navoverflowwrap { display: block; position: absolute; width: 100%; height: 100%; z-index: 10000; cursor: pointer; background-color: #000000; opacity: 0.25; }
42 |
43 | #contentwrap:not(.nomenu) .proptitle { max-width: none; margin-left: 0; margin-right: 0; display: table-row; }
44 | #contentwrap:not(.nomenu) .proptitletext { display: table-cell; vertical-align: middle; }
45 | #contentwrap:not(.nomenu) .propmessage, #contentwrap:not(.nomenu) .propdesc, #contentwrap:not(.nomenu) .propmain { max-width: none; margin-left: 0; margin-right: 0; }
46 |
47 | @media (max-width: 1000px) {
48 | #menuwrap { display: none; }
49 | #menuwrap.showmenu { display: flex; }
50 | #contentwrap:not(.nomenu) #navbutton { display: table-cell; }
51 | #contentwrap:not(.nomenu) .proptitletext { padding-left: 1.0em; }
52 | }
53 |
54 | a { color: #4E88C2; text-decoration: none; }
55 | a:hover { color: #297ACC; text-decoration: underline; }
56 |
57 | .formitem { margin-top: 1.0em; }
58 | .formitem:first-child { margin-top: 0; }
59 |
60 | .formitemtitle { margin-bottom: 0.2em; font-weight: bold; }
61 |
62 | .formitemdata { position: relative; }
63 |
64 | .formitemdata table.formitemtable { border-collapse: collapse; }
65 | .formitemdata table.formitemtable > thead > tr > th, .formitemdata table.formitemtable > tbody > tr > td { padding: 0.3em 0.5em; border-left: 1px solid #CCCCCC; border-right: 1px solid #CCCCCC; vertical-align: top; }
66 | .formitemdata table.formitemtable > thead > tr > th { background-color: #222222; color: #FAFAFA; font-weight: normal; }
67 | .formitemdata table.formitemtable > thead > tr > th:first-child { border-left: 1px solid #222222; }
68 | .formitemdata table.formitemtable > thead > tr > th:last-child { border-right: 1px solid #222222; }
69 | .formitemdata table.formitemtable > tbody > tr:first-child > td { border-top: none; }
70 | .formitemdata table.formitemtable > tbody > tr:nth-child(even) { background-color: #F6F6F6; }
71 | .formitemdata table.formitemtable > tbody > tr:last-child { border-bottom: 1px solid #CCCCCC; }
72 | .formitemdata table.formitemtable > tbody > tr > td.nowrap { white-space: nowrap; }
73 |
74 | .formitemdata .textitemwrap > input { outline: none; }
75 | .formitemdata .textitemwrap > input.text { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; }
76 | .formitemdata .textitemwrap > input.text:focus, .formitemdata .textitemwrap > input.text:hover { border: 1px solid #888888; }
77 |
78 | .formitemdata .textareawrap > textarea { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; outline: none; }
79 | .formitemdata .textareawrap > textarea:focus, .formitemdata .textareawrap > textarea:hover { border: 1px solid #888888; }
80 |
81 | .formitemdata .selectitemwrap > select { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; outline: none; }
82 | .formitemdata .selectitemwrap > select:focus, .formitemdata .selectitemwrap > select:hover { border: 1px solid #888888; }
83 |
84 | .formitemdata .radioitemwrap, .formitemdata .checkboxitemwrap { margin-left: 1.7em; text-indent: -1.7em; }
85 |
86 | .formitemdata .staticwrap { font-size: 0.9em; }
87 |
88 | .fieldtablewrap { margin-top: 1.0em; }
89 | .fieldtablewrap > table.rowwrap { border-collapse: collapse; width: 100%; }
90 | .fieldtablewrap > table.rowwrap > tbody > tr > td { padding: 1.0em 1.0em 0 0; vertical-align: top; }
91 | .fieldtablewrap > table.rowwrap > tbody > tr:first-child > td { padding-top: 0; }
92 | .formfieldsresponsive .fieldtablewrap > table.rowwrap > tbody > tr > td:nth-last-child(2) { padding-right: 0; }
93 | .fieldtablewrap > table.rowwrap > tbody > tr > td:last-child { padding-right: 0; }
94 | .fieldtablewrap .formitemtitle { white-space: nowrap; }
95 |
96 | hr { margin: 1.0em 0 0 0; border: none; border-top: 1px solid #CCCCCC; }
97 |
98 | .formitemdesc { color: #333333; margin-top: 0.1em; margin-left: 0.5em; font-size: 0.9em; }
99 |
100 | .formitemresult { margin-left: 0.5em; font-size: 0.9em; }
101 | .formitemresult .formitemerror { background: url('flex_forms_error.png') 0 0.1em no-repeat; padding-left: 25px; color: #A94442; font-weight: bold; }
102 |
103 | form.ff_form .formsubmit { margin-top: 1.2em; margin-right: 1.0em; }
104 | form.ff_form .formsubmit input { margin-right: 1.0em; padding: 0.3em 1.0em; font-weight: bold; font-size: 1.0em; background-color: #222222; color: #FAFAFA; border: 1px solid #222222; }
105 | form.ff_form .formsubmit input:hover { background-color: #444444; color :#FFFFFF; }
106 |
107 | @media (max-width: 420px) {
108 | .fieldtablewrap { margin-top: 0; }
109 | .fieldtablewrap > table.rowwrap { display: block; }
110 | .fieldtablewrap > table.rowwrap > tbody { display: block; }
111 | .fieldtablewrap > table.rowwrap > tbody > tr { display: block; }
112 | .fieldtablewrap > table.rowwrap > tbody > tr > td { display: block; padding: 0; margin-top: 1.0em; max-width: 100%; }
113 | .formfieldsresponsive .fieldtablewrap > table.rowwrap > tbody > tr > td:last-child { display: none; }
114 | }
115 |
116 |
117 | /* FlexForms Extras styles for Admin Pack */
118 |
119 | .formaccordionwrap.ui-accordion h3.ui-accordion-header { margin-top: 1.0em; border: 1px solid #CCCCCC; background: #F6F6F6; font-weight: bold; color: #222222; }
120 | .formaccordionwrap.ui-accordion:first-child h3.ui-accordion-header:first-child { margin-top: 0; }
121 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-hover { border: 1px solid #C5C5C5; background: #F0F0F0; font-weight: bold; color: #222222; }
122 | .formaccordionwrap.ui-accordion h3.ui-accordion-header .ui-icon { background-image: url("jquery_ui_themes/adminpack/images/ui-icons_333333_256x240.png"); }
123 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-active { border: 1px solid #444444; background: #222222; font-weight: bold; color: #ffffff; }
124 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-active .ui-icon { background-image: url("jquery_ui_themes/adminpack/images/ui-icons_fafafa_256x240.png"); }
125 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-active.ui-state-hover { border: 1px solid #444444; background: #333333; font-weight: bold; color: #ffffff; }
126 |
127 | .formaccordionwrap.ui-accordion .ui-helper-reset { line-height: normal; }
128 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-widget-content a { color: #4E88C2; text-decoration: none; }
129 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-widget-content a:hover { color: #297ACC; text-decoration: underline; }
130 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content { padding: 0 1.5em 1.0em; }
131 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content > .formitem:first-child { margin-top: 1.0em; }
132 |
133 | .formitemdata input.date { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; }
134 | .formitemdata input.date:focus, .formitemdata input.date:hover { border: 1px solid #888888; }
135 |
136 | .ui-state-default.ui-state-highlight, .ui-widget-content .ui-state-default.ui-state-highlight { border: 1px solid #c5c5c5; background: #f6f6f6; color: #454545; }
137 | .ui-state-active.ui-state-highlight, .ui-widget-content .ui-state-active.ui-state-highlight { border: 1px solid #444444; background: #222222; color: #ffffff; }
138 | .ui-state-hover.ui-state-highlight, .ui-widget-content .ui-state-hover.ui-state-highlight { border: 1px solid #444444; background: #333333; color: #ffffff; }
139 |
140 | .formitemdata .uix-multiselect .ui-widget-header div.header-text { white-space: normal; }
141 | .formitemdata .uix-multiselect .ui-widget-header { padding-top: 0.3em; padding-bottom: 0.3em; }
142 | .formitemdata .uix-multiselect .ui-widget-header .uix-control-right { padding-top: 0.8em; }
143 |
144 | .formitemdata .ui-multiselect { padding: 0.3em; }
145 | .formitemdata .ui-multiselect.ui-widget.ui-state-default { border: 1px solid #444444; background: #333333; color: #ffffff; }
146 | .formitemdata .ui-multiselect.ui-widget.ui-state-default span.ui-icon { background-image: url("jquery_ui_themes/adminpack/images/ui-icons_ffffff_256x240.png"); }
147 | .formitemdata .ui-multiselect.ui-widget.ui-state-active { border: 1px solid #444444; background: #222222; color: #ffffff; }
148 | .formitemdata .ui-multiselect.ui-widget.ui-state-hover { border: 1px solid #444444; background: #222222; color: #ffffff; }
149 | .formitemdata .ui-multiselect span.ui-icon { margin-top: 0.2em; }
150 |
151 | .formitemdata .select2-container { display: block; }
152 | .formitemdata .select2-container-multi .select2-choices .select2-search-choice { line-height: 17px; }
153 | .formitemdata .select2-search-choice-close { top: 5px; }
154 | .select2-results .select2-highlighted { background-color: #444444 !important; }
155 |
156 | .formitemdata .draghandle { cursor: move; }
157 | .formitemdata .dragactive { background-color: #E3E3E3; }
158 |
159 | .formitemdata table.tablecards > thead > th:nth-last-child(2) { border-right: 1px solid #222222; }
160 | .formitemdata table.tablecards.tablecard-show > thead > th:nth-last-child(2) { border-right: 1px solid #CCCCCC; }
161 | .formitemdata table.tablecard-show { width: 100%; }
162 | .formitemdata table.tablecard-show-nohead > tbody > tr:first-child > td { border-top: 1px solid #CCCCCC; }
163 |
164 | .formitemdata .tablebodyscroll-scroller3 > table.tablecard-show-nohead > thead > tr { display: none; }
165 |
166 | .formitemdata .tablebodyscroll-shadow-top .tablebodyscroll-scroller-shadow-top, .formitemdata .tablebodyscroll-shadow-both .tablebodyscroll-scroller-shadow-top { border-top: 1px solid #CCCCCC; }
167 |
168 |
169 | /* FlexForms Modules styles for Admin Pack */
170 |
171 | .formitemdata table.tablecard-show .calendar_day_of_week { margin-top: 0.5em; border-top: 1px solid #E0E0E0; padding-top: 0.5em; font-weight: bold; }
172 | .formitemdata table.tablecard-show .calendar_day_of_week:first-child { margin-top: 0; border-top: none; padding-top: 0; }
173 |
174 | .formitemdata .tablefiltersearchwrap { margin-bottom: 0.5em; }
175 | .formitemdata table.ff_tablefilter > tbody > tr.visible { background-color: #FFFFFF; }
176 | .formitemdata table.ff_tablefilter > tbody > tr.visible.altrow { background-color: #F6F6F6; }
177 | .formitemdata table.ff_tablefilter > tbody > tr.visible.lastrow { border-bottom: 1px solid #CCCCCC; }
178 |
--------------------------------------------------------------------------------
/server/support/admin.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | // For handling menu scroll.
3 | function GetScrollLineHeight() {
4 | var iframe = document.createElement('iframe');
5 | iframe.src = '#';
6 | document.body.appendChild(iframe);
7 | var iwin = iframe.contentWindow;
8 | var idoc = iwin.document;
9 | idoc.open();
10 | idoc.write('