====== Extra Fields To Click-To-Connect Pop-Up ======
===== Introduction =====
* The Dynamic Login Pages applet has a dedicated section for Click-To-Connect.
* By default there is an option for the user to first supply their email before they can connect to the Internet.
* This can be set with and additional expiry date.
* When these are selected the following happens:
* The **Click-ToConnect** is displayed as normal.
* When the user clicks the button, a check is done if there is a need for the user to supply anything (Based on his MAC Address and if there is an expiry date which now is active with this login page)
* If there is such a need there is a pop-up window asking the user to provide their email before the Internet connectivity will commence.
* There might however be the need to also provide additional info besides the email address.
* This document will show which files needs to be tweaked for that.
* We will add the following fields
* A Phone number that needs to be 8 digits long.
* A field called DN which is also a number that needs to be 7 digits long.
===== Files To Edit =====
==== ExtJs side (Dynamic Details Applet) ====
* Locate the file pnlDynamicDetailClickToConnect.js.
* It is typically under ///var/www/rdcore/rd/classic/src/view/dynamicDetails/pnlDynamicDetailClickToConnect.js//
* We will define two combo boxes below **cmbReSupply**
var cmbReSupplyP = Ext.create('Ext.form.ComboBox', {
fieldLabel : 'Re-Supply Phone Interval',
store : reSupply,
queryMode : 'local',
displayField : 'name',
valueField : 'id',
name : 'ctc_resupply_phone_interval',
itemId : 'cmbReSupplyPhone',
labelCls : 'lblRd',
allowBlank : false,
forceSelection : true,
value : 0
});
var cmbReSupplyDn = Ext.create('Ext.form.ComboBox', {
fieldLabel : 'Re-Supply DN Interval',
store : reSupply,
queryMode : 'local',
displayField : 'name',
valueField : 'id',
name : 'ctc_resupply_dn_interval',
itemId : 'cmbReSupplyDn',
labelCls : 'lblRd',
allowBlank : false,
forceSelection : true,
value : 0
});
* Then we will add check boxes for these below the email checkbox
{
xtype : 'checkbox',
fieldLabel : 'Require Phone To Connect',
itemId : 'chkCtcRequirePhone',
name : 'ctc_require_phone',
inputValue : 'ctc_require_phone',
checked : false,
disabled : true
},
cmbReSupplyP,
{
xtype : 'checkbox',
fieldLabel : 'Require DN To Connect',
itemId : 'chkCtcRequireDn',
name : 'ctc_require_dn',
inputValue : 'ctc_require_dn',
checked : false,
disabled : true
},
cmbReSupplyDn
* You will notice that the items we added are disabled by default.
* We need to also modify the View Controller to include these new components when certain action are done.
* Locate the file vcDynamicDetailClickToConnect.js.
* It is typically under ///var/www/rdcore/rd/classic/src/view/dynamicDetails/vcDynamicDetailClickToConnect.js//
* Compare the following with your original code.
Ext.define('Rd.view.dynamicDetails.vcDynamicDetailClickToConnect', {
extend : 'Ext.app.ViewController',
alias : 'controller.vcDynamicDetailClickToConnect',
control: {
'#chkClickToConnect' : {
change: 'chkClickToConnectChange'
},
'#chkCtcRequireEmail' : {
change: 'chkCtcRequireEmailChange'
},
'#chkCtcRequirePhone' : {
change: 'chkCtcRequirePhoneChange'
},
'#chkCtcRequireDn' : {
change: 'chkCtcRequireDnChange'
}
},
chkClickToConnectChange: function(chk){
var me = this;
var form = chk.up('form');
var un = form.down('#txtConnectUsername');
var sx = form.down('#txtConnectSuffix');
var cd = form.down('#nrConnectDelay');
var co = form.down('#chkConnectOnly');
var re = form.down('#chkCtcRequireEmail');
var rs = form.down('#cmbReSupply');
var rp = form.down('#chkCtcRequirePhone');
var rsp = form.down('#cmbReSupplyPhone');
var rdn = form.down('#chkCtcRequireDn');
var rsdn = form.down('#cmbReSupplyDn');
var value = chk.getValue();
if(value){
un.setDisabled(false);
sx.setDisabled(false);
cd.setDisabled(false);
co.setDisabled(false);
re.setDisabled(false);
rs.setDisabled(false);
rp.setDisabled(false);
rsp.setDisabled(false);
rdn.setDisabled(false);
rsdn.setDisabled(false);
}else{
un.setDisabled(true);
sx.setDisabled(true);
cd.setDisabled(true);
co.setDisabled(true);
re.setDisabled(true);
rs.setDisabled(true);
rp.setDisabled(true);
rsp.setDisabled(true);
rdn.setDisabled(true);
rsdn.setDisabled(true);
}
},
chkCtcRequireEmailChange: function(chk){
var me = this;
var form = chk.up('form');
var value = chk.getValue();
var rs = form.down('#cmbReSupply');
if(value){
rs.setDisabled(false);
}else{
rs.setDisabled(true);
}
},
chkCtcRequirePhoneChange: function(chk){
var me = this;
var form = chk.up('form');
var value = chk.getValue();
var rs = form.down('#cmbReSupplyPhone');
if(value){
rs.setDisabled(false);
}else{
rs.setDisabled(true);
}
},
chkCtcRequireDnChange: function(chk){
var me = this;
var form = chk.up('form');
var value = chk.getValue();
var rs = form.down('#cmbReSupplyDn');
if(value){
rs.setDisabled(false);
}else{
rs.setDisabled(true);
}
}
});
* Finally we need to add those extra fields to the grid displaying these items
* Locate the file gridDynamicDetailEmails.js.
* It is typically under ///var/www/rdcore/rd/classic/src/view/dynamicDetails/gridDynamicDetailEmails.js//
* See the following snippet
{ text: 'E-Mail', dataIndex: 'email', tdCls: 'gridTree', flex: 1,stateId: 'StateGridDynamicDetailEmails2'},
{ text: 'Phone', dataIndex: 'phone', tdCls: 'gridTree', flex: 1,stateId: 'StateGridDynamicDetailEmails2a'},
{ text: 'DN', dataIndex: 'dn', tdCls: 'gridTree', flex: 1,stateId: 'StateGridDynamicDetailEmails2b'},
{
text : 'Captive MAC',
dataIndex : 'cp_mac',
tdCls : 'gridTree',
hidden : true,
flex : 1,
stateId : 'DD_Email_B'
},
* Remember to run **Sencha Command** after these to generate the optimized JavaScript code.
* There is a dedicated Wiki Page in this site for it.
* Next we will see what we need to modify on the CakePHP3 App Side.
==== CakePHP3 App Side ====
* We need to add extra fields to the **dynamic_details** sql table inside the **rd** database.
* See the following SQL Patch snippet which add those fields if not present
if not exists (select * from information_schema.columns
where column_name = 'ctc_require_phone' and table_name = 'dynamic_details' and table_schema = 'rd') then
alter table dynamic_details add column `ctc_require_phone` tinyint(1) NOT NULL DEFAULT '0';
end if;
if not exists (select * from information_schema.columns
where column_name = 'ctc_resupply_phone_interval' and table_name = 'dynamic_details' and table_schema = 'rd') then
alter table dynamic_details add column `ctc_resupply_phone_interval` int(4) NOT NULL DEFAULT '0';
end if;
if not exists (select * from information_schema.columns
where column_name = 'ctc_require_dn' and table_name = 'dynamic_details' and table_schema = 'rd') then
alter table dynamic_details add column `ctc_require_dn` tinyint(1) NOT NULL DEFAULT '0';
end if;
if not exists (select * from information_schema.columns
where column_name = 'ctc_resupply_dn_interval' and table_name = 'dynamic_details' and table_schema = 'rd') then
alter table dynamic_details add column `ctc_resupply_dn_interval` int(4) NOT NULL DEFAULT '0';
end if;
* We also need to add extra field to the **data_collectors** sql table inside the **rd** database.
* See the following SQL Patch snippet which add those fields if not present
if not exists (select * from information_schema.columns
where column_name = 'phone' and table_name = 'data_collectors' and table_schema = 'rd') then
alter table data_collectors add column `phone` varchar(36) NOT NULL DEFAULT '';
end if;
if not exists (select * from information_schema.columns
where column_name = 'dn' and table_name = 'data_collectors' and table_schema = 'rd') then
alter table data_collectors add column `dn` varchar(36) NOT NULL DEFAULT '';
end if;
* After taking care of the database lets see what needs to be modified to the CakePHP 3 controllers
* Locate the file DynamicDetailsController.php.
* It is typically under ///var/www/rdcore/cake3/rd_cake/src/Controller/DynamicDetailsController.php//
* There is not much that needs modification since CakePHP takes care of most things behind the scenes.
* Just locate the **editClickToConnect** method and modify **$check_items**
$check_items = [
'connect_check',
'connect_only',
'ctc_require_email',
'ctc_require_phone',
'ctc_require_dn',
];
* Locate the file DataCollectorsController.php.
* It is typically under ///var/www/rdcore/cake3/rd_cake/src/Controller/DataCollectorsController.php//
* We will modify the login page eventually to have various options displayed in the Pop-Up for Click-To-Connect so we need to modify the DataCollectorsController to only check the email field **IF PRESENT**
* See the following snippets
public function addMac(){
if ($this->request->is('post')) {
if($this->request->getData('email')){
if(!$this->_test_email($this->request->data['email'])){
return;
}
}
$dd = $this->_find_dynamic_detail_id();
* And this one
private function _addOrEdit($user,$type= 'add') {
//__ Authentication + Authorization __
$user_id = $user['id'];
if($this->request->getData('email')){
if(!$this->_test_email($this->request->data['email'])){
return;
}
}
$this->request->data['public_ip'] = $this->request->clientIp();
* Also the **macCheck** method need to give feedback on those new methods in order so we can determine which fields must be shown in the Pop-Up
public function macCheck(){
$data = $this->request->data;
$q_r = $this->_find_dynamic_detail_id();
$data['ctc_require_email'] = false; // By defaul don't ask for email;
if($q_r){
//Once we found the Dynamic Login Page; We need to figure out if we need to ask for an email for this person
//For that we need to look for a combo **dynamic_detail_id** and **mac**
//IF found look at the modify timestamp and if it 'expired' ask for it again
//If not found ask for it (ctc_require_email == true)
//Else we set ctc_require_email == false since we found the combo and it has not expired yet
$dd_id = $q_r->dynamic_detail_id;
$dd_resuply_int = $q_r->dynamic_detail->ctc_resupply_email_interval;
$dd_resuply_intP = $q_r->dynamic_detail->ctc_resupply_phone_interval;
$data['dd_id'] = $dd_id;
if($q_r->dynamic_detail->ctc_require_email == true){
$q_dd = $this->{$this->main_model}->find()
->where([$this->main_model.'.dynamic_detail_id' => $dd_id,$this->main_model.'.mac' => $this->request->data['mac']])
->first();
if($q_dd){
if($dd_resuply_int > 0){ //This has an expiry date lets compare
$expiry_time = $q_dd->modified->toUnixString()+($dd_resuply_int * 24 * 60 *60);
$now = new FrozenTime();
if($expiry_time < $now->toUnixString()){
//It already expired ask for a new one
$data['ctc_require_email'] = true;
}
}
}else{
$data['ctc_require_email'] = true; //We did not found it so have to supply email
}
}
if($q_r->dynamic_detail->ctc_require_phone == true){
$q_dd = $this->{$this->main_model}->find()
->where([$this->main_model.'.dynamic_detail_id' => $dd_id,$this->main_model.'.mac' => $this->request->data['mac']])
->first();
if($q_dd){
if($dd_resuply_intP > 0){ //This has an expiry date lets compare
$expiry_time = $q_dd->modified->toUnixString()+($dd_resuply_intP * 24 * 60 *60);
$now = new FrozenTime();
if($expiry_time < $now->toUnixString()){
//It already expired ask for a new one
$data['ctc_require_email'] = true;
}
}
}else{
$data['ctc_require_phone'] = true; //We did not found it so have to supply email
}
}
if($q_r->dynamic_detail->ctc_require_dn == true){
$q_dd = $this->{$this->main_model}->find()
->where([$this->main_model.'.dynamic_detail_id' => $dd_id,$this->main_model.'.mac' => $this->request->data['mac']])
->first();
if($q_dd){
if($dd_resuply_intP > 0){ //This has an expiry date lets compare
$expiry_time = $q_dd->modified->toUnixString()+($dd_resuply_intP * 24 * 60 *60);
$now = new FrozenTime();
if($expiry_time < $now->toUnixString()){
//It already expired ask for a new one
$data['ctc_require_dn'] = true;
}
}
}else{
$data['ctc_require_dn'] = true; //We did not found it so have to supply dn
}
}
}
$this->set(array(
'data' => $data,
'success' => true,
'_serialize' => array('data','success')
));
}
* This brings us to the end of the CakePHP 3 modifications.
* The only remaining part is the login page themselves.
==== Modify the Login Pages for the Extra Options ====
* Locate the file rdConnect.js.
* It is typically under ///var/www/login/cp/js/rdConnect.js//
* We need to add a flag to set if we created the pop-up window (**ctcFormDone**)
cMinWidth = 240; //240 mobile 300 desktop
scrollHeight = 1000;
var ctcFormDone = false;
fDebug = function(message){
if(cDebug){
console.log(message)
}
};
* Next we have to add some **OR** coonditions when the user click on the **Click To Connect** button
var email_check = location.protocol+'//'+document.location.hostname+"/cake3/rd_cake/data-collectors/mac-check.json";
webix.ajax().timeout(3000).post(
email_check,formData,{
error : function(text, data, XmlHttpRequest){
console.log("ERROR -> Getting Info for MAC");
},
success : function(text, data, XmlHttpRequest){
if(data.json().success == true){
if((data.json().data.ctc_require_email == true)||(data.json().data.ctc_require_phone == true)||(data.json().data.ctc_require_dn == true)){
if(ctcFormDone == false){ //If not already done
buildClickToConnectForm(data.json().data);
}
showForm("winEmail", b);
}else{
onBtnClickToConnectClick();
}
}else{
console.log("OTHER ERROR");
}
}
});
* Lets then see the function that is called **buildClickToConnectForm**.
* This is called only once.
* We also include validation rules for those fields.
var buildClickToConnectForm = function(data){
console.log("Click To Connect Form");
console.log(data);
var e1 = {
view : "template",
template : "Please supply to get Guest Access"
};
var e2 = { view:"text", label:'Email', name:"email" };
var e3 = { view:"text", label:'Phone', name:"phone" };
var e4 = { view:"text", label:'DN', name:"dn" };
var b1 = { view:"button", value: "Submit", click:function(){
if (this.getParentView().validate()){ //validate form
var button = this;
var formData = new FormData();
var mac_address = getParameterByName('mac');
formData.append("mac", mac_address);
var values = this.getParentView().getValues();
console.log(values);
if(values.email){
formData.append("email", values.email);
}
if(values.phone){
formData.append("phone", values.phone);
}
if(values.dn){
formData.append("dn", values.dn);
}
//We also add the following
var called = getParameterByName('called');
formData.append("cp_mac", called);
var nasid = getParameterByName('nasid');
formData.append("nasid", nasid);
//This might not always be included
var ssid = getParameterByName('ssid');
if(ssid !== ''){
formData.append("ssid", ssid);
}
var add_mac = location.protocol+'//'+document.location.hostname+"/cake3/rd_cake/data-collectors/add-mac.json";
webix.ajax().timeout(3000).post(
add_mac,
formData,
{
error : function(text, data, XmlHttpRequest){
console.log("ERROR -> Adding MAC");
},
success : function(text, data, XmlHttpRequest){
if(data.json().success == true){
//console.log("ADDED MAC NOW TRY TO CONNECT");
webix.message("All is correct");
button.getTopParentView().hide(); //hide window
onBtnClickToConnectClick();
}else{
//console.log("OTHER ERROR");
webix.message({ type:"error", text:data.json().message });
}
}
});
}
else
webix.message({ type:"error", text:"Form data is invalid" });
}};
var elements = [e1];
var height = 140;
var rules = {};
if(data.ctc_require_email == true){
elements.push(e2);
height = height+60;
rules.email = webix.rules.isEmail;
}
if(data.ctc_require_phone == true){
elements.push(e3);
height = height+60;
rules.phone = function(value){
if (! /^[0-9]{8}$/.test(value)) {
webix.message("Phone number must be 8 digits long");
return false;
}
return true;
};
}
if(data.ctc_require_dn == true){
elements.push(e4);
height = height+60;
rules.dn = function(value){
if (! /^[0-9]{7}$/.test(value)) {
webix.message("DN number must be 7 digits long");
return false;
}
return true;
};
}
elements.push(b1);
frmEmail = {
view :"form",
borderless :true,
elements : elements,
rules :rules,
elementsConfig:{
labelPosition:"top",
}
};
webix.ui({
view : "popup",
id : "winEmail",
head : false,
height : height,
body :webix.copy(frmEmail)
});
ctcFormDone = true;
}
* That brings us to the totality of the changes required.
* These login pages does not need any further optimization. (They are done using Webix)