Revision of Drupal feeds module: FeedsProcessor with ubercart Order from Mon, 2014-08-04 13:00

使用者情境:批量使用 CSV 匯入 ubercart 訂單

Feeds 模組已經有一個 CSV 格式讀取 parser,又有批量 batch 支持,能處理大檔案 CSV ,Feeds 的代碼也有很多使用的經驗,而且最重要的是,模組自己已經有四種內容可以匯入:(nodes, users, terms, feednode?),有更高的機會支持其他內容 (ubercart orders)

先從 “The developer's guide to Feeds” 開始研究:(https://www.drupal.org/node/622700)
有三種 plugin: fetcher,parser 和 processor

  • Fetcher 是「獲得匯入源的方法」feeds module 已經有一個檔案上載的 fetcher
  • Parser 是「如何分析,讀取匯入源」feeds module 已經有一個 CSV parser
  • Processer 的工作是將處理過的資料儲存成為 Drupal 的內容

建立一個新模組 ubercart_order_feeds.module:

<?php
/**
 * Implementation of hook_feeds_plugins().
 * http://drupalcontrib.org/api/drupal/contributions%21feeds%21feeds.api.php/function/hook_feeds_plugins/6
 */
function ubercart_order_feeds_feeds_plugins() {
 
$path = drupal_get_path('module', 'ubercart_order_feeds');
 
$info = array();
 
$info['FeedsOrderProcessor'] = array(
   
'name' => 'Order processor',
   
'description' => 'Create orders.',
   
'help' => 'Create orders from parsed content.',
   
'handler' => array(
     
'parent' => 'FeedsProcessor',
     
'class' => 'FeedsOrderProcessor',
     
'file' => 'FeedsOrderProcessor.inc',
     
'path' => $path,
    ),
  );
  return
$info;
}
?>

建立一個新檔案 FeedsOrderProcessor.inc:
<?php
class FeedsOrderProcessor extends FeedsProcessor {
}
?>

首先在 getMappingTargets() 定義 CSV 中會用到的欄位:

<?php
 
/**
   * Return available mapping targets.
   */
 
public function getMappingTargets() {
   
$targets = array(
     
'sku' => array(
       
'name' => t('Product SKU'),
       
'description' => t('SKU of the product.'),
      ),
     
'quantity' => array(
       
'name' => t('Quantity'),
       
'description' => t(''),
      ),
     
'cost' => array(
       
'name' => t('Cost'),
       
'description' => t(''),
      ),
     
'delivery_first_name' => array(
       
'name' => t('Delivery: First name'),
       
'description' => t(''),
      ),
     
'delivery_last_name' => array(
       
'name' => t('Delivery: Last name'),
       
'description' => t(''),
      ),
   
//....
?>

which the key of the array will be used again to obtain the CSV value and name will be used in mappings admin page.

I had dump a native order object to find out the default attributes and use exact keys to save me some code mapping hazel. (i.e. delivery_first_name, delivery_last_name above and more)

process() method is the main entry point, and copied some code from NodeProcessor:

<?php
 
public function process(FeedsImportBatch $batch, FeedsSource $source) {
   
// Count number of created and updated nodes.
   
$created  = $updated = $failed = 0;

    while (
$item = $batch->shiftItem()) {
     
// Map item to a term.
     
$order = $this->map($batch);
     
//.......
?>

At this point a new function map() is defined, and should return an empty ubercart order object:

<?php
 
/**
   * Execute mapping on an item.
   */
 
protected function map(FeedsImportBatch $batch, $target_order = NULL) {
   
// Prepare user account object.
   
if (empty($target_order)) {
     
$order = uc_order_new($user->uid, 'completed');
    }

   
// Have parent class do the iterating.
   
return parent::map($batch, $order);
  }
?>

parent::map() function will assign data from CSV to order object

Create order programatically

The next step in process() is adding the order’s product, and save that to DB

Load a product by vid: <?php uc_product_load() ?>
http://drupalcontrib.org/api/drupal/contributions!ubercart!uc_product!uc...

Add product to order:
<?php $order->products[] = $product; ?>

after adding some basic mail and payment method, save the order, complete the sale:

<?php
uc_order_save
($order);
uc_cart_complete_sale($order, TRUE);
?>

Optionally, add payment method as well
<?php uc_payment_enter($order->order_id, 'bank_transfer', 0, 0, NULL, t('Checkout completed for a free order.')); ?>

Full source in Github repo: https://github.com/joetsuihk/ubercart_order_feeds

One more thing

custom settings can be added to the processor
In my case, the client need to deploy to multiple sites and they have slightly different webform IDs that need to be filled, so I put the ID into a settings so client can change that ID easily:

<?php
 
/**
   * Override parent::configDefaults().
   */
 
public function configDefaults() {
    return array(
     
'card_form_id' => '',
     
'mappings' => array(),
    );
  }

  public function
configForm(&$form_state) {
   
$form = array();
   
$form['card_form_id'] = array(
     
'#type' => 'textfield',
     
'#title' => t('Card webform node ID'),
     
'#default_value' => $this->config['card_form_id'],
    );
    return
$form;
  }
?>
Google