====== 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)