More fixes + authentication.
This commit is contained in:
parent
2b7b8e7597
commit
4e80c5f0a0
12 changed files with 111 additions and 83 deletions
|
@ -20,6 +20,7 @@ require_once AJAX_CHAT_PATH . '/src/AJAXChatFileSystem.php';
|
||||||
require_once AJAX_CHAT_PATH . '/src/AJAXChatHTTPHeader.php';
|
require_once AJAX_CHAT_PATH . '/src/AJAXChatHTTPHeader.php';
|
||||||
require_once AJAX_CHAT_PATH . '/src/AJAXChatLanguage.php';
|
require_once AJAX_CHAT_PATH . '/src/AJAXChatLanguage.php';
|
||||||
require_once AJAX_CHAT_PATH . '/src/AJAXChatTemplate.php';
|
require_once AJAX_CHAT_PATH . '/src/AJAXChatTemplate.php';
|
||||||
|
require_once AJAX_CHAT_PATH . '/src/SockChatAuth.php';
|
||||||
require_once AJAX_CHAT_PATH . '/src/CustomAJAXChat.php';
|
require_once AJAX_CHAT_PATH . '/src/CustomAJAXChat.php';
|
||||||
require_once AJAX_CHAT_PATH . '/src/CustomAJAXChatShoutBox.php';
|
require_once AJAX_CHAT_PATH . '/src/CustomAJAXChatShoutBox.php';
|
||||||
require_once AJAX_CHAT_PATH . '/src/CustomAJAXChatInterface.php';
|
require_once AJAX_CHAT_PATH . '/src/CustomAJAXChatInterface.php';
|
||||||
|
|
|
@ -37,6 +37,9 @@ $config['dbConnection']['type'] = null;
|
||||||
// Database link:
|
// Database link:
|
||||||
$config['dbConnection']['link'] = null;
|
$config['dbConnection']['link'] = null;
|
||||||
|
|
||||||
|
if(is_file(AJAX_CHAT_PATH . '/config/config-db.php'))
|
||||||
|
include_once AJAX_CHAT_PATH . '/config/config-db.php';
|
||||||
|
|
||||||
// Database table names:
|
// Database table names:
|
||||||
$config['dbTableNames'] = array();
|
$config['dbTableNames'] = array();
|
||||||
$config['dbTableNames']['online'] = 'ajax_chat_online';
|
$config['dbTableNames']['online'] = 'ajax_chat_online';
|
||||||
|
@ -105,7 +108,7 @@ $config['forceAutoLogin'] = false;
|
||||||
$config['showChannelMessages'] = true;
|
$config['showChannelMessages'] = true;
|
||||||
|
|
||||||
// If enabled, the chat will only be accessible for the admin:
|
// If enabled, the chat will only be accessible for the admin:
|
||||||
$config['chatClosed'] = false;
|
$config['chatClosed'] = true;
|
||||||
// Defines the timezone offset in seconds (-12*60*60 to 12*60*60) - if null, the server timezone is used:
|
// Defines the timezone offset in seconds (-12*60*60 to 12*60*60) - if null, the server timezone is used:
|
||||||
$config['timeZoneOffset'] = null;
|
$config['timeZoneOffset'] = null;
|
||||||
// Defines the hour of the day the chat is opened (0 - closingHour):
|
// Defines the hour of the day the chat is opened (0 - closingHour):
|
||||||
|
|
|
@ -333,9 +333,9 @@ class AJAXChat {
|
||||||
case 'legacy2':
|
case 'legacy2':
|
||||||
return AJAX_CHAT_PATH . '/template/loggedOut~.html';
|
return AJAX_CHAT_PATH . '/template/loggedOut~.html';
|
||||||
case 'legacy3':
|
case 'legacy3':
|
||||||
return AJAX_CHAT_PATH . '/template/loggedOutFA.html';
|
|
||||||
default:
|
|
||||||
return AJAX_CHAT_PATH . '/template/loggedOut.html';
|
return AJAX_CHAT_PATH . '/template/loggedOut.html';
|
||||||
|
default:
|
||||||
|
return AJAX_CHAT_PATH . '/template/loggedOutFA.html';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2651,8 +2651,8 @@ class AJAXChat {
|
||||||
'',
|
'',
|
||||||
time()-42000,
|
time()-42000,
|
||||||
$this->getConfig('sessionCookiePath'),
|
$this->getConfig('sessionCookiePath'),
|
||||||
$this->getConfig('sessionCookieDomain'),
|
$this->getConfig('sessionCookieDomain') ?? '',
|
||||||
$this->getConfig('sessionCookieSecure')
|
$this->getConfig('sessionCookieSecure') ?? false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2938,8 +2938,8 @@ class AJAXChat {
|
||||||
$this->getLangCode(),
|
$this->getLangCode(),
|
||||||
time()+60*60*24*$this->getConfig('sessionCookieLifeTime'),
|
time()+60*60*24*$this->getConfig('sessionCookieLifeTime'),
|
||||||
$this->getConfig('sessionCookiePath'),
|
$this->getConfig('sessionCookiePath'),
|
||||||
$this->getConfig('sessionCookieDomain'),
|
$this->getConfig('sessionCookieDomain') ?? '',
|
||||||
$this->getConfig('sessionCookieSecure')
|
$this->getConfig('sessionCookieSecure') ?? false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,9 +59,9 @@ class AJAXChatDataBase {
|
||||||
|
|
||||||
// Method to retrieve the current DataBase name:
|
// Method to retrieve the current DataBase name:
|
||||||
function getName() {
|
function getName() {
|
||||||
return $this->_db->getName();
|
//return $this->_db->getName();
|
||||||
//If your database has hyphens ( - ) in it, try using this instead:
|
//If your database has hyphens ( - ) in it, try using this instead:
|
||||||
//return '`'.$this->_db->getName().'`';
|
return '`'.$this->_db->getName().'`';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to retrieve the last inserted ID:
|
// Method to retrieve the last inserted ID:
|
||||||
|
|
|
@ -12,73 +12,39 @@ class CustomAJAXChat extends AJAXChat {
|
||||||
// Returns an associative array containing userName, userID and userRole
|
// Returns an associative array containing userName, userID and userRole
|
||||||
// Returns null if login is invalid
|
// Returns null if login is invalid
|
||||||
function getValidLoginUserData() {
|
function getValidLoginUserData() {
|
||||||
if($this->getRequestVar('password')) {
|
if(empty($_COOKIE['msz_auth'])) {
|
||||||
|
header('Location: https://flashii.net/auth/login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userInfo = SockChatAuth::attempt($this->getConfig('flashiiSecret'), (string)filter_input(INPUT_COOKIE, 'msz_auth'));
|
||||||
|
|
||||||
|
if($userInfo->success) {
|
||||||
// Check if we have a valid registered user:
|
// Check if we have a valid registered user:
|
||||||
|
|
||||||
$userName = $this->getRequestVar('userName');
|
$chatUser = [
|
||||||
$userName = $this->convertEncoding($userName, $this->getConfig('contentEncoding'), $this->getConfig('sourceEncoding'));
|
'userID' => $userInfo->user_id,
|
||||||
|
'userName' => $userInfo->username,
|
||||||
|
'userRole' => AJAX_CHAT_GUEST,
|
||||||
|
];
|
||||||
|
|
||||||
$password = $this->getRequestVar('password');
|
// sock chat auth doesn't actually return role ids lol
|
||||||
$password = $this->convertEncoding($password, $this->getConfig('contentEncoding'), $this->getConfig('sourceEncoding'));
|
if($userInfo->colour_raw === 0xEE9400)
|
||||||
|
$chatUser['userRole'] = DONATOR;
|
||||||
|
elseif($userInfo->colour_raw === 0x0099FF)
|
||||||
|
$chatUser['userRole'] = CMOD;
|
||||||
|
elseif($userInfo->colour_raw === 0x7353C4)
|
||||||
|
$chatUser['userRole'] = PURPLE;
|
||||||
|
elseif($userInfo->colour_raw === 0x9E8DA7)
|
||||||
|
$chatUser['userRole'] = BOTS;
|
||||||
|
elseif($userInfo->rank >= 10)
|
||||||
|
$chatUser['userRole'] = AJAX_CHAT_ADMIN;
|
||||||
|
elseif($userInfo->rank >= 5)
|
||||||
|
$chatUser['userRole'] = AJAX_CHAT_MODERATOR;
|
||||||
|
elseif($userInfo->rank >= 1)
|
||||||
|
$chatUser['userRole'] = AJAX_CHAT_USER;
|
||||||
|
|
||||||
$flashiiConfig = parse_ini_file('/www/flashii.net/config/config.ini', true, INI_SCANNER_TYPED);
|
return $chatUser;
|
||||||
|
|
||||||
if (!empty($flashiiConfig['Database'])) {
|
|
||||||
$dbConfig = $flashiiConfig['Database'];
|
|
||||||
$flashiiDb = new PDO(
|
|
||||||
"mysql:unix_socket={$dbConfig['unix_socket']};dbname={$dbConfig['database']}",
|
|
||||||
$dbConfig['username'],
|
|
||||||
$dbConfig['password'],
|
|
||||||
[
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$getFlashiiUser = $flashiiDb->prepare('
|
|
||||||
SELECT `user_id` as `userID`, `username` as `userName`, `display_role` as `userRole`, `password`
|
|
||||||
FROM `msz_users`
|
|
||||||
WHERE LOWER(`username`) = LOWER(:username)
|
|
||||||
');
|
|
||||||
$getFlashiiUser->bindValue('username', $userName);
|
|
||||||
$flashiiUser = $getFlashiiUser->execute() ? $getFlashiiUser->fetch(PDO::FETCH_ASSOC) : [];
|
|
||||||
|
|
||||||
if (!empty($flashiiUser) && password_verify($password, $flashiiUser['password'])) {
|
|
||||||
unset($flashiiUser['password']);
|
|
||||||
|
|
||||||
// corrections, i'm not going to update the random IDs scattered about
|
|
||||||
switch ($flashiiUser['userRole']) {
|
|
||||||
case 2:
|
|
||||||
$flashiiUser['userRole'] = AJAX_CHAT_MODERATOR;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
$flashiiUser['userRole'] = AJAX_CHAT_ADMIN;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
$flashiiUser['userRole'] = BOTS;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
$flashiiUser['userRole'] = AJAX_CHAT_GUEST;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
case 7:
|
|
||||||
$flashiiUser['userRole'] = DONATOR;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$flashiiUser['userRole'] = AJAX_CHAT_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if ($flashiiUser['userID'] === 2) {
|
|
||||||
$flashiiUser['userRole'] = CMOD;
|
|
||||||
} else*/if ($flashiiUser['userID'] === 3) {
|
|
||||||
$flashiiUser['userRole'] = AJAX_CHAT_MODERATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $flashiiUser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} else {
|
} else {
|
||||||
// Guest users:
|
// Guest users:
|
||||||
return $this->getGuestUser();
|
return $this->getGuestUser();
|
||||||
|
|
58
src/SockChatAuth.php
Normal file
58
src/SockChatAuth.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
final class SockChatAuth {
|
||||||
|
private const ENDPOINT = 'https://flashii.net/_sockchat/verify';
|
||||||
|
|
||||||
|
public static function attempt(string $secret, string $cookie): object {
|
||||||
|
if(!empty($cookie)) {
|
||||||
|
$decoded = str_pad(base64_decode(str_pad(strtr($cookie, '-_', '+/'), strlen($cookie) % 4, '=', STR_PAD_RIGHT)), 37, "\0");
|
||||||
|
$unpacked = unpack('Cversion/Nuser/H*token', $decoded);
|
||||||
|
|
||||||
|
if(isset($unpacked['version']) && $unpacked['version'] === 1
|
||||||
|
&& isset($unpacked['user']) && $unpacked['user'] > 0) {
|
||||||
|
$loginRequest = [
|
||||||
|
'user_id' => $unpacked['user'],
|
||||||
|
'token' => 'SESS:' . $cookie,
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||||
|
];
|
||||||
|
$loginSignature = hash_hmac('sha256', implode('#', $loginRequest), $secret);
|
||||||
|
|
||||||
|
$login = curl_init(self::ENDPOINT);
|
||||||
|
curl_setopt_array($login, [
|
||||||
|
CURLOPT_AUTOREFERER => false,
|
||||||
|
CURLOPT_FAILONERROR => false,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_HEADER => false,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($loginRequest),
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TCP_FASTOPEN => true,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 2,
|
||||||
|
CURLOPT_MAXREDIRS => 2,
|
||||||
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
||||||
|
CURLOPT_TIMEOUT => 5,
|
||||||
|
CURLOPT_USERAGENT => 'AJAX Chat',
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'X-SharpChat-Signature: ' . $loginSignature,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$userInfo = json_decode(curl_exec($login));
|
||||||
|
curl_close($login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(empty($userInfo->success)) {
|
||||||
|
$userInfo = new stdClass;
|
||||||
|
$userInfo->success = false;
|
||||||
|
$userInfo->user_id = 0;
|
||||||
|
$userInfo->username = 'Anonymous';
|
||||||
|
$userInfo->colour_raw = 0x40000000;
|
||||||
|
$userInfo->rank = 0;
|
||||||
|
$userInfo->hierarchy = 0;
|
||||||
|
$userInfo->is_silenced = '1970-01-01T00:00:00+00:00';
|
||||||
|
$userInfo->perms = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $userInfo;
|
||||||
|
}
|
||||||
|
}
|
|
@ -221,7 +221,7 @@ if(!$satoriSetting['enabled'])
|
||||||
|
|
||||||
// Bring the process of sending a message down to one if
|
// Bring the process of sending a message down to one if
|
||||||
if(isset($satoriResp)) {
|
if(isset($satoriResp)) {
|
||||||
foreach($satoriResp as $directives => $response) {
|
foreach($satoriResp as $response) {
|
||||||
if($response == '[s]Trigger Kick') {
|
if($response == '[s]Trigger Kick') {
|
||||||
$this->logout('Kicked');
|
$this->logout('Kicked');
|
||||||
} else {
|
} else {
|
||||||
|
@ -230,7 +230,7 @@ if(isset($satoriResp)) {
|
||||||
$satoriSetting['userName'],
|
$satoriSetting['userName'],
|
||||||
$satoriSetting['userRank'],
|
$satoriSetting['userRank'],
|
||||||
$this->getChannel(),
|
$this->getChannel(),
|
||||||
vsprintf($response, $directives),
|
$response,
|
||||||
$satoriSetting['userIP'],
|
$satoriSetting['userIP'],
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
|
@ -130,9 +130,9 @@
|
||||||
<input type="hidden" name="login" id="loginField" value="login"/>
|
<input type="hidden" name="login" id="loginField" value="login"/>
|
||||||
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
||||||
<div><label for="userNameField">[LANG]userName[/LANG]:</label><br />
|
<div><label for="userNameField">[LANG]userName[/LANG]:</label><br />
|
||||||
<input type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]"/></div>
|
<input disabled="disabled" type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]"/></div>
|
||||||
<div><label for="passwordField">[LANG]password[/LANG]*:</label><br />
|
<div><label for="passwordField">[LANG]password[/LANG]*:</label><br />
|
||||||
<input type="password" name="password" id="passwordField"/></div>
|
<input disabled="disabled" type="password" name="password" id="passwordField"/></div>
|
||||||
<div><label for="channelField">[LANG]channel[/LANG]:</label><br />
|
<div><label for="channelField">[LANG]channel[/LANG]:</label><br />
|
||||||
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select></div>
|
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select></div>
|
||||||
<div><label for="languageSelection">[LANG]language[/LANG]:</label><br />
|
<div><label for="languageSelection">[LANG]language[/LANG]:</label><br />
|
||||||
|
|
|
@ -55,8 +55,8 @@ div.mioboxcontent {
|
||||||
<div class="mioboxcontent">
|
<div class="mioboxcontent">
|
||||||
<input type="hidden" name="login" id="loginField" value="login"/>
|
<input type="hidden" name="login" id="loginField" value="login"/>
|
||||||
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
||||||
<input type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]" placeholder="[LANG]userName[/LANG]" />
|
<input disabled="disabled" type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]" placeholder="[LANG]userName[/LANG]" />
|
||||||
<input type="password" name="password" id="passwordField" placeholder="[LANG]password[/LANG]" />
|
<input disabled="disabled" type="password" name="password" id="passwordField" placeholder="[LANG]password[/LANG]" />
|
||||||
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select>
|
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select>
|
||||||
<select id="languageSelection" name="lang" onchange="ajaxChat.switchLanguage(this.value);">[LANGUAGE_OPTIONS/]</select>
|
<select id="languageSelection" name="lang" onchange="ajaxChat.switchLanguage(this.value);">[LANGUAGE_OPTIONS/]</select>
|
||||||
<input type="submit" name="submit" id="loginButton" value="[LANG]login[/LANG]"/>
|
<input type="submit" name="submit" id="loginButton" value="[LANG]login[/LANG]"/>
|
||||||
|
|
|
@ -55,7 +55,7 @@ div.mioboxcontent {
|
||||||
<div class="mioboxcontent">
|
<div class="mioboxcontent">
|
||||||
<input type="hidden" name="login" id="loginField" value="login"/>
|
<input type="hidden" name="login" id="loginField" value="login"/>
|
||||||
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
||||||
<input type="text" id="userNameField" disabled="" style="width: 388px;" placeholder="Click 'login' to login with your Flashii Account" />
|
<input type="text" id="userNameField" disabled="" style="width: 388px;" placeholder="Click "Login" to login with your Flashii Account" />
|
||||||
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select>
|
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select>
|
||||||
<select id="languageSelection" name="lang" onchange="ajaxChat.switchLanguage(this.value);">[LANGUAGE_OPTIONS/]</select>
|
<select id="languageSelection" name="lang" onchange="ajaxChat.switchLanguage(this.value);">[LANGUAGE_OPTIONS/]</select>
|
||||||
<input type="submit" name="submit" id="loginButton" value="[LANG]login[/LANG]"/>
|
<input type="submit" name="submit" id="loginButton" value="[LANG]login[/LANG]"/>
|
||||||
|
|
|
@ -46,8 +46,8 @@
|
||||||
<form id="loginForm" action="[LOGIN_URL/]" method="post" enctype="application/x-www-form-urlencoded">
|
<form id="loginForm" action="[LOGIN_URL/]" method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<input type="hidden" name="login" id="loginField" value="login"/>
|
<input type="hidden" name="login" id="loginField" value="login"/>
|
||||||
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
||||||
<input type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]" placeholder="[LANG]userName[/LANG] (e.g. [CHAT_BOT_NAME/])" />
|
<input disabled="disabled" type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]" placeholder="[LANG]userName[/LANG] (e.g. [CHAT_BOT_NAME/])" />
|
||||||
<input type="password" name="password" id="passwordField" placeholder="[LANG]password[/LANG]" />
|
<input disabled="disabled" type="password" name="password" id="passwordField" placeholder="[LANG]password[/LANG]" />
|
||||||
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select>
|
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select>
|
||||||
<input type="submit" name="submit" id="loginButton" value="[LANG]login[/LANG]"/>
|
<input type="submit" name="submit" id="loginButton" value="[LANG]login[/LANG]"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -55,9 +55,9 @@
|
||||||
<input type="hidden" name="login" id="loginField" value="login"/>
|
<input type="hidden" name="login" id="loginField" value="login"/>
|
||||||
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
<input type="hidden" name="redirect" id="redirectField" value="[REDIRECT_URL/]"/>
|
||||||
<div><label for="userNameField">[LANG]userName[/LANG]:</label><br />
|
<div><label for="userNameField">[LANG]userName[/LANG]:</label><br />
|
||||||
<input type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]"/></div>
|
<input disabled="disabled" type="text" name="userName" id="userNameField" maxlength="[USER_NAME_MAX_LENGTH/]"/></div>
|
||||||
<div><label for="passwordField">[LANG]password[/LANG]*:</label><br />
|
<div><label for="passwordField">[LANG]password[/LANG]*:</label><br />
|
||||||
<input type="password" name="password" id="passwordField"/></div>
|
<input disabled="disabled" type="password" name="password" id="passwordField"/></div>
|
||||||
<div><label for="channelField">[LANG]channel[/LANG]:</label><br />
|
<div><label for="channelField">[LANG]channel[/LANG]:</label><br />
|
||||||
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select></div>
|
<select name="channelName" id="channelField">[CHANNEL_OPTIONS/]</select></div>
|
||||||
<div><label for="languageSelection">[LANG]language[/LANG]:</label><br />
|
<div><label for="languageSelection">[LANG]language[/LANG]:</label><br />
|
||||||
|
|
Loading…
Reference in a new issue