欢迎各位兄弟 发布技术文章
这里的技术是共享的
After dealing with this issue for some time now, and experiencing it on our site, I believe I've found the root issue. For a complete method of how to reproduce the issue (somewhat fixed on our site since I am implementing the fixes I come across as I get them):
http://www.ubercart.org/forum/bug_reports/8693/anonymous_user_add_cart_d...
When an Add to Cart button is pressed, the submit handler calls uc_cart_get_id() which retrieves the $_SESSION['uc_cart_id'] for the current user. Unfortunately, when a user is Anonymous, Drupal doesn't seem to allow the cookie to be set, even if you have Normal core caching disabled. It seems that this cookie (containing the $_SESSION['uc_cart_id'] variable) needs to be set before the add_to_cart_submit handler is called by an Add to Cart button. (Oddly enough, Buy it Now buttons and Cart Links are not affected here, at least, this was the case in my testing.)
The solution is really simply: I added a function uc_cart_boot(), which is run even on cached pages, that simply calls uc_cart_get_id(). Since this gets called on every page load (even on cached pages) the cookie is retrieved if it exists, or set if it does not. I didn't notice any additional overhead, and it gives us the benefit of allowing all anonymous users to start with a cookie containing an empty cart that uc_cart.module can now act upon.
<?php
/**
* Implementation of hook_boot().
*/
function uc_cart_boot() {
if (!isset($_SESSION['uc_cart_id'])) {
uc_cart_get_id();
}
}
?>
Caveat: Boost.module still has trouble, because it serves - via .htaccess rules - static .html pages from Apache, and so hook_boot() is never invoked. See my comment #7 here: #586210: Set session cookie
... which is essentially the issue that needs to be resolved in order to get Anonymous Add to Cart buttons working with Boost. My suggestion is to include some client-side JS that calls a non-cached AJAX callback. This AJAX callback would run the uc_cart_get_id() function, thereby allowing the anonymous user to set a cookie despite being on a static page. (It's still a theory, I have yet to test it out. Will post back here once I've done so.)
Comment | File | Size | Author |
---|---|---|---|
#80 | boost-fix_exit-679422.patch | 1.42 KB | chadcrew |
#61 | boost-679422.patch | 2.59 KB | mikeytown2 |
#60 | boost-679422.patch | 2.59 KB | mikeytown2 |
#33 | uc_cart.ajax_.js_.txt | 95 bytes | jeremy.zerr |
#6 | uc_cart.anon_.patch | 2.04 KB | torgosPizza |
Title: | Anonymous users "Add to Cart" does not work - cookie not being set (fix included) | Anonymous users "Add to Cart" does not work - session var not being set (fix included) |
subscribe
@torgosPizza
Great work on this - was having the exact same issue.
Can you please confirm, to implement this patch I need:
1. The patch form #6 plus
2. The JS from #4
Thanks,
I'm having this same issue, but only on my live server and not the development one for some odd reason. This patch doesn't seem to solve the problem for me, anything else that has to be changed (settings?) besides just patching the files?
Currently I'm not using any cache on either the dev server or the live server, our site has been changing so much lately that i found it counter productive to enable it just yet.
The dev server is running Debian while the live server is Red Hat Enterprise, but the actual services are the same, php5, mysql and apache2, config files for apache all seem to be the same for those particular sites, and general config is also the same.
I also noticed (using firebug/webdeveloper toolbar) that the cookies ARE being set for the anonymous users, ubercart just seems to be ignoring them, or not finding what it needs in them.
Unfortunately I don't have a public site to share, the site itself is public but the cart itself doesn't have any public facing navigation as it's for internal use only.
Seems that turning on 'normal' cache in drupal makes it function as expected, however it's still odd that with caching off, it works fine on the development server.
Ok, so now the problem has come back and now anonymous users can't add anything to a cart regardless of cache settings on the live server. Only thing I changed was enabling the cart_links module and adding products.
www.descentpass.com/shop and /cart to see for yourself, the links aren't public anywhere on the page but going directly to them works.
ETA: also, I'd be willing to test some if I knew what to do, and where. Live server or not, I can't continue unless I get this working :)
Ok, putting that line in the uc_cart_menu function didn't seem to do anything at all, putting it in uc_cart_view() (uc_cart.pages.inc) however produces a message on each page.
On the test server I get
Array ( [uc_cart_id] => 89e776eca7a2356e096a3f442a198758 )
There are no products in your shopping cart.
On the live server I do get a uc_cart_id, every time i hit the /cart page, but nothing else
I just noticed that if I simply stay on the /cart page and hit refresh a few times, the session id changes every time. Isn't that supposed to stay the same until the session expires?
Doing the same thing on the test server return the SAME session id every time, so it seems for some reason the live server is dropping the sessions
Yes, D6. No caching modules. The base url and cookie domain are set in the settings.php, and it's a dedicated server with multiple sites/domains on it (dev server is setup the same way).
At one point I did try commenting out the cookie domain just to see, and it didn't seem to have any affect.
$base_url = 'http://www.descentpass.com';
$cookie_domain = 'www.descentpass.com';
Perhaps I put the code in the wrong spot?
function uc_cart_menu() {
drupal_set_message(print_r($_SESSION, true));
$items = array();
*snip*
Oddly enough, uid 0 was missing from the live server. I've never transfered user data between the sites, so that's a little strange.
Either way, that seems to have fixed it (hopefully for good this time)
Thanks for all the help on this :)
Hi, I'm trying to get it to work but without success.
The website: www.musikzoneshop.com
If anyone want to give it a try... thanks!
Hi, thanks for the fast reply!!!
The cart is at www.musikzoneshop.com/cart
I'm sorry if it is not related... I actually didnt notice! I was searching for a solution to display the ajax_cart with boost module in the proper way.
I have no overriding functions on my theme or other modules enabled... at least that I know.
Right now my problem is that the cart block is cached in each page in different ways. Isn't this the right post to discuss about it?
Thank you
P
I haven't read all the posts here, but just to say that in my case it was 100% Boost that was the problem; turning off Boost caching solved the issues immediately. Turning it on and off and testing confirmed that Boost was the culprit. Interested in any solution as I'd like to use Boost but at least we are back on track for launch!
Status | File | Size |
---|---|---|
new | uc_cart.ajax_.js_.txt | 95 bytes |
I have been experiencing this same problem, here is what I expeirenced on UC 2.2. I found that I also had a bad uid 0 in my users table, the anonymous user had been given the uid of 40, likely due to export/import of db during development.
1) With bad uid 0 and no #6/#4 fix:
Caching enabled: anonymous cart says "your shopping cart", and couldn't add items to cart
No caching enabled: anonymous cart says # and cost of items, but couldn't add items to cart
2) Fixed uid 0 and no #6/#4 fix
Caching enabled: anonymous cart says "your shopping cart", but can add items to cart and see by visiting the "cart" URL directly
No caching enabled: anonymous cart says # and cost of items, and can add items to cart, can see them on cart page, and it updates the cart # and cost
3) Left uid 0 broken and applied #6/#4 fix
Same results as 1)
4) Fixed uid 0 and applied #6/#4 fix
Same results as 2)
So I really have no idea what the combination of #6/#4 is supposed to help because in my case, it didn't do a thing. Really the root of the problem was the uid 0 being wrong.
I checked on quite a few of my sites, and all sites that I did a export/import db to create the site had uid 0 messed up. Only a site that I installed through the Drupal installer had uid 0 correct. So really, I think this is the root cause of the issue for me. However, the situation still is not ideal in my mind because in state 2), with caching enabled, the cart still says "your shopping cart". By the way, the uid fix was easy, my anonymous uid had been changed to 40 (just look at the first row of the users table), so I did UPDATE users SET uid = 0 WHERE uid = 40.
Also, I made sure I was investigating the returns of the AJAX call inserted by the #6/#4 fix, and it returned the UID for a logged in person and a session ID for an anonymous person.
On a side note, the code in the javascript file from #4 will not work in all cases because it assumes the cart is at "/cart", so if you have a Drupal site with a root of http://www.example.com/drupal, then it looks at http://www.example.com/cart when it instead needs to look at http://www.example.com/drupal/cart. This problem can be worked around by using the Drupal global javascript var Drupal.settings.basePath. See attached uc_cart.ajax.js replacement.
Jeremy Zerr - http://www.zerrtech.com
Minor changes to the jQuery in #33:
1) We don't have to wait for the DOM to load, so I removed the $(document).ready().
2) I changed the $.get() to $.post() so the Ajax request wouldn't be subject to caching on the client side. And it's more proper semantically as well, since you're using it to initiate a session on the server. I don't know where you came up with the ?nocache=1 suffix, but since that suffix is static you'll always be requesting the same URL, and it won't have the effect you desire. You could have used ? followed by a random number or a unique string (a timestamp, for instance), but "nocache" isn't a key word or anything ...
3) As per Drupal recommendations (http://drupal.org/update/modules/6/7#javascript_compatibility), I put the definition of $ into local scope so the jQuery code doesn't conflict with other JavaScript libraries.
I think this should go into uc_cart.js - no sense loading yet another script separately from the page. I'm just going to inline my version of the script here, because this issue still needs a proper patch to address all the points:
(function($){ $.post(Drupal.settings.basePath + 'cart/ajax/id'); })(jQuery);
nocache is for boost; if it sees that it will not cache it. If using a post it won't cache it so safe to remove in this case.
Which file would it be where you would add this code?
/**
* Implementation of hook_boot().
*/
function uc_cart_boot() {
if (!isset($_SESSION['uc_cart_id'])) {
uc_cart_get_id();
}
}
Thanks in advance.
uc_cart.* file
http://api.drupal.org/api/function/hook_boot/6
I'm always open to new additions to the boost module; it's extremely rare for me to fully turn down a boost patch (I don't think I ever have).
Ok, it seems this is working for me on Drupal:
1. I added this code for the 'else' of uc_cart_menu because there's no hook_boot on Drupal 5:
else {
if (!isset($_SESSION['uc_cart_id'])) {
$cart_id = uc_cart_get_id();
}
}
2. I noticed Ubercart for Drupal 5 has a slighly different uc_cart_get_id and there wasn't a $_SESSION['uc_cart_id'] set so I replaced uc_cart_get_id which was like this on Ubercart for Drupal 5:
function uc_cart_get_id() {
global $user;
if ($user->uid) {
return $user->uid;
}
elseif ($sid = session_id()) {
return $sid;
}
// What to do if neither of these work? -RS
}
with this, that is the version from Ubercart for Drupal 6:
function uc_cart_get_id() {
global $user;
if ($user->uid) {
return $user->uid;
}
elseif (!isset($_SESSION['uc_cart_id'])) {
$_SESSION['uc_cart_id'] = md5(uniqid(rand(), TRUE));
}
return $_SESSION['uc_cart_id'];
}
I've tested and so far the 'no products in shopping cart' problem seem to be gone.
Thanks torgosPizza for your great research into this problem!
After I've found this page, applied the patch without any effect, I've found out that this could be my issue: #391534: Fails to add to cart when using views global:random display Maybe this info could help someone as well.
I am also having this problem with anonymous users getting "There are no products in your shopping cart.".
Where do i find the file i need to edit the user id and change it to 0?
Can i just copy the "uc_cart.anon_.patch" and "uc_cart.ajax_.js" files to this directory sites/all/modules/ubercart/uc_cart ??
Thanks
Tristan
I've found the anonymous cart to work just fine without Boost. As far as I can see, the cart id is generated when it needs to be, when something is added to the cart.
I don't see a problem with making an AJAX callback to generate a session id for Boost, I guess, but I don't see a need for the hook_boot(). I also don't know about setting the session cart id in uc_cart_user_login_form_submit(). Once the user is logged in, I'm not sure that variable is even used anymore. But it does make a good sanity check regardless.
I guess I feel like I need more information, because it doesn't seem like a problem in general. If there's another situation besides using Boost that causes the cart to not work, then we can do something to fix it.
hmm so aborting the rest of the hook_exit calls causes bad things to happen... This is in here as a hack for drupal_goto for the most part. Is there a hook_exit in ubercart?
looking at drupal_goto() it gets its $url from url(). This can be modded by custom_url_rewrite_outbound() or language_url_rewrite(). I could try one and then the other before reverting to the fallback of the exit call.
@torgosPizza
Try it when ?destination=
is set. This is what the hook_exit code is for.
EDIT: Well technically its here to add in nocache=1 so the next page after a POST is not cached. Since I have the detection of drupal_set_message this code is not as critical as it used to be... I think there could be a better way to do this.
what about a drupal_set_message call? does uid=0 get the message then?
Can you also try calling this code in boost_exit
<?php
if (empty($user->uid) && ($messages = drupal_set_message())) {
// FIXME: call any remaining exit hooks since we're about to terminate?
uc_store_exit();
uc_cart_exit();
$query_parts = parse_url($destination);
?>
Status: | Active | » Needs review |
Status | File | Size |
---|---|---|
new | boost-679422.patch | 2.59 KB |
sounds like I should make this an option for now till I figure out the best way to fix it.
Status | File | Size |
---|---|---|
new | boost-679422.patch | 2.59 KB |
This has the correct default value
Status: | Needs review | » Fixed |
committed #61 to dev
http://drupal.org/cvs?commit=390574
exit is code that was inherited when I took over boost. The reason why its needed is logical. If you do a form submit and the landing page is cached then you want to add in a nocache=1 at the end so you skip the boost cache and get any kind of message back.
Example:
Submit something to "/form-process" via POST. form-process does its thing and does a "drupal_goto()" with a "drupal_set_message('thanks')". Without the altered location header then you get the cached front page because drupal_goto sets the location header again.header('Location: '. $url, TRUE, $http_response_code);
since the second parameter is replace the old header that we just set would be overwritten by this new one at the end of drupal_goto. http://php.net/header
Because of this, there is an exit call so the redirect with nocache=1 tacked on to the end stays put and doesn't get over written by the ending of drupal_goto.
This logic may very well be browser dependent. If it does the location redirect request with a POST then we are good & don't need the funky exit call in boost_exit. If the browser does a normal GET request with the new location then there is the potential for the message to not get through since the browser could get a cached page on the redirect. Let me know if you have any other questions on this subject.
Status: | Fixed | » Closed (fixed) |
Automatically closed -- issue fixed for 2 weeks with no activity.
Mikey, Torgos: Excellent stuff guys - highly appreciated!
Thanks for this great work.
Just want to represent here that there are several modules which use hook_exit. In the application I'm building right now, there's Devel, LDAP Integration, and Organic Groups, all of which use hook_exit. It would be worth exploring if Boost can find a different way to do what it's doing in hook_exit.
Status: | Closed (fixed) | » Active |
If I'm not mistaken, Boost is actually set to be lighter than all the other modules, so that it can buffer the output and write the cached files.
Marking as "active", based on these recent comments, and #60.
Plan A:
The 2 alts is to define language_url_rewrite OR custom_url_rewrite_outbound in order to add in the nocache=1 query string to the URL. If a site uses both then it would fallback to hook_exit.
Plan B:
Detect when a message is waiting to be sent for the user and set the drupal_uid cookie to something like -2. Once message is sent out then remove cookie.
Plan A is the quick fix & but it could get ugly in the details (only mod URL if called from drupal_goto). Plan B is IMHO the better long term solution. It would be a modified version of what pressflow does with lazy session loading. Except in this case it would be a configurable instead of a catch all; default config would be to disable the boost cache if user has a messages in the session table. Once message has been delivered then re-enable boost cache.
I'm tracking this issue as I work on adding Boost to an Ubercart site. A couple of thoughts:
1) Wouldn't adding cart and cart/* to the "Cache every page except the listed pages" option in Boost solve this issue also? You'd never want those pages cached anyway.
2) To fix this issue more universally for Boost, would it make sense for boost_exit to run module_invoke_all('exit'), with a static variable test at the top to avoid a loop? Boost's weight means that it should run first, so it seems like that would let it do what it needs but still give all other modules a chance to run their exit functions.
I'm new to Boost though, so please correct me if my thinking is off-base!
Best,
Chad
I exclude the cart path from getting cached.
http://drupalcode.org/project/boost.git/blob/refs/heads/6.x-1.x:/boost.m...
option 2 is something to think about. The hook exit no cache redirect was code I inherited; there are better ways of doing this. I'm thinking about a 30 second no cache cookie or something to that effect. See plan B above. Sad to say but this is sorta low on my priority list. Getting 1.19 out is higher on that list.
Plan B code would look like this & would go in hook_exit; might need to be placed higher up.
<?php
if (!empty($_SESSION['messages']) && empty($_COOKIE['DRUPAL_UID'])) {
// Set cookie if message needs to be sent out.
boost_set_cookie(-2);
}
elseif (empty($_SESSION['messages']) && isset($_COOKIE['DRUPAL_UID']) && $_COOKIE['DRUPAL_UID'] == -2) {
// Remove cookie.
boost_set_cookie(0, BOOST_TIME - 86400);
}
?>
Status | File | Size |
---|---|---|
new | boost-fix_exit-679422.patch | 1.42 KB |
Got it. Thanks for the clarification. In that case, the problem for ubercart isn't the missing hook_exit calls, it's the missing drupal_session_commit(). From drupal_goto:
// Even though session_write_close() is registered as a shutdown function,
// we need all session data written to the database before redirecting.
drupal_session_commit();
So here's what I'm adding to boost_exit to fix the issue:
// Do what drupal_goto() would do if we were to return to it:
$boost_exit_running = TRUE;
module_invoke_all('exit', $destination);
drupal_session_commit();
exit(header('Location: ' . $destination));
I've attached a patch versus the current stable release. (By the way, I'm currently testing on Pressflow 6 and Boost 6.x-1.18). This seems like a relatively clean way to fix Boost's current method.
As for plan B, at first glance (and if I understand what you're proposing) pausing caching for 30 seconds after a POST based on a cookie doesn't sound much cleaner to me. It's the time-based element that worries me -- if I have a problem downstream, now I have to worry about how far the user made it before caching came back on. Did they make it 2, 3, 4 more clicks? Reproducing downstream bugs could become a headache, no?
EDIT: sorry, my testing on Pressflow is throwing my patch off here. Standard D6 drupal_goto calls session_write_close(), but Pressflow calls drupal_session_commit(), due to it's lazy session handling. I think my solution still works, but it would need something like this to support both D6 and Pressflow 6:
if (function_exists('drupal_session_commit')) drupal_session_commit();
else session_write_close();
I think there might be another issue with boost_exit for ubercart:
Appending nocache=1 to the cart/checkout url can cause uc_cart's referrer check to fail. For example, enter an invalid coupon code (which causes a drupal_set_message call) on the checkout screen twice - on the second time, I lose all the info I've entered (shipping address, credit card, etc). Adding the following check seems to eliminate this issue:
if (stristr($destination, 'cart/checkout')) return;
nocache=1 isn't a good solution IMHO. Messing with the cookie is a better solution.
#79 PLAN B sounds promising
I'm now using boost together with Pressflow's Cookie cache bypass + I added following line to the .htaccess "boost skipping section":
RewriteCond %{HTTP_COOKIE} NO_CACHE [OR]
Subscribe.
One who want to know how it doesn't work - can try http://legrand1.budka.ru
confirming that torgosPizza's fix from #4/#6 works wonderfully for me patched against
uc-6.x-2.x-dev
pressflow 6.22
boost-6.x-1.8
does this have any chance of being committed or is it such an unique issue that we just patch and be done?
thank you all!
the patch on #6 and .js file from #4 did it for me
so yeah basically #6 is the fix
#61 worked for me (I upgraded to a 6.x-1.x dev snapshot of Boost).
#80 looks interesting, but I'm also using Pressflow and I don't fully understand the patch.
Note that in #80 drupal_session_commit() is available since Drupal 7.x, for Drupal 6.x use session_write_close() instead.
What is the latest word on this? There have been a lot of changes to the dev branch since most of this issue's discussions.
I'm using the latest version (6.x-1.9-beta1) with Pressflow, and anonymous visitors cannot add products to their cart. It works fine with Boost disabled.
Edit: Nevermind! Sorry, I just found the "Exit() inside of boost_exit" setting. Turning it off (as recommended) fixes the issue for me. Thanks!
#61 is in 6.x-1.19-beta1. You have to enable the option in the boost settings ("do not exist" or something like that).
I'd be tempted to backport the solution in 7.x-1.x, which is to set the boost cookie to skip the cache for a single request. It removes the need to have a "nocache=1". See: #1242416: Avoid caching Drupal messages
Category: | bug | » support |
Status: | Needs work | » Active |
I think there might be another issue with boost_exit for ubercart:Appending nocache=1 to the cart/checkout url can cause uc_cart's referrer check to fail. For example, enter an invalid coupon code (which causes a drupal_set_message call) on the checkout screen twice - on the second time, I lose all the info I've entered (shipping address, credit card, etc). Adding the following check seems to eliminate this issue:
Adding the following check seems to eliminate this issue
but I don't see a check attached (except for the user's signature).
Category: | support | » bug |
Status: | Active | » Needs work |
Please don't post spam on the issue queue.
The issue you describe in #96 is not related to the use of hook_exit(), please open a separate issue.
Using Pressflow, the issue seemed to be solved no need to patch.
However, IE 11 fails : nothing in cart.
This new MS version is lightning fast, that might be related...
来自 https://drupal.org/node/679422
AJAX functionality
Another thing I've just discovered; I created a function that is associated with an ajax menu callback:
<?php
function uc_cart_get_id_ajax() {
$cid = uc_cart_get_id();
return drupal_json($cid);
}
?>
.. so, for an anonymous user, we'd want to hit this callback (via the current testing URL of /cart/ajax/id) which would fire the uc_cart_get_id() function, thereby setting the cookie. Right? Wrong. Here's why:
<?php
/**
* Return the unique cart_id of the user.
*/
function uc_cart_get_id() {
global $user;
if ($user->uid) {
return $user->uid;
}
elseif (!isset($_SESSION['uc_cart_id'])) {
$_SESSION['uc_cart_id'] = md5(uniqid(rand(), TRUE));
}
return $_SESSION['uc_cart_id'];
}
?>
All this function does is return the uid of the current user if they are logged in. The problem is that it still does not set the session cookie if it's been deleted (or never existed, in the rare event that hook_boot() got missed). The solution is easy, add a condition to look for the session variable, and set it if it doesn't exist:
<?php
/**
* Return the unique cart_id of the user.
*/
function uc_cart_get_id() {
global $user;
if ($user->uid) {
if (!isset($_SESSION['uc_cart_id'])) {
$_SESSION['uc_cart_id'] = $user->uid;
return $user->uid;
}
}
elseif (!isset($_SESSION['uc_cart_id'])) {
$_SESSION['uc_cart_id'] = md5(uniqid(rand(), TRUE));
}
return $_SESSION['uc_cart_id'];
}
?>
Is there a reason why UC shouldn't be doing this? I think it never hurts to check, and by doing so, will open us up to more AJAX functions that won't need to set the cookie on their own.(See my comment further down: #679422-5: hook_exit() causes Ubercart "Add to Cart" to fail)