im developing a custom joomla 2.5 component integrating jqGrid.
im setting up a controller task to handle (update mysql record) data sent from jqGrid postData
var grid = jQuery(\"#list\");
grid.jqGrid({
onSelectRow: function(id){
$('#list').editRow(id, true);
},
url: '/index.php?option=com_nutraidev&view=products&format=raw',
datatype: 'json',
mtype: 'GET',
colNames: [...],
rowNum: 25,
rowList: [5, 10, 20, 50],
height: 'auto',
pager: '#pager',
loadonce: true,
sortname: 'ID',
viewrecords: true,
direction:'RTL',
autowidth: true,
sortorder: \"desc\",
caption: 'abc',
width: '70%',
editurl:'/index.php?option=com_nutraidev&view=products&task=Products.save&token=". JUtility::getToken() ."=1',
postData: {'code':\" \",....},
.....
this is the url which makes the request
/index.php?option=com_nutraidev&view=products&task=Products.save&token=". JUtility::getToken() ."=1&format=raw
im getting:
error 500
Invalid controller: name='products', format='raw'
tried it from the browser with the actual token - same result.
here is the important part of my code:
com_nutraidev
- controllers
- products.php
require_once JPATH_COMPONENT.'/controller.php';
class NutraidevControllerProducts extends NutraidevController
{
public function &getModel($name = 'Products', $prefix = 'NutraidevModel')
{
$model = parent::getModel($name, $prefix, array('ignore_request' => true));
return $model;
}
public function save()
{
// Check for request forgeries.
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
// Initialise variables.
$app = JFactory::getApplication();
$model = $this->getModel('Products', 'NutraidevModel');
$data = JRequest::get('get');
// Attempt to save the data.
$return = $model->updItem($data);
}
}
com_nutraidev
- models
- products.php
jimport('joomla.application.component.modellist');
class NutraidevModelProducts extends JModelList {
public function __construct($config = array()) {
parent::__construct($config);
}
public function updItem($data)
{
// set the variables from the passed data
$code = $data['code'];
$name = $data['name'];
// set the data into a query to update the record
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->clear();
$query->update(' #__product ');
$query->set(' name = '.$db->Quote($name) );
$query->where(' code = ' . (int) $code );
$db->setQuery((string)$query);
if (!$db->query()) {
JError::raiseError(500, $db->getErrorMsg());
return false;
} else {
return true;
}
}
}
com_nutraidev
- views
- products
- view.raw.php
jimport('joomla.application.component.view');
class NutraidevViewProducts extends JView
{
/**
* Display the view
*/
public function display($tpl = null)
{
$app = JFactory::getApplication();
$document = JFactory::getDocument();
// Get data from the model
$items = $this->get('Items');
$this->state = $this->get('State');
$this->params = $app->getParams('com_nutraidev');
// Check for errors.
if (count($errors = $this->get('Errors'))) {;
throw new Exception(implode("\n", $errors));
}
// Assign data to the view
$response->page = 1;//JRequest::getVar('page');
$response->total = 1;//JRequest::getVar('total');
$response->records = count($items);
$i=0;
for ($i = 0; $i < count($items); ++$i) {
$response->rows[$i]['id'] = intval($items[$i]->code); //id
$response->rows[$i]['cell'] = array($items[$i]->code,
$items[$i]->name
);
}
echo json_encode($response);
jexit();
}
public function save($tpl = null)
{
echo "test";
jexit();
}
}
looking at other questions that had the similar issue i double checked my
administrator/components/nutraidev/nutraidev.xml
and made sure
<files folder="site">
<filename>controller.php</filename>
was there.
what could be the reason im getting this error ? i tried it also with view.json.php
and got the same result. thanks
Related
I have attached screen short I want to implement this stepped progress bar in Xamarin.iOS.
Please help any source code regarding to this process in Xamarin.iOS.Thanks
You could create a custom step progress bar.
public class StepProgressBarControl : StackLayout
{
Button _lastStepSelected;
public static readonly BindableProperty StepsProperty =BindableProperty.Create(nameof(Steps), typeof(int), typeof(StepProgressBarControl), 0);
public static readonly BindableProperty StepSelectedProperty =BindableProperty.Create(nameof(StepSelected), typeof(int), typeof(StepProgressBarControl), 0, defaultBindingMode: BindingMode.TwoWay);
public static readonly BindableProperty StepColorProperty = BindableProperty.Create(nameof(StepColor), typeof(Xamarin.Forms.Color), typeof(StepProgressBarControl), Color.Black, defaultBindingMode: BindingMode.TwoWay);
public Color StepColor
{
get { return (Color)GetValue(StepColorProperty); }
set { SetValue(StepColorProperty, value); }
}
public int Steps
{
get { return (int)GetValue(StepsProperty); }
set { SetValue(StepsProperty, value); }
}
public int StepSelected
{
get { return (int)GetValue(StepSelectedProperty); }
set { SetValue(StepSelectedProperty, value); }
}
public StepProgressBarControl()
{
Orientation = StackOrientation.Horizontal;
HorizontalOptions = LayoutOptions.FillAndExpand;
Padding = new Thickness(10, 0);
Spacing = 0;
AddStyles();
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == StepsProperty.PropertyName)
{
for (int i = 0; i < Steps; i++)
{
var button = new Button()
{
Text = $"{i + 1}", ClassId= $"{i + 1}",
Style = Resources["unSelectedStyle"] as Style
};
button.Clicked += Handle_Clicked;
this.Children.Add(button);
if (i < Steps - 1)
{
var separatorLine = new BoxView()
{
BackgroundColor = Color.Silver,
HeightRequest = 1,
WidthRequest=5,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.FillAndExpand
};
this.Children.Add(separatorLine);
}
}
}else if(propertyName == StepSelectedProperty.PropertyName){
var children= this.Children.First(p => (!string.IsNullOrEmpty(p.ClassId) && Convert.ToInt32(p.ClassId) == StepSelected));
if(children != null) SelectElement(children as Button);
}else if(propertyName == StepColorProperty.PropertyName){
AddStyles();
}
}
void Handle_Clicked(object sender, System.EventArgs e)
{
SelectElement(sender as Button);
}
void SelectElement(Button elementSelected){
if (_lastStepSelected != null) _lastStepSelected.Style = Resources["unSelectedStyle"] as Style;
elementSelected.Style = Resources["selectedStyle"] as Style;
StepSelected = Convert.ToInt32(elementSelected.Text);
_lastStepSelected = elementSelected;
}
void AddStyles(){
var unselectedStyle = new Style(typeof(Button))
{
Setters = {
new Setter { Property = BackgroundColorProperty, Value = Color.Transparent },
new Setter { Property = Button.BorderColorProperty, Value = StepColor },
new Setter { Property = Button.TextColorProperty, Value = StepColor },
new Setter { Property = Button.BorderWidthProperty, Value = 0.5 },
new Setter { Property = Button.BorderRadiusProperty, Value = 20 },
new Setter { Property = HeightRequestProperty, Value = 40 },
new Setter { Property = WidthRequestProperty, Value = 40 }
}
};
var selectedStyle = new Style(typeof(Button))
{
Setters = {
new Setter { Property = BackgroundColorProperty, Value = StepColor },
new Setter { Property = Button.TextColorProperty, Value = Color.White },
new Setter { Property = Button.BorderColorProperty, Value = StepColor },
new Setter { Property = Button.BorderWidthProperty, Value = 0.5 },
new Setter { Property = Button.BorderRadiusProperty, Value = 20 },
new Setter { Property = HeightRequestProperty, Value = 40 },
new Setter { Property = WidthRequestProperty, Value = 40 },
new Setter { Property = Button.FontAttributesProperty, Value = FontAttributes.Bold }
}
};
Resources = new ResourceDictionary();
Resources.Add("unSelectedStyle", unselectedStyle);
Resources.Add("selectedStyle", selectedStyle);
}
}
Or you could use Xamarin.Forms.StepProgressBar. Install it from NuGet.
I am using normal mvc textboxfor on strong view, I have created custom validation attribute (the detailed code explained below).
On form submit everything works fine. In case if validation fails by natural it shows error message as configured.
Now when I enter the correct value inside text box I expect the error message to be vanished automatically but this does not happen until I post the form
JS File
$.validator.addMethod('validaterequiredif', function (value, element, parameters) {
var id = parameters['dependentproperty'];
var clickValue = $("input[name=" + id + "]:checked").val();
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
if (clickValue == targetvalue) {
if (value == null) {
return false;
}
else {
return $.validator.methods.required.call(
this, value, element, parameters);
}
}
else {
return true;
}
});
$.validator.unobtrusive.adapters.add(
'validaterequiredif',
['dependentproperty', 'targetvalue'],
function (options) {
options.rules['validaterequiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['validaterequiredif'] = options.message;
});
Server side custom validator class as below
public class ValidateRequiredIf : ValidationAttribute, IClientValidatable
{
protected RequiredAttribute _innerAttribute;
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public bool AllowEmptyStrings
{
get
{
return _innerAttribute.AllowEmptyStrings;
}
set
{
_innerAttribute.AllowEmptyStrings = value;
}
}
public ValidateRequiredIf(string dependentProperty, object targetValue)
{
_innerAttribute = new RequiredAttribute();
DependentProperty = dependentProperty;
TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
// trim spaces of dependent value
if (dependentValue != null && dependentValue is string)
{
dependentValue = (dependentValue as string).Trim();
if (!AllowEmptyStrings && (dependentValue as string).Length == 0)
{
dependentValue = null;
}
}
// compare the value against the target value
if ((dependentValue == null && TargetValue == null) ||
(dependentValue != null && (TargetValue.Equals("*") || dependentValue.Equals(TargetValue))))
{
// match => means we should try validating this field
//if (!_innerAttribute.IsValid(value))
if (value == null)
// validation failed - return an error
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object, and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "validaterequiredif",
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (TargetValue ?? "").ToString();
if (TargetValue is bool)
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
}
Model property
[ValidateRequiredIf("IsFeederSelected", "True", ErrorMessage = "Please select atleast one feeder")]
public List<string> selectedMeterName { get; set; }
Strongly typed view
<div class="meterTextboxRadio col-md-4">
<p>
#Html.RadioButtonFor(m => m.IsFeederSelected, true, new { #class = "radio", #Name = "IsFeederSelected", value = "meter", id = "rdbMeterConsumption", #checked = "checked" })
<span> Feeder</span>
#Html.RadioButtonFor(m => m.IsFeederSelected, false, new { #class = "radio", #Name = "IsFeederSelected", value = "group", id = "rdbGroupConsumption",style= "margin-left: 30px;" })
<span> Group</span>
</p>
<div class="group dropdownhidden" id="MeterNameConsumption" style="margin-top:4px;">
#Html.DropDownListFor(m => m.selectedMeterName, Model.MeterName
, new { #class = "chosen-select" ,#id= "ddlConsumptionMeterName", multiple = "multiple", Style = "width:100%", data_placeholder = "Choose Feeders.." })
<span class="highlight"></span> <span class="bar"></span>
</div>
<div class="group dropdownhidden" id="GroupNameConsumption" style="margin-top:4px;">
#Html.DropDownListFor(m => m.selectedGroupName, Model.GroupName
, new { #class = "chosen-select", #id = "ddlConsumptionGroupName", multiple = "multiple", Style = "width:100%", data_placeholder = "Choose Group.." })
<span class="highlight"></span> <span class="bar"></span>
</div>
#Html.ValidationMessageFor(m => m.selectedMeterName, "", new { #class = "error" })
#Html.ValidationMessageFor(m => m.selectedGroupName, "", new { #class = "error" })
</div>
Please provide some inputs for the same
Thanks.
I am trying to prevent the login from another device. And after all the research i have found using session. I am using the default login system of yii2 framework. And added these codes in the user model.
The user model is:
<?php
namespace app\models;
use Yii;
//app\models\Users is the model generated using Gii from users table
use app\models\Users as DbUser;
class User extends \yii\base\Object implements \yii\web\IdentityInterface {
public $id;
public $username;
public $password;
public $authKey;
public $token;
public $email;
public $any;
public $user_type;
/**
* #inheritdoc
*/
public static function findIdentity($id) {
$dbUser = DbUser::find()
->where([
"id" => $id
])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $userType = null) {
$dbUser = DbUser::find()
->where(["token" => $token])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username) {
$dbUser = DbUser::find()
->where([
"username" => $username
])
->one();
if (!count($dbUser))
{
return null;
}
return new static($dbUser);
}
/**
* #inheritdoc
*/
public function getId() {
return $this->id;
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return Yii::$app->getSecurity()->validatePassword($password, $this->password);
}
public function session_validate()
{
// Encrypt information about this session
$user_agent = $this->session_hash_string($_SERVER['HTTP_USER_AGENT'], $this->any);
// Check for instance of session
if ( session_exists() == false )
{
// The session does not exist, create it
$this->session_reset($user_agent);
}
// Match the hashed key in session against the new hashed string
if ( $this->session_match($user_agent) )
{
return true;
}
// The hashed string is different, reset session
$this->session_reset($user_agent);
return false;
}
/**
* session_exists()
* Will check if the needed session keys exists.
*
* #return {boolean} True if keys exists, else false
*/
private function session_exists()
{
return isset($_SESSION['USER_AGENT_KEY']) && isset($_SESSION['INIT']);
}
/**
* session_match()
* Compares the session secret with the current generated secret.
*
* #param {String} $user_agent The encrypted key
*/
private function session_match( $user_agent )
{
// Validate the agent and initiated
return $_SESSION['USER_AGENT_KEY'] == $user_agent && $_SESSION['INIT'] == true;
}
/**
* session_encrypt()
* Generates a unique encrypted string
*
* #param {String} $user_agent The http_user_agent constant
* #param {String} $unique_string Something unique for the user (email, etc)
*/
private function session_hash_string( $user_agent, $unique_string )
{
return md5($user_agent.$unique_string);
}
/**
* session_reset()
* Will regenerate the session_id (the local file) and build a new
* secret for the user.
*
* #param {String} $user_agent
*/
private function session_reset( $user_agent )
{
// Create new id
session_regenerate_id(TRUE);
$_SESSION = array();
$_SESSION['INIT'] = true;
// Set hashed http user agent
$_SESSION['USER_AGENT_KEY'] = $user_agent;
}
/**
* Destroys the session
*/
private function session_destroy()
{
// Destroy session
session_destroy();
}
}
this a better way of doing it is below.
you do is that you add an extra column to your users table, name it 'conc_login' or preferably text because it might be difficult to predict the size of data we are expecting.
when the user logs in, check if the logins column is empty, if it is empty, create a json that contains the session_id, time of login using the time() function.
If the logins column is not empty or the count of the column when decoded is greater than zero, then you check if the count is greater than login limit , if the number of logins is not yet greater than the login limit, then append the new session to the logins column in the database table
If the login limit is reached then check through the logins and check for the one with expired time, for example, a user that is not active of 300secnds is assumed to be logged out, then you delete the session that has expired from the table
In any request that is been made by a logged user you check if the session key still exists in the login column of the database ($logins[‘session_key’]), and if it is not found then log the user out immediately to avoid escalation of right and if otherwise then update the $login[‘time’] to the new time().
You can implement this code.
Login Form Model Add this function for concurrent user validation
$get_limit = Setting::findOne(['name' => 'login_limit']);
$login_limit = 3; //$get_limit->value;
$active_sess = User::findOne($getUser->id);
if ($active_sess->conc_login == '' or count(json_decode($active_sess->conc_login)) == 0) {
$login_json = json_encode([
[Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()]
]);
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if (count(json_decode($active_sess->conc_login)) > 0 and count(json_decode($active_sess->conc_login)) $login_json = json_decode($active_sess->conc_login);
$login_json[] = [Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
//print_r($login_json); exit;
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if (count(json_decode($active_sess->conc_login)) >= $login_limit) {
$logins = json_decode($active_sess->conc_login);
foreach ($logins as $key => $login) {
if ($login->time < time() - 120) {
//this checks if the iterated login is greater than the current time -120seconds and if found to be true then the user is inactive
//then set this current login to null by using the below statement
//$logins[$key] = null; // or unset($logins[$key]) either should work;
unset($logins[$key]);
}
}
//after iteration we check if the count of logins is still greater than the limit
if (count($logins) >= $login_limit) {
//then return a login error that maximum logins reached
//echo 'you are not allowed to login as you have breeched the maximum session limit.';
//exit;
$login_json = json_encode($logins);
$active_sess->conc_login = $login_json;
$active_sess->save();
$this->addError($attribute, 'you are not allowed to login as you have breeched the maximum session limit.');
} else {
//then login is successsfull
$login_json = [];
foreach ($logins as $key => $val) {
$login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => $val->time];
}
$login_json[] = [Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
$active_sess->conc_login = $login_json;
$active_sess->save();
}
//update the logins column to equal to json_encode($logins);
}
User side update session every 60seconds : SiteController
public function actionUserSessionUpdate() {
$session = Yii::$app->session;
$userid = $session->get('userid');
$username = $session->get('username');
$data = array('session_id' => Yii::$app->session->getId());
$isUserLogin = (!empty($userid) && !empty($username)) ? 'true' : 'false';
if ($isUserLogin == 'false') {
echo 'gotologin'; exit;
//return $this->redirect(['/login']);
} else {
//Login user
$active_sess = Clientmanagement::findOne($userid);
$loginjson = json_decode($active_sess->conc_login);
$login_json = [];
foreach ($loginjson as $key => $val) {
if ($val->session_key == Yii::$app->session->getId()) {
$login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => time()];
} else {
$login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => $val->time];
}
}
$login_json = json_encode($login_json);
$active_sess->conc_login = $login_json;
$active_sess->save();
}
exit;
}
Client ajax
<?php
if (!empty($session->get('userid'))) {
$usersession_url = Yii::$app->urlManager->createAbsoluteUrl(["/site/user-session-update"]);
$scriptb = <<< JS
$(document).ready(function () {
//Wait 1 Minutes
setInterval(function(){
$.ajax({
type:"post",
data:'id=session',
url:"$usersession_url",
success:function(data){
}
});
}, 60000);
});
JS;
$this->registerJs($scriptb);
}
?>
UserController Logout
public function actionLogout() {
$session = Yii::$app->session;
$userid = $session->get('userid');
//concurrent active user session removed
$active_sess = Clientmanagement::findOne($userid);
$loginjson = json_decode($active_sess->conc_login);
foreach ($loginjson as $key => $login) {
if ($login->session_key == Yii::$app->session->getId()) {
unset($loginjson[$key]);
}
}
$login_json = json_encode($loginjson);
$active_sess->conc_login = $login_json;
$active_sess->save();
$session->destroy();
return $this->redirect(['/login']);
}
We can set that code in component and it will also work for every controller and action. Also, I changed a code
common/components/LoginDevicesLimit.php
<?php
namespace app\common\components;
use Yii;
/**
* Logged In Devices Storage Helper
*/
class LoginDevicesLimit extends \yii\base\Component
{
public function init() {
$this->saveLoginSession();
parent::init();
}
public function saveLoginSession(){
$login_limit = env('DEVICES_LIMIT', 3);
$active_sess = \Yii::$app->user->identityClass::findOne(\Yii::$app->user->id);
if(!$active_sess){
return false;
}
$session_count = count(json_decode($active_sess->conc_login, true));
if ($active_sess->conc_login == '' || $session_count == 0) {
$login_json = json_encode([
Yii::$app->session->getId() => ['session_key' => Yii::$app->session->getId(), 'time' => time()]
]);
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if ($session_count > 0 && $session_count < $login_limit) {
$login_json = json_decode($active_sess->conc_login, true);
$login_json[Yii::$app->session->getId()] = ['session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
//print_r($login_json); exit;
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if ($session_count > $login_limit) {
$logins = json_decode($active_sess->conc_login, true);
foreach ($logins as $key => $login) {
if ($login->time < time() - 120) {
//this checks if the iterated login is greater than the current time -120seconds and if found to be true then the user is inactive
//then set this current login to null by using the below statement
//$logins[$key] = null; // or unset($logins[$key]) either should work;
unset($logins[$key]);
}
}
//after iteration we check if the count of logins is still greater than the limit
if (count($logins) > $login_limit) {
//then return a login error that maximum logins reached
//echo 'you are not allowed to login as you have breeched the maximum session limit.';
//exit;
$login_json = json_encode($logins);
$active_sess->conc_login = $login_json;
$active_sess->save();
throw new \yii\web\HttpException(404, 'You are not allowed to login as you have reached the maximum session limit.');
} else {
//then login is successsfull
$login_json = [];
foreach ($logins as $key => $val) {
$login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => $val->time];
}
$login_json[] = [Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
$active_sess->conc_login = $login_json;
$active_sess->save();
}
}
}
}
You can generate additional authentication code during login process that will be stored in user's identity and saved in db as well. Every time user invokes action his code is compared to this saved in db and if doesn't match user is forcefully logged out . This way only the latest login is valid. refer this link
This is an alternative way to prevent the login from another device.
You need to configure your application as shown below using the webvimark/user-management library.
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`auth_key` varchar(32) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`confirmation_token` varchar(255) DEFAULT NULL,
`status` int(11) NOT NULL DEFAULT '1',
`superadmin` smallint(1) DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
`registration_ip` varchar(15) DEFAULT NULL,
`bind_to_ip` varchar(255) DEFAULT NULL,
`email` varchar(128) DEFAULT NULL,
`email_confirmed` smallint(1) NOT NULL DEFAULT '0',
`sessions` longtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
<?php
$config =
[
'bootstrap' => ['log', 'LogoutDevices'],
'components' =>
[
'user' => ['class' => 'webvimark\modules\UserManagement\components\UserConfig', 'enableAutoLogin' => true, 'on afterLogin' => function($event) {(new \app\components\ABCDEFG\DevicesLimit\LoginDevicesLimit())->sessionManager(); \webvimark\modules\UserManagement\models\UserVisitLog::newVisitor($event->identity->id);}],
'LogoutDevices' => ['class' => 'app\components\ABCDEFG\DevicesLimit\LogoutDevices']
]
]
Then, you need to use the classes below:
<?php
namespace app\models\UserManagement;
use Yii;
/**
* This is the model class for table "user".
*
* #property string $sessions
*/
class User extends \webvimark\modules\UserManagement\models\User
{
function __construct($config = [])
{
parent::__construct($config);
}
function init()
{
parent::init();
}
}
<?php
namespace app\components\ABCDEFG\DevicesLimit;
use Yii;
use yii\base\Component;
use app\models\UserManagement\User;
/**
* Logged In Devices Storage Helper
*
* Class LoginDevicesLimit
* #package app\components\ABCDEFG\DevicesLimit
*
* #property integer $iDevicesLimit
*
*/
class LoginDevicesLimit extends Component
{
private int $iDevicesLimit = 1;
public function init()
{
parent::init();
}
public function getDevicesLimit() : int
{
return $this->iDevicesLimit;
}
public function setDevicesLimit(int $iDevicesLimit = 1) : self
{
if($iDevicesLimit >= 1)
{
$this->iDevicesLimit = $iDevicesLimit;
}
return $this;
}
public function sessionManager() : self
{
if(isset(Yii::$app->user->id))
{
$oUser = User::findOne(Yii::$app->user->id);
if(!empty($oUser))
{
if(User::hasRole('Expert', $superAdminAllowed = false))
{
$this->setDevicesLimit(3);
}
$sSessionsJSON = $oUser->sessions;
$aSessions = json_decode($sSessionsJSON, true);
$aSession = Helper::getCurrentSessionData();
if(is_array($aSessions) && !empty($aSessions))
{
$bIsSessionExists = false;
foreach($aSessions as $iSessionKey => $aSessionData)
{
if($aSessionData['id'] == $aSession['id'])
{
$aSessions[$iSessionKey] = $aSession;
$bIsSessionExists = true;
break;
}
}
if($bIsSessionExists == true)
{
$aTime = array_column($aSessions, 'time');
array_multisort
(
$aTime, SORT_NUMERIC, SORT_ASC,
$aSessions
);
}
else
{
array_unshift($aSessions, $aSession);
}
}
else
{
$aSessions[] = $aSession;
}
$aSessions = array_slice($aSessions, 0, $this->getDevicesLimit());
$sSessionsJSON = json_encode($aSessions);
if(json_last_error() == JSON_ERROR_NONE)
{
$oUser->sessions = $sSessionsJSON;
$oUser->save();
}
}
}
return $this;
}
}
<?php
namespace app\components\ABCDEFG\DevicesLimit;
use Yii;
use yii\base\Component;
use app\models\UserManagement\User;
/**
* Logged In Devices Storage Helper
*
* Class LogoutDevices
* #package app\components\ABCDEFG\DevicesLimit
*
*/
class LogoutDevices extends Component
{
public function init()
{
parent::init();
self::logoutOnExceedingLimit();
}
public static function logoutOnExceedingLimit()
{
$xRetValue = NULL;
if(Yii::$app->request->isAjax == false && isset(Yii::$app->user->id))
{
$oUser = User::findOne(Yii::$app->user->id);
if(!empty($oUser))
{
$sSessionsJSON = $oUser->sessions;
$aSessions = json_decode($sSessionsJSON, true);
$aSession = Helper::getCurrentSessionData();
if(is_array($aSessions) && !empty($aSessions))
{
$bIsSessionExists = in_array($aSession['id'], array_column($aSessions, 'id'));
if($bIsSessionExists == false)
{
Yii::$app->session->setFlash('devices-limit', true);
Yii::$app->session->close();
Yii::$app->user->logout(false);
$xRetValue = Yii::$app->response->redirect(['site/devices-limit-reached'], 302);
}
}
}
}
return $xRetValue;
}
}
<?php
namespace app\components\ABCDEFG\DevicesLimit;
use Yii;
use yii\base\Component;
/**
* Helper
*
* Class Helper
* #package app\components\ABCDEFG\DevicesLimit
*
*/
class Helper extends Component
{
public function init()
{
parent::init();
}
public static function getCurrentSessionData() : array
{
return
[
'id' => Yii::$app->session->getId(),
'time' => time(),
'device' => Yii::$app->request->userAgent,
];
}
}
I'd like to display errors on my form, highlighting the fields which have errors, and displaying the error text next to the field. If there's no elegant way to display next to each field, above would be fine.
I've found examples from earlier versions, but the API has seemed to change and they do not work for 3.2.
It's just a project I'm learning Kohana with, so it's not critical. I just want to know the "kohana" way of handling this problem.
In my controller, I have this:
if (isset($_POST) && Valid::not_empty($_POST))
{
$post = Validation::factory($_POST)
->rule('zipcode', 'not_empty'));
if ($post->check()) {
$errors = $post->errors('zipcode');
}
}
$this->template->content = View::factory('myview', $data)
->bind('errors', $errors);
And here is my form in 'myview.php':
<?php echo Form::open(); ?>
<dl>
<dt><?php echo Form::label('zipcode', 'Zip Code') ?></dt>
<dd><?php echo Form::input('zipcode') ?></dd>
</dl>
<p><?php echo Form::submit(NULL, 'Get Records'); ?></p>
<?php echo Form::close(); ?>
I've taken the approach of extending the Form helper class to add an 'error' class name on the form fields, as well as showing the error message in the field label.
<?php defined('SYSPATH') or die('No direct script access.');
class Form extends Kohana_Form {
private static function attributes($name, & $attributes = NULL, $errors = NULL)
{
// Set the id attribute
if (!isset($attributes['id']))
{
$attributes['id'] = $name;
}
if ($errors !== NULL)
{
// Merge in external validation errors.
$errors = array_merge($errors, (isset($errors['_external']) ? $errors['_external'] : array()));
// Set the error classname
if (isset($errors[$name]))
{
$attributes['class'] = trim( (string) #$attributes['class'].' error-field');
}
}
}
public static function input($name, $value = NULL, array $attributes = NULL, array $errors = NULL)
{
static::attributes($name, $attributes, $errors);
return parent::input($name, $value, $attributes);
}
public static function select($name, array $options = NULL, $selected = NULL, array $attributes = NULL, array $errors = NULL)
{
static::attributes($name, $attributes, $errors);
return parent::select($name, $options, $selected, $attributes);
}
public static function password($name, $value = NULL, array $attributes = NULL, array $errors = NULL)
{
static::attributes($name, $attributes, $errors);
return parent::password($name, $value, $attributes);
}
public static function textarea($name, $body = '', array $attributes = NULL, $double_encode = TRUE, array $errors = NULL)
{
static::attributes($name, $attributes, $errors);
return parent::textarea($name, $body, $attributes, $double_encode);
}
public static function file($name, array $attributes = NULL, array $errors = NULL)
{
static::attributes($name, $attributes, $errors);
return parent::file($name, $attributes);
}
public static function label($input, $text = NULL, array $attributes = NULL, array $errors = NULL, $view = 'messages/label_error')
{
if ($errors !== NULL)
{
// Merge in external validation errors.
$errors = array_merge($errors, (isset($errors['_external']) ? $errors['_external'] : array()));
// Use the label_error view to append an error message to the label
if (isset($errors[$input]))
{
$text .= View::factory($view)->bind('error', $errors[$input]);
}
}
return parent::label($input, $text, $attributes);
}
}
You then pass in the $errors array into the label and field helper methods:
<?php echo
Form::label('username', 'Username', NULL, $errors),
Form::input('username', $user->username, NULL, $errors);
?>
This idea was suggested on the Kohana forums but I've struggled to find the original thread. Anyway, I've found this approach works best for me.
[edit] View an example of this approach in action here: http://kohana3.badsyntax.co/contact (submit the form)
This is some sample code I have used for a personal experiment with Kohana forms. It is part of a contact form. This should work for you.
The code below shows a contact form. After a user submits the form, it gives feedback (failed + errors / succeed).
if (isset($errors) && count($errors) > 0)
{
echo '<ul>';
foreach ($errors as $error)
{
echo '<li>' . $error . '</li>';
}
echo '</ul>';
}
// form
echo Form::open(null);
// fields
echo Form::label('firstname') . Form::input('firstname', null, array('id' => 'firstname')) . '<br />';
echo Form::label('email') . Form::input('email', null, array('id' => 'email')) . '<br />';
echo Form::label('message') . Form::textarea('message', '', array('id' => 'message')) . '<br />';
// submit
echo Form::submit('submit', 'Send message');
echo Form::close();
In the controller, I validate the form and assign the error- and success messages to the view.
public function action_index()
{
// make view
$view = View::factory('pages/contact')
->bind('post', $post)
->bind('errors', $errors)
->bind('success', $success);
// check if form is submitted
if ($_POST)
{
// trim fields
$post = array_map('trim', $_POST);
$post = Validation::factory($_POST)
->rules('firstname', array(
array('not_empty'),
array('min_length', array(':value', '2'))
))
->rules('email', array(
array('not_empty'),
array('email')
))
->rule('message', 'not_empty');
if ($post->check())
{
$success[] = 'Thank you for your message!';
}
else
{
$errors = $post->errors('contact');
}
}
// view
$this->response->body($view);
}
I hope this helps!
I have a problem with my CakePhp Site.
My index is showing fine with pagination, but when I search the page, pagination is gone and the search results aren't showing (instead the index is showing without paging).
Any idea where my code is wrong?
class ItemsController extends AppController {
var $name = 'Items';
// load any helpers used in the views
var $helpers = array('Paginator', 'Html', 'Form', 'Javascript', 'Misc', 'Time', 'Tagcloud');
var $components = array('RequestHandler');
/**
* index()
* main index page for items
* url: /items/index
*/
function index() {
// get all options for form
$tags = $this->Item->Tag->find('list', array(
'fields'=>'id, name',
'order'=>'Tag.name',
'conditions'=> array(
'Tag.status'=>'1'
)
));
// add name to option
$tags = array(''=>'Tags') + $tags;
//pr($tags);
// if form submitted
if (!empty($this->data)) {
// if reset button pressed redirect to index page
if(isset($this->data['reset'])) {
$this->redirect(array('action'=>'index'));
}
// init
$url = '';
// remove search key if not set
if($this->data['search'] == '') {
unset($this->data['search']);
}
// loop through filters
foreach($this->data as $key=>$filter) {
// ignore submit button
if($key != 'filter') {
// init
$selected = '';
switch($key) {
case 'tag':
$selected = $tags[$filter];
break;
case 'search':
$selected = $filter;
break;
}
// if filter value is not empty
if(!empty($filter)) {
$selected = $this->slug($selected);
$url .= "/$key/$selected";
}
}
}
// redirect
$this->redirect('/items/index/'.$url);
} else {
// set form options
$this->data['tag'] = '';
$this->data['search'] = '';
}
// if any parameters have been passed
if(!empty($this->params['pass'])) {
// only select active items
$conditions = array('Item.status'=>1);
// get params
$params = $this->params['pass'];
// loop
foreach($params as $key=>$param) {
// get the filter value
if(isset($params[$key+1])) {
$value = $params[$key+1];
}
// switch on param
switch($param)
{
case 'tag':
// get tag
$tag = $this->Item->Tag->find('first', array(
'recursive' => 0,
'conditions' => array(
'Tag.slug'=>$value
)
));
// save value for form
$this->data['tag'] = $tag['Tag']['id'];
break;
case 'search':
// setup like clause
$conditions['Item.name LIKE'] = "%{$value}%";
// save search string for form
$this->data['search'] = str_replace('_', ' ', $value);
break;
}
}
//pr($conditions);
// get all items with param conditions
$items = $this->Item->find('all', array(
'order' => 'Item.name',
'conditions' => $conditions
));
// if tag filter has been set
if(isset($tag)) {
// loop through items
foreach($items as $key=>$item) {
// init
$found = FALSE;
// loop through tags
foreach($item['Tag'] as $k=>$g) {
// if the tag id matches the filter tag no need to continue
if($g['id'] == $tag['Tag']['id']) {
$found = TRUE;
break;
}
}
// if the tag was not found in items
if(!$found) {
// remove from list
unset($items[$key]);
}
}
}
} else {
// get all items from database where status = 1, order by name
$items = $this->Item->find('all', array(
'order' => 'Item.name',
'conditions' => array(
'Item.status'=>1
)
));
}
$this->paginate = array(
'limit' => 10
);
$data = $this->paginate('Item');
// set page title
$this->pageTitle = 'Index Page';
// set layout file
$this->layout = 'index';
// save the items in a variable for the view
$this->set(compact('data', 'tags', 'items'));
}
You'll want to pass your search conditions to the paginate functionality. You do this through the controller's paginate property.
function index() {
...
switch($param) {
...
case 'search':
$this->paginate['conditions']['Item.name LIKE'] = "%{$value}%";
More information on setting up pagination can be found here.