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
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
});
{
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
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;
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();
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();
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')
));
}
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)
}
};
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 <b>Guest Access</b>"
};
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;
}