3

I am developing a component and I use ajax calls to do some tasks in the frontend. I have come to a problem when the session is expires and I don't want to use keepalive to solve it.

In the frontend I have a list view. And there there are some buttons that perform tasks using ajax requests.

For example :

$('#resetData').on('click', function (e) {
  e.preventDefault();
  var token = jQuery("#token").attr("name");
  var itemId = $(this).attr('data-id');
$.ajax({
  method: 'POST',
  cache: false,
  dataType: "json",
  data: { [token]: "1", id: itemId},
  url: 'index.php?option=com_contract&task=contract.resetData'
}).success(function (r) {
  console.log('Response :');
  console.log(r);
  if (r.responseText) {
    UIkit.modal.alert(r.responseText).then(function () {
      window.location.reload();
    });
  }
  if (r.message) {
    UIkit.modal.alert(r.message).then(function () {
      window.location.reload();
    });
  }
}).error(function (err) {
  console.log(err);
  UIkit.modal.alert('<div class="alert alert-error">' + err + '</div>').then(function () {
    return;
  });
}).complete(function (r) {
  console.log(r.responseText);
});

When the session has expired, in my main controller I redirect to login page. JFactory::getApplication()->redirect(JURI::base() . 'index.php?option=com_users&view=login', $error, 'error'); //redirect to login page

This means that the hole html is returned in the Ajax response and when I popup the response I get an [object object] instead of a human readable error.

I guess I need to find a way to check if the session has expired when click event fires and alert an error, before I make the Ajax call.

Is it possible to check the JSESSION in javascript or any other way to solve it ?

3 Answers 3

3

One way I can imagine to check the session is before you ignite the above ajax call, you make a previous ajax call to another function in com_contract/controller(task = your new session checking function. so: url:...com_contract/task=checkSession) where you create a function, something like:

public function checkSession() 
{
  try
    {    
      $session1 = JFactory::getSession();
      $isActive = $session1->isActive();
      $sessionState = $session1->getState();
      echo new JResponseJson($isActive, $sessionState);
    }
  catch(Exception $e)
    {
      echo new JResponseJson($e);
    }
}

I just created this function here fast, so mistakes might be in it and also can be improved with anything more but this is how I would start to check the session via ajax.

The other implication would be according to me, that if a session is expired then the token might be invalid during this call. I did not try that, so it has to be tested, whether the ajax call can be sent with the token (from an expired session. it does not sound too logical).

Exactly because of this reason (expired tokens) I suggested earlier to keep the session alive, instead of other solutions on sessions.

FEW ADDITIONAL THOUGHTS ON THIS SUBJECT (since this subject will come up many times here)

If you try to make an ajax call with an expired session token, then you will always get an 'invalid token' response, thus the ajax call will fail anyway, so it is better to do a redirect in javascript if the response is 'invalid token' because of expired session or because of the invalid response (simply a failed ajax call). Then the check session function could be extended with few lines, as an example, but probably it does not have valid purpose in this case:

public function checkSession() 
{
    if (! JSession::checkToken())
    {
        echo new JResponseJson('invalid token');
        // jexit( 'Invalid Token' );
    }
    else
    {
      try
        {    
          $session1 = JFactory::getSession();
          $isActive = $session1->isActive();
          $sessionState = $session1->getState();
          echo new JResponseJson($isActive, $sessionState);
        }
      catch(Exception $e)
        {
          echo new JResponseJson($e);
        }
    }
}

This I wanted to add to this subject. But without this relatively redundant function, simply any invalid token response should ignite redirect in the javascript, so checking session state is not really needed here. At least I at the moment do not see the reason for that.

ANOTHER IMPORTANT POINT REGARDING AJAX CALLS

Because of the above theory and reasons, the solution of this problem is only 1 ajax call, not two (due to the possible complications of javascript promises structure). As the problem was mentioned in the comments. Which means that the token check simply has to be put in your original controller function(resetData). So you do not have to use another function to check the token. We just go back to basics here. You original Joomla function should start with the token check of course and if it does not fail on invalid token then it could be completed.

public function resetData()
{
    if (! JSession::checkToken())
    {
        echo new JResponseJson('invalid token');
        die('my messages';
    }
    elseif ( JSession::checkToken())
    {

        // all of your resetData function steps

    }
}

Or still more simple as it is given in all of this kind of Joomla functions:

public function resetData()
{
    JSession::checkToken() or die('my messages');
    // then all of your resetData function steps...
    // what is giving back different responses
}

So this would be a right start of any Joomla function which is called with ajax.

7
  • Thank you for your time @Zollie. I tested your function and it works with a small addition. After the echo new JResponseJson must call die() or JFactory::getApplication()->close(); because it will return html instead of the JSON response. As you already said, only the first if is enough, if the session has expired then since the function is not called at all. I will post a new answer with the complete working code and comments for future searches if is ok with you.
    – mixahlos
    Commented Nov 19, 2018 at 20:19
  • I'm happy that it helped you in solving the problem. I also used jexit() after the invalid token jsonresponse (I just put it as a comment in the code), so I agree with you on this too, it should be closed this way or that way with a required response. :)
    – Zollie
    Commented Nov 19, 2018 at 20:37
  • After a lot of testing it is not working properly. This is because of the asynchronous nature of ajax. When making the ajax call to check the session, the rest of the code is still running. So the next ajax call is also being called and returns before the checkSession. This is happening in most of the times, this results the error message from the next ajax call (the original one) to be displayed before the alert or (reload in my case) if the session has expired, so this takes us back to the original problem.
    – mixahlos
    Commented Nov 20, 2018 at 3:38
  • I see your point and I do not want to start a long session here in comments :) , but you should restructure your javascript/jQuery script because of the nature of javascript. There are at least 3 different solutions on that problem in javascript. The second function should only be ignited if we get the first function' response.
    – Zollie
    Commented Nov 20, 2018 at 7:23
  • @mixahlos - I just added a short update to my answer in which I point out that you should only make 1 ajax call, not two actually. That way you can avoid the javascript promises implications easier maybe. I hope this can also help you to solve your issue on this, if you did not yet consider it yet.
    – Zollie
    Commented Nov 20, 2018 at 16:30
2

To get the session state, you can use the following:

JFactory::getSession()->getState();

I'm not sure if this script is inline or a separate file. If it's inline then you can simply add the following to your Javascript:

var sessionState = '<?php echo JFactory::getSession()->getState(); ?>';
if (sessionState === 'expired') {
    // redirect to login page
}

On a side note, the use of .success() and .error() are deprecated in jQuery. Please use .done() and .fail(). Also, just so you know, .then() will not work in IE11.

4
  • This looks like a more workable solution to this question, simpler and better than what I wrote few minutes after this... :) upvoted
    – Zollie
    Commented Nov 19, 2018 at 11:14
  • Actually this isnt a good solution as it doesnt check when the button is clicked. It checks on page load. Your anawer does the trick properly so I'll delete this
    – Lodder
    Commented Nov 19, 2018 at 11:22
  • From this approach you are right. But I don't know why, I just liked the simple approach of your answer. I would leave that here any way, it's still very good for reference in other session checking cases for later researchers.
    – Zollie
    Commented Nov 19, 2018 at 11:35
  • It would be great if it was working. Really clean simple with just a few lines of code. Unfortunately as already said is not working in the click listener. Thank you
    – mixahlos
    Commented Nov 19, 2018 at 20:28
2

well after the valuable help of @Zollie I will post the answer and working code to help others.

A sample of the template file :

    <form action="<?php echo JRoute::_('index.php?option=com_contract&view=contracts'); ?>" id="adminForm" method="post" name="adminForm">
      <?php if (!empty($this->items)) { ?>
        <div class="contractList items">
          <?php foreach ($this->items as $item) {?>
            <div class="contract item">
              <h3> <?php echo $item->title; ?></h3>
              <p> <?php echo $item->text; ?></p>
              <div class="actions">
                <a class="action" href="#" data-action="unpublish"><?php echo JText::_('COM_CONTRACT_UNPUBLISH_ITEM');?></a>
                <a class="action" href="#" data-action="resetData"><?php echo JText::_('COM_CONTRACT_RESTET_ITEM');?></a>
                    <input type="checkbox" class="uk-checkbox" name="cid[<?php echo $i; ?>]" id="cb<?php echo $i; ?>" value="<?php echo $item->id; ?>" onclick="Joomla.isChecked(this.checked);">
              </div>
           </div>
          <?php } ?>
        </div>
      <? } ?>
      <input type="hidden" name="task" value=""/>
      <?php echo '<input id="token" type="hidden" name="' . JSession::getFormToken() . '" value="1" />'; // We need the token this way in order to get it to the javascript. ?>
    </form>

The javascript

<script type="text/javascript">
  function checkSession(caller) {
    var token = jQuery("#token").attr("name");
    $.ajax({
      method: 'POST',
      dataType: "json",
      data: {[token]: "1"},
      url: 'index.php?option=com_contract&task=contracts.checkSession'
    }).done(function (r) {
      // Since we had a success call, means that the session is active so we can continue to call the actual task.
      var data = {};
      data.id = $(caller).closest("div.actions").find("input[type='checkbox']").val();
      data.action = $(caller).attr('data-action');
      ajaxSubmit('index.php?option=com_contract&task=contracts.' + data.action, token, data);
    }).fail(function (err) {
      // If there the call fails, means there is no active session.
      UIkit.modal.alert('<div class="alert alert-error">' + Joomla.JText._('SESSION_TIMEOUT') + '</div>').then(function () {
        window.location.reload(); // reload to get to the login page.
      });
    });
  }

  function ajaxSubmit(Url, token, data) {
    $.ajax({
      method: 'POST',
      dataType: "json",
      data: {[token]: "1", data},
      url: Url
    }).done(function (r) {
        window.location.reload();
      }
      if (r.message) {
        UIkit.modal.alert(r.message).then(function () {
          window.location.reload();
        });
      }
    }).fail(function (err) {
      UIkit.modal.alert('<div class="alert alert-error">' + err + '</div>').then(function () {
        return;
      });
    });
  }
$(document).ready(function () {
  $('.action').on('click', function (e) {
    e.preventDefault();
    checkSession(this);
  });
});
</script>

The controller's function.

In the checkSession function I realized that it doesn't matter what you return since if the function is called means that the the session is still active.

  public function checkSession() {
    if (!JSession::checkToken()) {
      echo new JResponseJson('invalid token');
      JFactory::getApplication()->close();
    }
  }

I have done numerous test and always works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.