18

I paid a programmer to make a shop basket script to work with Spreadshirt API. Everything is working perfectly, except that the basket keeps emptying itself. I think the session is lost at some point so the script creates another BasketId.

I tried to find if there was a specific reason it was happening, without any success... I can't reproduce the bug. It just happens randomly without any reason. Closing the browser, resetting apache or even the whole webserver won't provoke session lost.

I've got two different scripts working with cookies on the same domain and they don't have any problem (one is a cookie for the admin login session and the other cookie is to save the user's last viewed articles on the shop)

I tried all solutions found on google without any success : editing php.ini , forcing ini settings through php, tried the htaccess way, ...

Here's the "sessions" part of my phpinfo: http://gyazo.com/168e2144ddd9ee368a05754dfd463021

shop-ajax.php (session handling @ line 18)

ini_set('session.cookie_domain', '.mywebsite.com' );
header("Pragma: no-cache");
header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate");
$language = addslashes($_GET['l']);
$shopid = addslashes($_GET['shop']);


// if($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {
//  die("no direct access allowed");
// }



if(!session_id()) {
  $lifetime=60 * 60 * 24 * 365;
  $domain = ".mywebsite.com";
   session_set_cookie_params($lifetime,"/",$domain);
    @session_start();
}





// Configuration
$config['ShopSource'] = "com";
$config['ShopId'] = $shopid;
$config['ShopKey'] = "*****";
$config['ShopSecret'] = "*****";



/*
 * add an article to the basket
*/
if (isset($_POST['size']) && isset($_POST['appearance']) && isset($_POST['quantity'])) {
    /*
     * create an new basket if not exist
    */
    if (!isset($_SESSION['basketUrl'])) {
        /*
         * get shop xml
        */
        $stringApiUrl = 'http://api.spreadshirt.'.$config['ShopSource'].'/api/v1/shops/' . $config['ShopId'];
        $stringXmlShop = oldHttpRequest($stringApiUrl, null, 'GET');
        if ($stringXmlShop[0]!='<') die($stringXmlShop);
        $objShop = new SimpleXmlElement($stringXmlShop);
        if (!is_object($objShop)) die('Basket not loaded');

        /*
         * create the basket
        */
        $namespaces = $objShop->getNamespaces(true);
        $basketUrl = createBasket('net', $objShop, $namespaces);
        $_SESSION['basketUrl'] = $basketUrl;
        $_SESSION['namespaces'] = $namespaces;

        /*
         * get the checkout url
        */
        $checkoutUrl = checkout($_SESSION['basketUrl'], $_SESSION['namespaces']);

        // basket language workaround
        if ($language=="fr") {
            if (!strstr($checkoutUrl,'/fr')) {
                $checkoutUrl = str_replace("spreadshirt.com","spreadshirt.com/fr",$checkoutUrl);
            }
        }

        $_SESSION['checkoutUrl'] = $checkoutUrl;

    }



    /*
    Workaround for not having the appearance id :(
    */
    if ($_POST['appearance']==0) {
        $stringApiArticleUrl = 'http://api.spreadshirt.'.$config['ShopSource'].'/api/v1/shops/' . $config['ShopId'].'/articles/'.intval($_POST['article']).'?fullData=true';
        $stringXmlArticle = oldHttpRequest($stringApiArticleUrl, null, 'GET');
        if ($stringXmlArticle[0]!='<') die($stringXmlArticle);
        $objArticleShop = new SimpleXmlElement($stringXmlArticle);
        if (!is_object($objArticleShop)) die('Article not loaded');
        $_POST['appearance'] = intval($objArticleShop->product->appearance['id']);
    }


    /*
     * article data to be sent to the basket resource
    */
    $data = array(

            'articleId' => intval($_POST['article']),
            'size' => intval($_POST['size']),
            'appearance' => intval($_POST['appearance']),
            'quantity' => intval($_POST['quantity']),
            'shopId' => $config['ShopId']

    );

    /*
     * add to basket
    */
    addBasketItem($_SESSION['basketUrl'] , $_SESSION['namespaces'] , $data);

    $basketData = prepareBasket();


    echo json_encode(array("c" => array("u" => $_SESSION['checkoutUrl'],"q" => $basketData[0],"l" => $basketData[1])));
}




// no call, just read basket if not empty
if (isset($_GET['basket'])) {
    if (array_key_exists('basketUrl',$_SESSION) && !empty($_SESSION['basketUrl'])) {

        $basketData = prepareBasket();

        echo json_encode(array("c" => array("u" => $_SESSION['checkoutUrl'],"q" => $basketData[0],"l" => $basketData[1])));
    } else {
        echo json_encode(array("c" => array("u" => "","q" => 0,"l" => "")));
    }
}






function prepareBasket() {

    $intInBasket=0;

    if (isset($_SESSION['basketUrl'])) {
        $basketItems=getBasket($_SESSION['basketUrl']);

        if(!empty($basketItems)) {
            foreach($basketItems->basketItems->basketItem as $item) {
                $intInBasket += $item->quantity;
            }
        }
    }

    $l = "";
    $pQ = parse_url($_SESSION['checkoutUrl']);
    if (preg_match("#^basketId\=([0-9a-f\-])*$#i", $pQ['query'])) {
        $l = $pQ['query'];
    }

    return array($intInBasket,$l);
}







// Additional functions
function addBasketItem($basketUrl, $namespaces, $data) {
    global $config;

    $basketItemsUrl = $basketUrl . "/items";

    $basketItem = new SimpleXmlElement('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
            <basketItem xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://api.spreadshirt.net">
            <quantity>' . $data['quantity'] . '</quantity>
            <element id="' . $data['articleId'] . '" type="sprd:article" xlink:href="http://api.spreadshirt.'.$config['ShopSource'].'/api/v1/shops/' . $data['shopId'] . '/articles/' . $data['articleId'] . '">
            <properties>
            <property key="appearance">' . $data['appearance'] . '</property>
            <property key="size">' . $data['size'] . '</property>
            </properties>
            </element>
            <links>
            <link type="edit" xlink:href="http://' . $data['shopId'] .'.spreadshirt.' .$config['ShopSource'].'/-A' . $data['articleId'] . '"/>
            <link type="continueShopping" xlink:href="http://' . $data['shopId'].'.spreadshirt.'.$config['ShopSource'].'"/>
            </links>
            </basketItem>');

    $header = array();
    $header[] = createAuthHeader("POST", $basketItemsUrl);
    $header[] = "Content-Type: application/xml";
    $result = oldHttpRequest($basketItemsUrl, $header, 'POST', $basketItem->asXML());
}



function createBasket($platform, $shop, $namespaces) {

    $basket = new SimpleXmlElement('<basket xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://api.spreadshirt.net">
            <shop id="' . $shop['id'] . '"/>
            </basket>');

    $attributes = $shop->baskets->attributes($namespaces['xlink']);
    $basketsUrl = $attributes->href;
    $header = array();
    $header[] = createAuthHeader("POST", $basketsUrl);
    $header[] = "Content-Type: application/xml";
    $result = oldHttpRequest($basketsUrl, $header, 'POST', $basket->asXML());
    $basketUrl = parseHttpHeaders($result, "Location");

    return $basketUrl;

}






function checkout($basketUrl, $namespaces) {

    $basketCheckoutUrl = $basketUrl . "/checkout";
    $header = array();
    $header[] = createAuthHeader("GET", $basketCheckoutUrl);
    $header[] = "Content-Type: application/xml";
    $result = oldHttpRequest($basketCheckoutUrl, $header, 'GET');
    $checkoutRef = new SimpleXMLElement($result);
    $refAttributes = $checkoutRef->attributes($namespaces['xlink']);
    $checkoutUrl = (string)$refAttributes->href;

    return $checkoutUrl;

}

/*
 * functions to build headers
*/
function createAuthHeader($method, $url) {
    global $config;

    $time = time() *1000;
    $data = "$method $url $time";
    $sig = sha1("$data ".$config['ShopSecret']);

    return "Authorization: SprdAuth apiKey=\"".$config['ShopKey']."\", data=\"$data\", sig=\"$sig\"";

}


function parseHttpHeaders($header, $headername) {

    $retVal = array();
    $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));

    foreach($fields as $field) {

        if (preg_match('/(' . $headername . '): (.+)/m', $field, $match)) {
            return $match[2];
        }

    }

    return $retVal;

}

function getBasket($basketUrl) {

    $header = array();
    $basket = "";

    if (!empty($basketUrl)) {
        $header[] = createAuthHeader("GET", $basketUrl);
        $header[] = "Content-Type: application/xml";
        $result = oldHttpRequest($basketUrl, $header, 'GET');
        $basket = new SimpleXMLElement($result);
    }

    return $basket;

}




function oldHttpRequest($url, $header = null, $method = 'GET', $data = null, $len = null) {

    switch ($method) {

        case 'GET':

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, false);

            if (!is_null($header)) curl_setopt($ch, CURLOPT_HTTPHEADER, $header);

            break;

        case 'POST':

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
            curl_setopt($ch, CURLOPT_POST, true); //not createBasket but addBasketItem
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

            break;

    }

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;

}
?>

There's also 2 other parts of the script : a form to add a sample tshirt to the basket (example.php) and a script to call the ajax (shop-controller.js). Can post it if needed but there's no session handling stuff.

update - Maybe the problem is not related to sessions. The BasketId is lost, but PHPSESSID stays the same in the browser cookies.

I did the following tests for the last 3 days (tested with diferent computers and browsers):

  • Empty browser cookies then start a new session during the afternoon

  • Add 1 item to basket, i write down the BasketId and check the browsers cookies to write down the PHPSESSID

  • Usually always around midnight, the basket empty itself

  • PHPSESSID stays the same in my browser cookies, even after basket empty itself

  • However the BASKETID is not the same, the one used during the afternoon is lost and a new one is regenerated

Server is CentOS 5.9 - PHP Version 5.2.9 (from OVH). Dedicated server on a dedicated IP.

Bruce
  • 1,647
  • 4
  • 20
  • 22
libertaire
  • 855
  • 3
  • 13
  • 33
  • Have you tried to set the `session.auto_start` to `on` in the `php.ini`? – tftd Jun 21 '15 at 18:43
  • @tftd He is using `session_set_cookie_params()`, which needs to be called before `session_start()` is called, so perhaps it's not an option, besides it has a few downsides (examples [here](http://stackoverflow.com/questions/11498486/is-there-a-downside-to-session-auto-start-in-php) and [here](http://stackoverflow.com/questions/8257083/is-setting-php-inis-session-auto-start-to-1-considered-bad-practice)). – Marcos Dimitrio Jun 21 '15 at 19:05
  • That's correct. I only suggested it, because we don't have the full code here - only a portion of an ajax script that's being called god knows when. The developer might have forgotten to set it somewhere in one of the scripts, which is why I suggested it. – tftd Jun 21 '15 at 20:23
  • Where is the server being hosted? If shared host, which one? If self-hosted, where is it and are there any crons on the server? If a vm on a cloud (aws/rackspace/etc), which one, and are there any crons? I'm just wondering if there is something else going on with the server which has nothing to do with the php code or sessions. – cegfault Jun 21 '15 at 21:42
  • I posted the 2 other files involved with this script here: http://board.phpbuilder.com/showthread.php?10393721-php-session-is-randomly-lost-and-cant-understand-why&p=11048549&viewfull=1#post11048549 – libertaire Jun 22 '15 at 05:15
  • Server is a dedicated server on a dedicated IP with no other websites running cookies. I am not aware of any crons messing with sessions, and ive got 2 other scripts using sessions that works good on the same domain – libertaire Jun 22 '15 at 05:16
  • Can you exec this command on you server? **df -m /tmp** it's show the free space where you save your sessions – Ivan Buttinoni Jun 27 '15 at 18:14

4 Answers4

17

First you need to find if the problem is in session's garbage collection or a logical error within the code. For that, you can:

// Add this right after session_start()
if (!isset($_SESSION['mySessionCheck'])) {
    $_SESSION['mySessionCheck'] = "This session (" . session_id() . ") started " . date("Y-m-d H:i:s");
}

// For HTML pages, add this:
echo '<!-- ' . $_SESSION['mySessionCheck'] . ' -->';

// For AJAX pages, add "mySessionCheck" to the JSON response:
echo json_encode(
    array(
        "c" => array(
            "u" => $_SESSION['checkoutUrl'],
            "q" => $basketData[0],
            "l" => $basketData[1]
        ),
        "mySessionCheck" => $_SESSION['mySessionCheck']
    )
);

If this message changes at the same time the basket empties, then you'll know for sure it's a problem with PHP sessions.

In that case, there are a few things you can try:

1) You are doing

$lifetime=60 * 60 * 24 * 365;
$domain = ".mywebsite.com";
session_set_cookie_params($lifetime,"/",$domain);
@session_start();

But according to a user contributed note from PHP.net docs:

PHP's Session Control does not handle session lifetimes correctly when using session_set_cookie_params().

So you may try using setcookie() instead:

$lifetime=60 * 60 * 24 * 365;
session_start();
setcookie(session_name(),session_id(),time()+$lifetime);

Even though it's a 4 year old note as pointed in the comments, I tested it and it still happens (I'm on PHP 5.5.7, Windows Server 2008, IIS/7.5). Only setcookie() produced the HTTP headers to change the expiring date (example setting $lifetime to 600):

Set-Cookie: PHPSESSID=(the id); expires=Mon, 22-Jun-2015 15:03:17 GMT; Max-Age=600

2) If you're using a Debian servers or some derivative, they use a cron job to clear out PHP sessions, so you might try:

3) To find out if there is some process clearing your sessions, you can place a watch on the directory where the session files are stored (actual path varies from server to server, use session_save_path to find out the location on yours). I'm no server admin, but I've read you can use auditctl for that, just make sure you log who made the changes to your files.

4) If you don't have access to server configuration, or don't want to depend on server config (good if you switch hosts), you can implement your own session handler. Check out this example by Pedro Gimeno.

Marcos Dimitrio
  • 6,651
  • 5
  • 38
  • 62
  • That user contributed note is four years old, and I doubt what’s said there still holds true … if it indeed ever did; I never experienced any such problems with PHP updating the session cookie lifetime on it’s own. – CBroe Jun 21 '15 at 19:06
  • 1
    @CBroe Agreed, that's an old comment from PHP docs. Since OP never said what version of PHP he is running, I'll wait until he tries it and if he doesn't succeed, I'd gladly review my answer to improve it or delete it. – Marcos Dimitrio Jun 21 '15 at 19:09
  • Server is CentOS 5.9 - PHP Version 5.2.9 – libertaire Jun 22 '15 at 05:22
  • see the update on my first post, it seems that the session isn't lost if i check in the browser cookies, but the basket still empty itself – libertaire Jun 22 '15 at 05:27
  • if session is lost or getting regenerated randomly, then should not user's log-in state too would not be lost ? I mean, if basket id is being generated due to session loss or unset, then how that user remains in log-in state ? – joy d Jun 22 '15 at 11:25
  • I would : Add debug statements like printing content of session and user's to a file inside that IF (if (!isset($_SESSION['basketUrl'])) {) block and try to find a patter at what point of time or situation this occurs. – joy d Jun 22 '15 at 11:26
  • I've updated my answer. If you do the checking I proposed, you'll know for sure where the problem is. – Marcos Dimitrio Jun 22 '15 at 15:12
  • Same result using setcookie(). Trying your updated answer now. I'll tell you at midnight if i lost the session – libertaire Jun 22 '15 at 18:57
  • so the basket emptied itself at midnight again, and the session changed in your script. First session "This session (b5be349527851c8091df5d4291cb7b9e) started 2015-06-22 18:31:09" Then at midnight it changed to "This session (b5be349527851c8091df5d4291cb7b9e) started 2015-06-23 05:10:03" – libertaire Jun 23 '15 at 05:12
  • The next step would be to inspect the cron jobs that run on the server. Do you have access to them? Also, do you have access where PHP store session files? You should monitor that as well, at midnight. – Marcos Dimitrio Jun 23 '15 at 20:57
  • following the recommendations in your previous link, i already changed the session path to something i can access through FTP, so yes i see the session files – libertaire Jun 24 '15 at 07:40
  • its a dedicated server, i have access to everything but i am a total noob with all the linux stuff.. i know nothing about the cron jobs – libertaire Jun 24 '15 at 07:42
  • What i don't understand is that all the other scripts running on the server have absolutly zero problem with session stuff. There's an admin cookie on the same domain, but the server is also hosting many other smaller websites on other domains/ip but same server (among others, 3 vbulletin forums plus 2 phpbb forums running a lot of sessions and cookies without any problems). Why all those sessions doesnt get lost ? – libertaire Jun 24 '15 at 07:44
  • From the picture above, you can see that all files were modified sometime around midnight. What do they look like now? Try to identify your own session file, add something to the basket, but **don't access it anymore**, just watch if the file is modified at night. Also, ask your sysadmin about any cron jobs running, ask for a list and a description of what each does. You can also ask if they can put a watch on that directory for you, and report the results. – Marcos Dimitrio Jun 24 '15 at 16:00
  • Did you have any progress? – Marcos Dimitrio Jun 26 '15 at 10:49
  • changing the session path seemed to fix the issue, i havent lost session since then – libertaire Jul 03 '15 at 08:39
2

You put only @session_start(); in the top of your all script.

An also put in the top of your ajax script.

Example Like following:

@session_start();
// you may use session script here or header file
include("header.php");
//some code. you may use session script here or header file

include("main.php");
//-----------next code
Ahosan Karim Asik
  • 3,219
  • 1
  • 18
  • 27
0

I post here, even if is an old post, in case someone experience this problem, check in php.ini session.gc_maxlifetime, or print ini_get('session.gc_maxlifetime'); you have to set it in your php script or php.ini, on my php version the default is 1440 seconds, I have changed it to 1 month, is enough in my case. Also after start session you can setcookie(session_name(),session_id(),time() + $sessionLifetime, "", "", false, true); I hope this helps.

0

I my case, I replaced session_destroy(); with session_unset(); and problem was solved.

s.abbaasi
  • 952
  • 6
  • 14