1

Background: I'm writing a component where you can add items to a cart. When the user isn't logged in, it uses a token to identify them, stored as a cookie. The cookie is stored in a view's controller.

Now when the user logs in, I want to take all of those temporary cart items and link them to the user's account. So I've decided to write an onUserAfterLogin plugin to do it when the user logs in.

The Problem

I can't access the token cookie from the plugin. I can still access it from the controller after the user has logged in, but not from the plugin. I confirmed this by running the following code in different contexts:

foreach ($_COOKIE as $key=>$val)
    echo $key.' is '.$val."<br>\n";

In the plugin it outputs none of my custom cookies, but in the component it does.

Why can't I access cookies from the plugin? Is this a design decision? Is there a "proper" way to do this?

I've searched around and the closest I've come is this question, where the asker seems to have the same problem as me. Unfortunately there aren't any working answers.

2
  • 1
    What event is your Plugin being triggered on? Perhaps you are firing the plugin too early and the Cookies aren't available yet.
    – Irata
    Apr 4, 2023 at 22:13
  • @Irata it's being triggered on the onUserAfterLogin event. I didn't think that there was any place where the cookies would not be available!
    – Obscerno
    Apr 4, 2023 at 23:42

2 Answers 2

1

I was able to see the $__COOKIE object through an IDE(PHPStorm) and by running your code snippet in a couple of the core plugins that are triggered by onUserAfterLogin to show $__COOKIE is properties are available to the plugins as designed.

In the file ..\plugins\user\joomla\joomla.php I added the following code to run immediately on the function being called.

public function onUserAfterLogin(array $options): void
{
    foreach ($_COOKIE as $key=>$val)
        echo $key.' is '.$val."<br>\n";

    die;

and the output was thus

XDEBUG_SESSION is XDEBUG_ECLIPSE
376cb7f524347ecfff7f779f6fc43200 is k7m06ln61nkf31iec3mt46elh3
joomla_user_state is logged_in
5dcf55e8c5c11e565dbd630d56638688 is r6k20feo1a7fmsei4phvabvqpm
1e43ae2d10d1aeaf0cd0c28173f07763 is 25p7k900hr7fe369p624cj3cn2

Without seeing the actual code around your test snippet to understand how you have implemented it I would assume that you have an issue with where the snippet is located in the plugin and that it isn't running or the test snippet is executing but because you are relying on echo statements you are not seeing the results due to later processing.

The die; is important in the above test as it makes sure you get to see the results of the echo statements so that will confirm it is running.

In a plugin that runs fairly early in the cycle of building a Joomla page you are not likely to see the result of the echo as later processing that builds what is sent to the browser has probably cleared any output from the early echo statements. The same test snippet in a component is more likely to get the echo output onto the page you see in the browser.

1
  • I just figured out what the problem is and self-answered; sorry about the timing. I've made it a community wiki so I won't collect rep for it. The issue was in how the values were being set; by default Joomla limits the path that the cookie is available on and that's why the plugin couldn't access cookies set by the component: joomla.stackexchange.com/a/32937/8455
    – Obscerno
    Apr 5, 2023 at 23:32
1

The problem is in how the cookie was set. If you try to set only the cookie's name and value like this, you fall back on a lot of default values:

$cookie = JFactory::getApplication()->input->cookie;
$cookies->set('cartToken', $token);

The set function accepts many more arguments than this. The following will work across the site:

$cookies->set('cartToken', $token, 0, '/');

Why does this work? This function accepts the following arguments (see the highlighted text):

  • $name - Name of the value to set.
  • $value - Value to assign to the input.
  • $expire - The time the cookie expires. This is a Unix timestamp so is in number of seconds since the epoch. In other words, you'll most likely set this with the time() function plus the number of seconds before you want it to expire. Or you might use mktime(). time()+606024*30 will set the cookie to expire in 30 days. If set to 0, or omitted, the cookie will expire at the end of the session (when the browser closes).
  • $path - The path on the server in which the cookie will be available on. If set to '/', the cookie will be available within the entire domain. If set to '/foo/', the cookie will only be available within the /foo/ directory and all sub-directories such as /foo/bar/ of domain. The default value is the current directory that the cookie is being set in.
  • $domain - The domain that the cookie is available to. To make the cookie available on all subdomains of example.com (including example.com itself) then you'd set it to '.example.com'. Although some browsers will accept cookies without the initial ., RFC 2109 requires it to be included. Setting the domain to 'www.example.com' or '.www.example.com' will make the cookie only available in the www subdomain.
  • $secure - Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client. When set to TRUE, the cookie will only be set if a secure connection exists. On the server-side, it's on the programmer to send this kind of cookie only on secure connection (e.g. with respect to $_SERVER["HTTPS"]).
  • $httpOnly - When TRUE the cookie will be made accessible only through the HTTP protocol. This means that the cookie won't be accessible by scripting languages, such as JavaScript. This setting can effectively help to reduce identity theft through XSS attacks (although it is not supported by all browsers).

I lifted this documentation from the set function definition in /libraries/src/Input/Cookie.php.

The cookie was being set in one of the component's controllers, with the path of that controller. The cookie was available because it was being accessed at the same path it was being set.

The plugin was running the code at a different path, so it did not have access. Using '/' as the path when setting the cookie makes it accessible across the site.

It looks like in future versions of Joomla you'll need to pass the other values in an options array, so this might not work in Joomla 4. But it works in the latest version of Joomla 3.

3
  • Good that you have found the cause and shared the resolution however you might consider creating a new question, rather than editing yuor question again, and moving your answer to the new question that better relates to your answer so that others in a similar situation might find your answer more readily. Your original question and my answer are about retrieving or accessing the cookie from a specific plugin while your actual issue appears to relate to incorrectly creating the cookies in one extension and not being able to find the cookie in other extension(s).
    – Irata
    Apr 6, 2023 at 1:07
  • @Irata I see your point, but I think the plugin context is going to be valuable for those searching for a solution, even if it didn't end up having to do with plugins. As a compromise I'll tweak it so the title is more broad so people don't rule it out, but keep the plugin references. I think this issue is more likely to trip people up in plugins as they load all over the site
    – Obscerno
    Apr 6, 2023 at 3:17
  • I found both answers very useful.
    – Zollie
    Apr 6, 2023 at 4:28

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.