I've written a magento controller which stores some filter information inside the customer session.
According to the magento 2 documentation I use dependency injection to let magento generate the session object for me:
/**
* #var \Magento\Catalog\Model\Session
*/
protected $_filterSession;
/**
* #param \Magento\Customer\Model\Session $filterSession
*/
public function __construct( \Magento\Customer\Model\Session $filterSession)
{
$this->_filterSession = $filterSession;
}
The injection process is working quite well. I'm able to access the session, store a variable in it and return it on a further invocation.
But magento seems to discard the whole session information from time to time. I cannot exactly identify the moment magento discards the information, it seems kind of random.
Here is the code:
$this->_filterSession->setFrequency($frequency);
$frequency = $this->_filterSession->getFrequency();
I tried out different session scopes but the behaviour is the same.
After many attempts I tried to use the PHP-session to store the information, but even this session was discarded from time to time.
I don't know what I'm doing wrong or what could be the reason for this weird behaviour. Does anybody else have a similar problem or an idea whats the reason?
Thanks in advance,
Thomas
This usually happens when the browser loses session cookies. You should check if domain name changes during the session when the variables are lost. Or with a different browser. Might be some misbehaving browser plugin. Or some Magento extension. Many Mageno 2 extensions currently are poorly written.
I had a similar problem using PHP. I had set session.referer_check. So, when a user was coming from an external page, the session was lost. If this is your problem, simply ini_set('session.referer_check', '');.
I didn't find a solution to the problem itself, but avoided it.
For those who also encounter the problem, here is my bandaid fix:
I introduced a new cookie
public function getFrequency()
{
$frequency = $this->_cookieManager->getCookie(self::FREQUENCY_SESSION_KEY);
if( !isset( $frequency ) )
{
$frequency = self::FREQUENCY_DEFAULT_VALUE;
}
return $frequency;
}
public function setFrequency( $frequency )
{
$metadata = $this->_cookieMetadataFactory
->createPublicCookieMetadata()
->setPath($this->_sessionManager->getCookiePath())
->setDomain($this->_sessionManager->getCookieDomain());
$this->_cookieManager->setPublicCookie(
self::FREQUENCY_SESSION_KEY,
$frequency,
$metadata
);
}
for further details I recommend you to look at this thread.
Regards, Thomas
Related
Is there a way to check if a user already has a valid session on a different machine?
What I want to do is when a user logs in, destroy an other sessions which they may already have, so that if they forget to logout from a computer say on campus or at work, and then they log in at home, it will destroy those other 2 sessions so they are no longer logged in?
Facebook employs this in some way.
My only thoughts so far is something to this effect:
$user = User::find(1); // find the user
Auth::login($user); // log them in
Auth::logout(); // log them out hoping that it will destroy all their sessions on all machines
Auth::login($user); // log them in again so they have a valid session on this machine
I have not had the chance to test this, and I do not know if Auth::login($user); will destroy all sessions for that user, or only the current one.
Thanks!
You can save a session_id within a user model, so that:
When logout event is fired (auth.logout) you would clear it.
When new logging event is fired you can check if attribute session_id is not null within the user model.
If it's not - destroy previous session by:
Session::getHandler()->destroy($user->session_id);
$user->session_id = Session::getId();
Hope that would help!
I realise this is an old question, but there is now a method in laravel 5.6 that does exactly this, so it may be useful for someone coming to this later. You can also retro-fit this method to earlier versions of laravel very easily.
See the docs at https://laravel.com/docs/5.6/authentication#invalidating-sessions-on-other-devices
I had the same use case as you (log out all other devices on log-in). I overrode the default login method to add my own custom logic (first copying the default login method from vendor/laravel/framework/src/illuminate/Foundation/Auth/AuthenticatesUsers.php)
In that method, there is the line if ($this->attemptLogin($request)) - within this, before the return statement, add your call to logoutOtherDevices, as below
if ($this->attemptLogin($request)) {
//log out all other sessions
Auth::logoutOtherDevices($request->password); //add this line
return $this->sendLoginResponse($request);
}
Also ensure you have un-commented the Illuminate\Session\Middleware\AuthenticateSession middleware in your app/Http/Kernel.php, as per the docs
(note that I haven't tested the above code as I was using an older version of laravel that doesn't have this method, see below). This should work in 5.6 though.
Older Laravel versions
I was actually using laravel 5.5, so didn't have access to this handy method. Luckily, it's easy to add.
I opened a laravel 5.6 project and copied the logoutOtherDevices method from vendor/laravel/framework/src/illuminate/Auth/SessionGuard.php - for reference I have pasted below
/**
* Invalidate other sessions for the current user.
*
* The application must be using the AuthenticateSession middleware.
*
* #param string $password
* #param string $attribute
* #return null|bool
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! $this->user()) {
return;
}
return tap($this->user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
I then copied this into my LoginController - it could go somewhere else of your choice, but I've put it here for ease / laziness. I had to modify it slightly, as below ($this->user() becomes Auth::user())
/**
* Invalidate other sessions for the current user.
* Method from laravel 5.6 copied to here
*
* The application must be using the AuthenticateSession middleware.
*
* #param string $password
* #param string $attribute
* #return null|bool
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! Auth::user()) {
return;
}
return tap(Auth::user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
I can then call this method in my login method, as specified earlier in my answer, with a slight adjustment - $this->logoutOtherDevices($request->password);
If you want to test this locally, it seems to work if you open your site on a normal and an incognito window. When you log in on one, you'll be logged out on the other - though you'll have to refresh to see anything change.
I hope you will see this job:
Session::regenerate(true);
a new session_id be obtained.
This may not be the best answer, but first thing that came to my mind was lowering the timeout on the session.
In app->config->session.php there's a setting for both lifetime and expire_on_close (browser).
I'd try looking into that for now, and see if someone else comes up with something better.
I made some classes having a lot of methods documented properly using PHP (something like a library).
Now, what the other developers will do is just require the PHP library I made in their code and use the predefined functions in it.
Is it possible to hide the PHP code (of the library I made) from the other PHP developers (requiring the file) and just show them the function name, parameters and its documentation without showing the code inside it? I'm not talking about obfuscation, which can be reversible, I'm talking about preventing users to actually see any code.
eg.
/**
*
* CREATE A NEW THREAD
* #param unknown_type $uid User ID of person who is creating the thread
* #param unknown_type $participant An array having collection of UID of people who are participating in this conversation
* #param unknown_type $msgtype Message Type Flags (1-normal, 2-chat, 3-sent as email, 4-profile post, 5-group post, 6-customer support)
* #param unknown_type $subject Subject of the thread
* #param unknown_type $tname Thread Name
* #param unknown_type $tpic Thread Cover Picture (Defaults to "")
* #param unknown_type $tflag Thread Flag (1-allowed,2-under review,3-blocked) (Defaults to 1)
* #return string|Ambigous <string, unknown> Thread ID on success, "" on failure
*/
public function createthread($uid,$participant,$msgtype,$subject,$tname,$tpic="",$tflag="1")
{
$randobj=new uifriend();
$tid=$randobj->randomstring(30,DB_MESSAGE,MSG_OUTLINE,msgoutline_tid);
$socialobj=new socialoperations();
$listid=$socialobj->createlist("threadlist_".$tid, "2",$msgtype,"1",$uid);
if($socialobj->addtolist($participant, $listid, $uid)!="SUCCESS")
{
return "";
}
if($listid=="")
{
$lasterror="An error occured in creating thread! Unable to Create Lists!";return "";
}
$dbobj=new dboperations();
$res=$dbobj->dbinsert("INSERT INTO ".MSG_OUTLINE." (".msgoutline_tid.",".msgoutline_subject.",".msgoutline_fid.",".msgoutline_participantid.",".msgoutline_msgtype.",".msgoutline_threadpic.",".msgoutline_threadflag.") VALUES
('$tid','$subject','$uid',$listid,'$msgtype','$tpic','$tflag')",DB_MESSAGE);
if($res=="SUCCESS")
{
return $tid;
}
else
{
$lasterror="Unable to create Thread!";return "";
}
}
The other developers must only be able to see the documentation I wrote above the function with the function name and parameters, but the code must not be accessible to them in any way.
Why I want this: I have a lot of secure code in my PHP file which I don't want to show to the other developers, but still allow them to call the functions and read the returned values.
You can't hide your code from other developers if you want to allow them call your functions directly. What you can do is to make a Web Service and give it's documentation to other developers.
Because I had a meta post so this was reopened and another meta post for formatting this question, I'll do my best to properly answer this question. Note that this is only a way of doing this, with its limitations stated at the end of the post.
The API
The remote server
You could create a web API in a different domain and access it from your main domain. I think the best way for explaining how it works is with a practical example. Imagine that your library includes the function 'joinstrings()', which takes 2 arguments. Then you have it in your separated web:
http://apiweb.com/functions.php
<?php
// Your API. I hope the real one is more complex than this (;
function joinstrings($s1, $s2)
{
return $s1 . $s2;
}
// More functions
The remote server access point
This is the public (but key-required) accessible page.
http://apiweb.com/joinstrings/index.php
<?php
// Check if the key is valid and if $v1 and $v2 aren't empty. Else, 'exit;'
include '../validate.php';
// Your API
include '../functions.php';
// The called function
echo joinstrings(urldecode($_GET['v1']), urldecode($_GET['v2']));
The wrapper
Now you can require all your programmers to learn how to use this API. Or, if you prefer to do it right, you'd make a wrapper that makes their life easier. You'd have a class with all the methods that you want to be accessible. You could do this wrapper with functions, but I think it's easier and better with an object and methods:
htpp://web.com/library.php
<?php
class DevelopersLibrary
{
private $Url = "http://apiweb.com/";
// Press your hand against the keyboard. A-Z0-9. Copy it in http://apiweb.com/validate.php
private $Key = "g139h0854g76dqfdbgng";
// Accesible method
public joinstrings($v1, $v2)
{
// Encode only the user input. You don't want to encode '?' nor '&'
if ($Return = file_get_contents($this->Url . 'joinstring'
'?key=' . $this->Key .
'&v1=' . urlencode($v1) .
'&v2=' . urlencode($v2)))
{
return $Return;
}
}
}
Developer's code
Finally, what your developers would do:
http://web.com/index.php
<?php
include './library.php';
$Lib = new DevelopersLibrary();
echo $Lib->joinstrings("Are you sure this is better", "than giving your developers access to the code?");
None of the code is tested, so you should expect some some typos.
Limitations
I can think of solutions for most limitations, but not to extend (more) this post I won't write them here. Ask for a solution to a limitation if you need it in the comments and I'll do my best. In normal case use, none of these limitations are THAT important.
Parameters passed. Using this method as described above, you can only pass numbers or strings as function parameters. Check out json_encoding() for passing other types.
Wrong returned values when there are bugs in the API or parameters passed. If there's a bug in the API, the developers cannot fix it and the returned value might be wrong. Now that might seem trivial, but what if they are trying to retrieve the join of 2 strings and retrieve another [wrong] string with the error text in it? Note: consider returning valid XML and then parsing it in your wrapper.
There's only a unique key which is there for preventing random users from using your API, not to be hidden from developers.
Slower speed. I don't think this even needs explanation.
Developer's extra work. This is solved this with the implementation of the wrapper.
Url length. There's a url length limitation for most browsers of 2000 characters, although I didn't find anything in the PHP manual for file_get_contents(). Read this SO question for more info about GET.
Sure there are more but these are the main ones I could think of.
I hope this long long answer is useful for you or someone.
I hope I worded the title accurately enough but I typically use Java and don't have much experience in Web Development/PHP/CodeIgniter. I have a difficult time understanding the life cycle of a script as I found out trying to implement a certain feature to a website I am developing (as a means of learning how to). I'll first describe the feature I tried implementing and then the problem I ran into that made me question my fundamental understanding of how scripts work since I'm used to typical OOP.
Ok so here goes...
I have a webpage that has 2 basic tasks a user can do, create and delete an entry. What I attempted to implement was a way to time a user how long it takes them to complete a certain task. The way I did this was have a homepage where there would be a list of tasks a user to choose from (in this case 2, create and delete). A user would click a task which would link to the 'true' homepage where the user then would be expected to complete the task. My script looks like this:
<?php
class Site extends CI_Controller {
var $task1;
var $tasks = array(
"task1" => NULL,
"date1" => 0,
"date2" => 0,
"diff" => 0);
function __construct()
{
parent::__construct();
include 'timetask.php';
$this->task1 = new TimeTask("create");
}
function index()
{
$this->tasks['task1'] = $this->task1->getTask();
$this->tasks['diff'] = $this->task1->getTimeDiff();
if($this->tasks['diff'] == NULL)
{
$this->tasks['diff'] = 0;
}
$this->load->view('usability_test', $this->tasks);
}
function origIndex()
{
$this->task1->setDate1(new DateTime());
$this->tasks['date1'] = $this->task1->getDate1()->getTimestamp();
$data = array();
if($q = $this->site_model->get_records())
{
$data['records'] = $q;
}
$this->load->view('options_view', $data);
}
function create()
{
$this->task1->setDate2(new DateTime());
$this->tasks['date2'] = $this->task1->getDate2()->getTimestamp();
$data = array(
'author' => $this->input->post('author'),
'title' => $this->input->post('title'),
'contents' => $this->input->post('contents')
);
$this->site_model->add_record($data);
$this->index();
}
I only included create to keep it short. Then I also have the TimeTask class, that actually another StackOverflow so kindly helped me with:
<?php
class TimeTask
{
private $task;
/**
* #var DateTime
*/
private $date1, $date2;
function __construct($currTask)
{
$this->task = $currTask;
}
public function getTimeDiff()
{
$hasDiff = $this->date1 && $this->date2;
if ($hasDiff) {
return $this->date2->getTimestamp() - $this->date1->getTimestamp();
} else {
return NULL;
}
}
public function __toString()
{
return (string) $this->getTimeDiff();
}
/**
* #return \DateTime
*/
public function getDate1()
{
return $this->date1;
}
/**
* #param \DateTime $date1
*/
public function setDate1(DateTime $date1)
{
$this->date1 = $date1;
}
/**
* #return \DateTime
*/
public function getDate2()
{
return $this->date2;
}
/**
* #param \DateTime $date2
*/
public function setDate2(DateTime $date2)
{
$this->date2 = $date2;
}
/**
* #return get current task
*/
public function getTask()
{
return $this->task;
}
}
?>
I don't think posting the views is necessary for the question but here is atleast how the links are made.
<?php echo form_open('site/create');?>
...and...
<?php echo anchor("site/delete/$row->id", $row->title); ?>
Now there's no error in the code but it doesn't do what I expect of it and the reason I assume why is because that each time a function of the script is called via a new page it is NOT the same instance of the script called previously so any previously created objects are no longer there. This confuses me and leaves me quite unsure of how to implement this gracefully. Some ways I would guess of how to do this is by passing the necessary data through the URL or have data saved in a database and retrieve it later to compare the times. What would be a recommended way to do, not just this, but anything that needs previously created data? Also, am I correct to think that a script is only 'alive' for one webpage at a time?
Thanks!
Web development is a bit different to "standard" development - principally because of the nature of HTTP. Each request to the web application has to travel across the network using HTTP, which, as all web developers know, is stateless. What this means is that web servers do not have to remember anything about previous HTTP requests. Usually, webdevs get round this using cookies in one way or another - where a cookie is some bit of data, coded as a text string, which is sent back to the browser so that it can resend it to the application on the next request. Like that, a cookie is a kind of transferable memory.
So, each time you make a request, unless you transfer some data using a cookie (either an HTTP cookie, or what is sometimes called a URL cookie - state data coded in the URL), it looks to the web application like a brand new request, unrelated to any past request. So, for your application to work, you need to use a cookie in some way to remember or recover the start time when you detect that the user has finished a task. You can either (i) use CI's built-in facilities for remembering data (flashdata, as mentioned above, or userdata from the CI Session class - see http://codeigniter.com/user_guide/libraries/sessions.html), which are built on top of CI cookies), (ii) do this using your own cookie data (not recommended - why use the framework in that case?), or (iii) use hidden form fields - an oldie but sometimes goldie technique that requires the PHP script generating a view to write hidden form fields whose values are the data you want to remember and have sent back to you on the next request.
This kind of problem is something you'll come across again and again in web development - so get to know the problem and its solutions well!
You can use flashdata to make data available for next server request.
Reference: http://codeigniter.com/user_guide/libraries/sessions.html
You can go the extra step with sessions and implement the database table to store the current sessions. This gives you the ability to validate the id and confirm it's a valid session and not an old session accidentially restored via modded cookies for example.
It's very straight forward.
Create the table:
CREATE TABLE IF NOT EXISTS `ci_sessions` (
session_id varchar(40) DEFAULT '0' NOT NULL,
ip_address varchar(45) DEFAULT '0' NOT NULL,
user_agent varchar(120) NOT NULL,
last_activity int(10) unsigned DEFAULT 0 NOT NULL,
user_data text NOT NULL,
PRIMARY KEY (session_id),
KEY `last_activity_idx` (`last_activity`)
);
Modify the config file(application/config/config.php):
$config['sess_use_database'] = TRUE;
$config['sess_table_name'] = 'ci_sessions';
And then, use the standard methods to set and get the values.
Here is the guide: CI User Guide - Session Class
Am creating a web application with the codeigniter framework, am working with version 2.0.3.
My makes ajax requests to update the page from time to time, and fetch notifications.
I've visited the codeigniter forums and asking questions about codeigniter sessions and ajax and found this snippet of code which i used, and saved in libraries and class "My_session.php"
class MY_Session extends CI_Session {
/**
* Update an existing session
*
* #access public
* #return void
*/
function sess_update() {
// skip the session update if this is an AJAX call! This is a bug in CI; see:
// https://github.com/EllisLab/CodeIgniter/issues/154
// http://codeigniter.com/forums/viewthread/102456/P15
if ( !($this->CI->input->is_ajax_request()) ) {
parent::sess_update();
}
}
}
But ever since i added this code i am unable to stay logged for more than five minutes without being logged out, or sometimes not being able to login in at all.
Does anyone have a similar experience?
If you want to stay logged in longer you have set sess_expiration for more then five minutes in your application/config/config.php
what happens when you evaluate the is_ajax_request()? in that code snippet add:
echo 'AJAX: ' . $this->CI->input->is_ajax_request(); exit;
to see if the if statement is working correctly. it might be returning false and updating the session each time. just a quick place to start :)
I'm using doctrine2 (2.0.3) with codeigniter, and I noticed that when I add, change or even remove some methods that are anotaded as lifecycle, sometimes doctrine just ignores the change. For example, I add
/*
* #PostLoad
*/
private function setUpObj() {
echo('in');
}
To the Model (entity) that #HasLifecycleCallbacks, function sometimes is called on postload, sometimes it is ignored, sometimes it accept one change, then ignores any other changes...
In bootstrap file I use some of the config options, here is sample of them, if more needed I will update my post
$cache = new \Doctrine\Common\Cache\ArrayCache;
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Set up driver
$Doctrine_AnnotationReader = new \Doctrine\Common\Annotations\AnnotationReader($cache);
$Doctrine_AnnotationReader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
$driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($Doctrine_AnnotationReader, APPPATH.'models');
$config->setMetadataDriverImpl($driver);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
$config->setAutoGenerateProxyClasses( TRUE );
After some time, (usually when I give up changing the method, take a walk and come back) it starts to work normally, it accepts my last change and then I usually create what my intention was and stop changing that method.
My server is standard/default xampp on win7, and I never noticed anything similar to any other php files so far. This is not related only to #PostLoad, but it happens with #PrePersist and #PreUpdate as well
Is this normal behavior, or am I missing something?
Thanks in advance,
Dalibor
It seems that notation and comment MUST be like this
/**
* #PostLoad
*/
function setUpObj() {
$this->mainObjName = 'models\Page';
$this->defaultSortingField = 'ordering';
}
Meaning, first comment line must start with /** (two stars) and function cannot be private. Or at least this is how it works for me, hopefully it helps someone else