Example product import script

There’s no standard import script - but here’s an example to get you going.

Note that you will need to customise the code to make it suit your source data and where the things should go. I’ll also update this a little more later.

It’s expected to be run from the command line with a config.core.php in the same directory (or one up) pointing to the MODX core.

<?php

// Initiate MODX - can skip this if you're using as a snippet
if (file_exists(__DIR__ . '/config.core.php')) {
    require_once __DIR__ . '/config.core.php';
}
elseif (file_exists(dirname(__DIR__) . '/config.core.php')) {
    require_once dirname(__DIR__) . '/config.core.php';
}
if (!defined('MODX_CORE_PATH')) {
    echo "Could not find MODX_CORE_PATH; please add a config.core.php file.\n";
    exit(1);
}

require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('web');
$modx->getService('error','error.modError', '', '');

// Load Commerce
$path = $modx->getOption('commerce.core_path', null, MODX_CORE_PATH . 'components/commerce/') . 'model/commerce/';
$params = ['mode' => $modx->getOption('commerce.mode')];
/** @var Commerce|null $commerce */
$commerce = $modx->getService('commerce', 'Commerce', $path, $params);
if (!($commerce instanceof Commerce)) {
    echo "Could not load Commerce service from {$path}.\n";
    exit(1);
}

// Get your data - this may be from a XML/CSV dump or another table. Process it to get the same array structure.
// For all available fields see core/components/commerce/model/schema/commerce.mysql.schema.xml line 78+
// Note some fields require special attention, like pricing
$data = [
    [
        'sku' => 'TV12541ASD',
        'name' => 'Big TV',
        'price' => 149900, // in cents
        'sale_price' => 129900,
        'sale_price_from' => '2019-08-07 18:11:31+02:00',
        'sale_price_until' => '2019-10-01 01:00:00+02:00',
        'stock' => 14,
        'weight' => 15.50,
        'weight_unit' => 'kg',
        'image' => 'full/url/to/image.jpg',
    ],
    [
        'sku' => 'RADIOA991',
        'name' => 'A991 Radio',
        'price' => 5900, // in cents
        'stock_infinite' => true, // v1.1+
        'weight' => 900,
        'weight_unit' => 'g',
        'image' => 'full/url/to/image.jpg',
    ],
];

// Get tax group and delivery type
// This assumes all products have the same tax group and delivery type which is configured as the default
$taxGroup = $modx->getObject('comTaxGroup', ['id' => (int)$modx->getOption('commerce.default_tax_group')]);
if (!($taxGroup instanceof comTaxGroup)) {
    echo "Tax group not found - configure that first.\n";
    exit(1);
}
$deliveryType = $modx->getObject('comDeliveryType', ['id' => (int)$modx->getOption('commerce.default_delivery_type')]);
if (!($deliveryType instanceof comDeliveryType)) {
    echo "Delivery type not found - configure that first.\n";
    exit(1);
}

// To use a specific non-default currencies, use $commerce->getCurrency('KEY');
$currency = $commerce->currency;


// Set these up accordingly
$resourceParent = 76;
$resourceTemplate = 3;
$tvName = 'products';
$tvType = 'list'; // list or matrix

foreach ($data as $sourceRow) {
    /** @var comProduct $product */
    $product = $modx->newObject('comProduct');
    $product->fromArray($sourceRow); // This assumes the source data has the right keys

    // Set delivery type and tax group
    $product->set('tax_group', $taxGroup->get('id'));
    $product->set('delivery_type', $deliveryType->get('id'));

    // Set pricing - for detailed explanation see https://docs.modmore.com/en/Commerce/v1/Developer/Products/Pricing.html
    $pricing = $product->getPricing($currency);
    $pricing->setRegularPrice(new \modmore\Commerce\Pricing\Price($currency, $sourceRow['price']));

    // have a sale price?
    if (isset($sourceRow['sale_price']) && $sourceRow['sale_price'] > 0) {
        $pricing->addPriceType(
            new \modmore\Commerce\Pricing\PriceType\Sale(
                new \modmore\Commerce\Pricing\Price(
                    $currency,
                    $sourceRow['sale_price']
                ),
                new DateTime($sourceRow['sale_price_from']), // when the sale started
                new DateTime($sourceRow['sale_price_until']) // when the sale ended
            )
        );
    }
    $product->savePricing($pricing);

    echo "Created product {$product->get('sku')} with ID #{$product->get('id')}\n";

    // use the processor to create the resource - this does all sort of processing that we don't want to rewrite
    /** @var modProcessorResponse $response */
    $response = $modx->runProcessor('resource/create', [
        'context_key' => 'web',
        'parent' => $resourceParent,
        'template' => $resourceTemplate,
        'pagetitle' => $sourceRow['name'],
        'alias' => $modx->filterPathSegment($sourceRow['name'] . '-' . rand(0,999999)), // can leave this out or set a specific alias
        'published' => true,
    ]);
    if ($response->isError()) {
        $errors = implode(', ', $response->getAllErrors());
        echo "Could not create resource: {$errors}{}\n";
    }
    else {
        $resourceId = $response->getObject()['id'];
        $resource = $modx->getObject('modResource', ['id' => $resourceId]);
        if (!($resource instanceof modResource)) {
            echo "Could not load created resource with ID {$resourceId}\n";
        }
        else {
            if ($tvType === 'list') {
                $tvValue = $product->get('id'); // Multiple products per resource? Separate IDs with a comma.
            }
            else { // matrix
                echo "Matrix portion of this example is not yet ready :(\n";
                $tvValue = '';
            }
            $resource->setTVValue($tvName, $tvValue);
        }
    }
}


echo "Done!\n";
@session_write_close();
exit(0);
2 Likes

hi Mark,

how about the Matrix-portion of this example :wink: ?

great job, thx!

Marc

The matrix is a bit more complex to import because you need to generate the rows and columns first, rather than the individual products within them. That means preparing your data in that way, too. Bit hard to implement side-by-side with the list - a much more complicate task.

After creating the resource like for the list tv, create the comProductMatrix and save its ID to the $tvValue.

In that matrix you need to create the comProductColumn and comProductRow records.

Next, to fill the products in the matrix, you can use the \modmore\Commerce\TVs\Matrix class. Instantiating that with the Commerce class and your Matrix instance is enough for it to create each individual product based on your columns and rows and matrix configuration. Alternatively first creating whatever records you already know with the right matrix, row, and column values will make it skip those and only create what’s missing.

To change values on individual products, you can use xPDO to getObject the comProductMatrixProduct object with your matrix, row, and column values. Keep in mind some values can get overwritten when the user edits the row or columns.

3 posts were split to a new topic: Importing to a product list or resource products

6 posts were split to a new topic: Importing custom product values

2 posts were split to a new topic: Import script can’t save pricing

If you have any questions about an import script you’ve created based on this example, please open a new topic to discuss it, and provide your full code with it.