Migration to Magento

Using our methodology and our Magento import scripts you will be able to successfully transfer customers data, categories data, products data from X-Cart to Magento platform.

Besides data migration process description we published here special Magento extension. Our extension will allow you and your customers to see X-Cart orders in your Magento admin and storefront. You can download the Magento extension and import scripts here.

Preparation for data transfer from X-Cart to Magento

Magento installation and configuration

First of all, you need to install Magento on your server, e.g. in some sub-folder.
Next download our archive with import scripts and Magento extension and upload it to the Magento root.

Export data from X-Cart

Next, execute the X-Cart Export feature and export the data. On this step you need to export only USERS, CATEGORIES and PRODUCTS:

X-Cart export fields

Upload the X-Cart "export.csv" file to the root Magento directory on your server.

Export SQL tables from X-Cart database

Additionally you need to export several database tables from X-Cart, you can use "mysqldump" utility to do this:

mysqldump -u %username% -p %database% --tables xcart_orders xcart_order_details xcart_images_D xcart_images_P xcart_images_T > dump.sql

Import the "dump.sql" file to the Magento database.

Copy X-Cart images to Magento

In order to transfer the products images, you need to copy the whole "/images" directory from X-Cart to Magento "/media" folder and rename it to "import" directory.

So that in the "/media" Magento directory you will have "import" directory with all X-Cart images.

Blowfish key

And last thing that you need from X-Cart is the "blowfish.php" file. This file is located in "include" directory of X-Cart, copy it to Magento root directory. Also, copy the blowfish key itself to the clipboard, you can find the key in the end of X-Cart's config.php file.

Note: Do not forget to remove the files you created in Magento root directory after data migration.

Data transfer from X-Cart to Magento

We will use Magento Import feature to import customers and products, and will create categories directly using 'catalog/category' model.

We created special parser (you can find the parser.php in our archive) which explode the data from single X-Cart export file to several files with corresponded data (in Magento style):

Here is the code of our parser.php:

$file = "export.csv";
$path = dirname(__FILE__) . DIRECTORY_SEPARATOR;

$fd = fopen($path . $file, "rb");
$delimiter = ";";
$found = false;
fgetcsv($fd);
$mag_fd = false;
while($row = fgetcsv($fd, 40960, $delimiter)) {
    if (count($row) == 1) {
        if ($mag_fd) {
            fclose($mag_fd);
            $mag_fd = false;
        }

        if ($row[0]) {
            $magento_file = "xcart_" . strtolower(trim($row[0], "[!]")) . ".csv";
            $mag_fd = fopen($path . $magento_file, "w+b");
        }
    } else {
        if (!$mag_fd ) {
            die("Cannot find file to write");
        }
        fputcsv($mag_fd, $row);
    }
}

fclose($fd);
if ($mag_fd) {
    fclose($mag_fd);
    $mag_fd = false;
}

The parser read all rows in "export.csv" file and create new file when new type of data starts. You can run the parser from the Unix shell using this command: "php parser.php".

Customers migration

There are several important things you should know before customers data importing.

  • X-Cart allow to have several customers with the same E-Mail and Magento do not allow it. That is why we will collect the data by unique E-mail.
  • X-Cart store guest customers in the same table (and in the export file too), and there is only one way to determinate quest - prefix of the login. We will not import customers which login starts from "anonymous", "guest " and "guest-". You can find your current prefixes in the X-Cart config.php file.
  • X-Cart store admins, providers and partners in the same table, so we will import usertype 'C' (customers) only.

Using the "customer.php" script from our archive you will be able to import customers data. The "customer.php" contain some X-Cart functions for customer password decryption.
You will need to paste your current X-Cart blowfish key to the customer.php script before script execution:

$blowfish_key = 'hereyourblowfishkey';

Also there are several variables which depends on your Magento store settings:

$default_group_id = 1;
$default_store_id = 1;
$default_website_id = 1;
$default_website = 'base';
$default_store = 'default';
$default_created_in = 'Default Store View';

We have only one store and only one customer group in our case.

If you wish to import customers to the different Magento customer group you will need to edit the "$default_group_id" variable. Also change "$default_store_id", "$default_website_id", "$default_website", "$default_store", "$default_created_in" with values of your Magento store view.

The script is working the following way:

It opens the X-Cart customers CSV file (created via parser.php) and prepare "magento_customer.csv" file which can be used with Magento Import Customers feature.

If you look at the source, you will see that it just contains mapping fields like:

$association_shipping = array(
    '!S_TITLE' => '_address_prefix',
    '!S_FIRSTNAME' => '_address_firstname',
    '!S_LASTNAME' => '_address_lastname',
    '!S_ADDRESS' => '_address_street',
    '!S_CITY' => '_address_city',
    '!S_COUNTRY' => '_address_country_id',
    '!S_ZIPCODE' => '_address_postcode',
    '!COMPANY' => '_address_company',
    '!PHONE' => '_address_telephone',
    '!FAX' => '_address_fax',
);

Also it checking if we need to create several addresses:

    $same_shipping = true;
    foreach ($xcart_address_fields as $xcart_field) {
        if ($row[$header['!B_' . $xcart_field]] != $row[$header['!S_' . $xcart_field]]) {
            $same_shipping = false;
            break;
        }
    }

Categories migration

Using the "categories.php" file from our archive you will be able to import X-Cart categories directly to Magento.
The script will not create any import CSV file, instead it will create categories using "catalog/category" model. The reason of it is following:

  • Magento do not have feature for import categories by default.
  • The number of categories is often less than 100, and it takes not so much time to save all category models directly.

In our script we do not import category images, if you need it you can check product import script. Also you will need to copy "xcart_images_C" table from X-Cart database.

The "categories.php" scripts works this way:

while ($row = fgetcsv($fd, 40960, $delimiter)) {
    $paths = explode('/', $row[$header['!CATEGORY']]);
    $cpath = count($paths);
    $name = array_pop($paths);
    if ($cpath == 1) {
        $parentId = 0;
    } else {
        if (!isset($_cache_xcart2id[join('/', $paths)])) {
            die ('Cannot find Xcart Category ID for ' . join('/', $paths));
        }

        $parentId = $_cache_xcart2id[join('/', $paths)];
    }

    $_cache_xcart2id[$row[$header['!CATEGORY']]] = $row[$header['!CATEGORYID']];
    if (!isset($sorted_cats[$parentId])) {
        $sorted_cats[$parentId] = array();
    }

    $sorted_cats[$parentId][$row[$header['!ORDERBY']] . '_' . $row[$header['!CATEGORYID']]] = $row;
}

Here we create array of subcategories. The keys of array are X-Cart category Ids. We do not make any changes in Magento on this step. This step is important to sort the categories by position:

foreach ($sorted_cats as $cats) {
    ksort($cats);
    foreach ($cats as $c) {
        $sorted[] = $c;
    }
}

After that we insert categories to Magento and find Magento category parent id:

    $paths = explode('/', $row[$header['!CATEGORY']]);
    $cpath = count($paths);
    $name = array_pop($paths);
    if ($cpath == 1) {
        $parentId = Mage::app()->getStore($store_id)->getRootCategoryId();
    } else {
        if (!isset($_cache_xcart2magento[join('/', $paths)])) {
            die ('Cannot find Magento Category ID for ' . join('/', $paths));
        }

        $parentId = $_cache_xcart2magento[join('/', $paths)];
    }

Next, the script create Magento category (if it is not exists) and add category id to local cache:

    $c = Mage::getModel('catalog/category')->getCollection()
        ->addAttributeToFilter('name', $paths[$cpath - 1])
        ->getFirstItem();
    if (!$c->getId()) {
        if (!isset($_cache_categories_path[$parentId])) {
            $_cache_categories_path[$parentId] = Mage::getModel('catalog/category')->load($parentId)->getPath();
        }

        $c = Mage::getModel('catalog/category')
            ->setName($name)
            ->setIsActive(($row[$header['!AVAIL']]=='Y')?1:0)
            ->setData('attribute_set_id', $default_attribute_set_id)

            ->setData('url_key', $row[$header['!CLEAN_URL']])
            ->setData('description', mb_convert_encoding($row[$header['!DESCR']], "UTF8"))
            ->setData('meta_title', mb_convert_encoding($row[$header['!TITLE_TAG']], "UTF8"))
            ->setData('meta_keywords', mb_convert_encoding($row[$header['!META_KEYWORDS']], "UTF8"))
            ->setData('meta_description', mb_convert_encoding($row[$header['!META_DESCRIPTION']], "UTF8"))

            ->setPath($_cache_categories_path[$parentId])
            ->setStoreId(0)
            ->save();
    }

    $_cache_xcart2magento[$row[$header['!CATEGORY']]] = $c->getId();

Products migration

Our "products.php" migration script converts X-Cart options to Magento Custom Options and variants to Magento Configurable products. It also converts images.

You need to setup websites to import products:

$websites = array('base');
$_attribute_set = 'Default';

The "products.php" script works this way:

1. The script collects general data of the products and use mapping for attributes (just like in the "customer.php" script). There are only two things that should be noted:
"meta_description" is wrapped to 256 symbols and the script use lowercase for SKU.
2. The script collects X-Cart classes for Magento custom options and use "xcart_product_options.csv" file for this.

while($row = fgetcsv($fd, 40960, $delimiter)) {
    if ($row[$header['!PRODUCTID']]) {
        $prev_id = $row[$header['!PRODUCTID']];
    }

    if ($row[$header['!CLASS']]) {
        $prev_class = $row[$header['!CLASS']];
        if ($row[$header['!TYPE']] == 'Y') {
            $is_option = true;
        } else {
            $is_option = false;
        }
    }

    if ($is_option && isset($products[$prev_id])) {
        if ($row[$header['!CLASS']]) {
            $products[$prev_id]['options'][$prev_class] = array(
                'name'   => mb_convert_encoding($row[$header['!DESCR']], "UTF8"),
                'position' => $row[$header['!ORDERBY']],
                'values' => array(),
            );
        }

        $products[$prev_id]['options'][$prev_class]['values'][] = mb_convert_encoding($row[$header['!OPTION']], "UTF8");
    }

    if (!isset($prods2toclasses[$prev_id])) {
        $prods2toclasses[$prev_id] = array();
    }

    if ($row[$header['!CLASS']]) {
        $prods2toclasses[$prev_id][$row[$header['!CLASS']]] = mb_convert_encoding($row[$header['!DESCR']], "UTF8");
    }
}

3. The script collects configurable attributes and options (X-Cart variants). It uses X-cart "CLASS" for super attribute code. After that it creates new attributes:

    if ($installer->getAttribute('catalog_product', $code)) {
        $code2id[$code] = $installer->getAttribute('catalog_product', $code, 'attribute_id');
        continue;
    }
    $installer->addAttribute('catalog_product', $code, array(
        'label'        => $name,
        'required'     => false,
        'input'        => 'select',
        'default'      => '',
        'position'     => 1,
        'sort_order'   => 3,
        'group'        => 'General',
        'user_defined' => true
    ));
    $code2id[$code] = $installer->getAttribute('catalog_product', $code, 'attribute_id');

and create attribute options:

    $res = $connection->query("select a.option_id from " . $optionTable . " AS a INNER JOIN " . $optionValueTable . " AS b ON a.option_id = b.option_id WHERE a.attribute_id = " . $code2id[$code] . " AND b.value = " . $connection->quote($value) . "");
    $opt_val = $res->fetch(PDO::FETCH_ASSOC);
    if ($opt_val && $opt_val['option_id']) {
        $option_id = $opt_val['option_id'];
    } else {
        $data = array(
            'attribute_id'  => $code2id[$code],
            'sort_order'    => 0,
        );
        $connection->insert($optionTable, $data);
        $magento_id = $connection->lastInsertId();
        $data = array(
            'option_id' => $magento_id,
            'store_id'  => 0,
            'value'     => $value,
        );
        $connection->insert($optionValueTable, $data);
        $option_id = $magento_id;
    }

4. Collect images. Here script will use X-Cart tables "xcart_images_*":

$a = $connection->query("select image_path, id, alt, orderby, avail from xcart_images_p");
$_media_attribute_id = 88;
while ($roes1 = $a->fetch(PDO::FETCH_ASSOC)) {
    if (!isset($products[$roes1['id']])) {
        continue;
    }

    $products[$roes1['id']][$magento_header['small_image']] = getImageImportName($roes1['image_path']);
    $products[$roes1['id']][$magento_header['small_image_label']] = $roes1['alt'];
    $products[$roes1['id']][$magento_header['image']] = getImageImportName($roes1['image_path']);
    $products[$roes1['id']][$magento_header['image_label']] = $roes1['alt'];
    $products[$roes1['id']]['media'][] = array(
        '_media_attribute_id' => $_media_attribute_id,
        '_media_image' => getImageImportName($roes1['image_path']),
        '_media_lable' => $roes1['alt'],
        '_media_position' => $roes1['orderby'],
        '_media_is_disabled' => ($roes1['avail'] == 'Y')?0:1,
    );
}

5. Preparing CSV files for import. The script uses a limit for products in the CSV file, it allow to decrease file size (if needed):;

    $counter++;
    if ($counter >= 100000) {
        $counter = 0;
        $globali++;
        fclose($mag_fd);
        $mag_fd = fopen($path . sprintf($magento_file_counter, $globali), "w+b");
        fputcsv($mag_fd, $magento_header_arr);
    }

After products files are created you need to import them to Magento via Import feature.

Orders migration

We create simple extension which show X-Cart orders in the admin area and customer area of Magento. To use it you need to create "customer_id" field in the "xcart_orders" table:

ALTER TABLE xcart_orders ADD customer_id int(10) not null default 0;
ALTER TABLE xcart_orders ADD KEY(customer_id);

and run "orders.php" file from our archive which will assign "customer_id" field.

Voila! Data migration from X-Cart to Magento is finished.