Jul 02 2019
Jul 02

In the last article we managed to set up all commerce types and additional modules to import the data from our csv files. Now we need to do this regularly in order to provide users with the latest updates from our remote Hotellinx server.

Importing data from the server and writing them into the csv files is done by hook_cron() in our custom module from the first article. We want to do this once every hour, so we use the ultimate cron module in order to set up different execution times for different cronjobs.

Feeds can also be called via cron which imports the data every hour. As mentioned in the second article, the feeds have to be imported in the correct order for the Commerce variations to actually become visible. Unfortunately, we cannot specify the order in the feeds module but because we repeat this process every hour, it takes maximal two hours for the variations to be correctly imported.

Jun 19 2019
Jun 19

In the last article, we programmed a new module which created CSV files from data on a different server. Now, we need to migrate this data into our live-system. As first step we need to create a commerce store to which our products and product variations belong. Otherwise, they cannot be imported.

Commerce product and product variations

Afterwards we need a commerce product type and a commerce product variation type. A product type stores a general product, while a product variation type stores different variation of said product type. I.e. "Pair of cotton pants" would be defined as product type, while "Pair of cotton pants in red/blue/yellow, size s/m/l would be defined as product variation type.

Our product type is a 'room' and as fields, we want to store an id, a title and an image carousel showcasing the room.

Our product variation type needs to display different variations of a 'room'. Here we also need the id and additional fields for a variation title and a description of the room variation.

Feed settings

After these types are finished, we need to define two corresponding feed types. Both feed types need almost the same settings.

Feeds Tamper basic settingsThe fetcher is 'Directory' as parser we choose 'CSV'. As processor we choose 'product' for our rooms and 'product variation' for our room variations. As product type/product variation type we choose the commerce types we just created in the first step.

The other settings can be changed to personal liking, except the default delimiter in the Parser settings which needs to be a comma(,) because that is what we chose for our csv files.

Now, the feed type mapping has to be set up. The feeds will only work, if all mandatory fields are mapped correctly. We need to select 'Feed: Authored by' and 'Feed: Authored on' as source fields. Additionally we need 'Status' and 'SKU' which need to be set for Commerce products. These are the minimum settings we need for feeds to run. Additionally, our product type room also needs 'Store'. Otherwise commerce cannot import the product. Store and status need to be imported from the csv files, authored by and authored on don't need to be. Store has to be the store for which the product and product variation types were created for.

feeds mapping

We select all other fields from the csv file we'd like to map to our product type /product variation type and save.

If we were to import now, the variations would not appear. They are linked to their corresponding products by the sku defined in the csv file, but the feeds module needs help to understand the link. The module feeds tamper creates another tab 'Tamper' in the feeds type settings. Here we create a plugin for our feeds type room. We select explode and as field we chose the sku and as delimiter we select '|', because that is what we chose during the csv file creation.

feeds tamper

Creating Feeds and importing

After defining the feed types, we have to create actual feeds from them. In Content > Feeds. All we need the the path to our csv file and the correct delimiter ','. After creating one feed for the products and one for the product variations, all we have to do is activate them. Feeds belong to website-content, which means they need to be imported like content. Otherwise you have to create them again and they can't be exported to config-files.

feeds

When actually importing, the order is important. We need to import the product variations before the products, otherwise they will not appear. Feeds module creates a temporary table with the newly imported product variations and compares their skus with skus from newly imported products and connects them accordingly. It does not work the other way around.

Jan 23 2019
Jan 23

The task was to integrate an ERP system (more specific Hotel management system called Hotellinx) which is (and should always stay or be) a single source of truth (SSOT) for the products they have and administer. If you want to sell these products from another system like a webshop you need to transfer the data there to showcase your goods to customers. Another way of explaining that is to say the webshop is only to make a purchase but all the data is stored to the ERP system or SSOT.

Importing old content to a new website (usually only once) is called migration what is a part of every site creation. If you do importing regularly by scheduling it automatically the process is called integration. Yes, integration usually goes both ways so the sequel of the article will talk about it. Here we are going to get data from the Hotellinx API, write the result into a CSV file and import the data as commerce products (more specific product variations) into our Drupal 8 project. Below a image about the integration from which we implement bringing the data to a CSV file in this article.

Hotellinx integration architectureFull Hotellinx ERP integration architecture

 

 

The modules we are going to need are Commerce, Commerce Feeds, Feeds and Feeds Tamper. We use Feeds for migrating the data because it is easy and appropriate for this case. Of course there are other options like custom PHP scripts, the Drupal migrate module/framework or doing it by hand.

As first step we need a custom module which downloads data from the Hotellinx API and writes the result into a CSV file.

We use a MODULE.install file with a MODULE_install() function to make it possible to change data from the drupal UI.

function MODULE_install() 
  { 
    \Drupal::configFactory()
    ->getEditable('MODULE.settings') 
    ->set('Username', '') 
    ->set('Password', '') 
    ->set('url', 'link/to/hotellinx.api') 
    ->set('filepath', 'public://MODULE/') 
    ->set('productsFileName', 'MODULE_hotellinx_rooms.csv') 
    ->set('VariationsFileName', 'MODULE_hotellinx_variations.csv') 
    ->save(); 
  }

Then we create an admin UI in MODULE>src>Form which enables the user to edit his Hotellinx credentials or the path of the CSV file. We need a MODULE.routing.yml to make the form accessible from the UI.

MODULE.settings: 
  path: '/admin/config/services/MODULE' 
  defaults: 
    _form: '\Drupal\MODULE\Form\MigrationAdminUI' 
    _title: 'Hotellinx Settings'
  requirements: 
    _permission: 'access administration pages'

First we add the route to the module.info by adding

configure: MODULE.settings 

 

as last line. This way a link to the settings page is accessible from the install new module page.

You can add a link to the admin menu. Create file

 

MODULE.links.menu.yml

 

and add

MODULE.adminUI: 
  title: 'Hotellinx Settings'
  parent: system.admin_config_services
  route_name: MODULE.settings 
  weight: 10

This enables a user to find the admin form from the module settings like API endpoint, tokens or passwords.

Now we can finally create the actual functionality. We are using hook_cron() to activate the download process.

We use PHP curl to connect to the external Hotellinx API.

$url = \Drupal::configFactory()
  ->getEditable('module.settings')
  ->get('url'); 
$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, $url); 
curl_setopt($ch, CURLOPT_POST, true); 
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
curl_setopt($ch, CURLOPT_POSTFIELDS, $request()); 
$xmlstring = curl_exec($ch); 
curl_close($ch);

 

The general $request() looks like this:

$Username = \Drupal::configFactory()
  ->getEditable('module.settings')
  ->get('Username'); 
$Password = \Drupal::configFactory()
  ->getEditable('module.settings')
  ->get('Password'); 
  return "<HOTELLINX POST REQUEST>";

Which returns the responcs as it is defined in the Hotellinx API - after adding necessary and optional parameters. So replace the part <HOTELLINX POST REQUEST> with the desired API request.

The result is written into two different CSV files like this:

$file = fopen($productsPath, "w"); 
  foreach ($roomlist as $line) 
    { 
      $line .= $productMap[explode(',', $line)[0]]; 
      fwrite($file, $line . "\n"); 
    } 
  fclose($file);

According to the Feeds module documentation, all strings have to be surrounded by quotes like "string".

Hotellinx provides us with rooms and which are sold at different rates. In order to translate that into Commerce "terms" (so that it's technically practical) we need two files. The rates will be migrated as Commerce variations and the rooms will be migrated as products. Each product can be sold in different variations (e.g. Price, offer, people in it etc.).

The first variation file contains the rates and some important variables which are needed later in the integration:

"SKU","RoomTypeId",            "Title"
  104,          67,   "Single package"
  105,          68,   "Family package"
  106,          68,"Newly wed package"

The different variations are identified by the SKU. The RoomTypeId defines to which room these rates belong. Because the Feeds module cannot make this connection without external help, we need a second file for the products.

"RoomTypeId",      "Title",   "Store",  "SKU"
          67,"Single Room","my_store",    104
          68,"Double Room","my_store",105|106

This file lists the products, in this case hotel rooms and defines with a pipe as delimiter which variations belong to each product. (See SKU 105|106. The pipe is used by the module Feeds tamper, which extends Feeds.)

After running the program (execute Cron) we have two different files, one containing future commerce products and the other containing future commerce variations of those products.

The next step is migrating the data from the CSV files by using the Feeds and Commerce Feeds modules usable Commerce entities - products and variations.

Jun 23 2017
Jun 23

Our goal is to create a block with an image 3d effect integrated view, so we can easily filter which images will be displayed. I chose cloud9carousel because it had the style we were looking for. You can check the default outlook here. 

The first step is creating the view with the name carousel3d. There is no need to change a lot. Just the fields, where I remove everything and add the field 'Image'. In the field-settings, I choose 'medium' as image style. This is optional and can be changed later on. Now I add a block and give it the machine name 'block3d'.

Done. I can place this block everywhere on the website that displays all kinds of images. Lastly I use Features to export the view carousel3d.

The next step is creating the custom module. I name it slideshow and it contains a slideshow.module, slideshow.libraries.yml, slideshow.info.yml a folder 'js' and a folder 'config'. The config folder contains a folder 'install' and there I put the views.view.carousel3d.yml that I exported via Features. When my module is installed, drupal will scan for the install folder and install everything in it automatically.

I need to add the js sources to the js folder. It contains the jquery.cloud9carousel.js from cloud9carousel and another js file slideshow.js.

slideshow.libraries.yml must define the js files that are included in this module. (I also included reflection.js because it adds reflections to the slideshow. But this is totally optional and is explained on cloud9carousel.)

slideshow:
  version: VERSION
  js:
    js/slideshow.js: {}
    js/jquery.cloud9carousel.js: {}
    js/reflection.js: {}
  dependencies:
    - core/jquery
    - core/jquery.once
    - core/drupal

slideshow.module must add the js from the library to the page. I use the hook page_attachments_alter. The path is 'modulename/libraryname'. Which are the same in this case.

function slideshow_page_attachments_alter(array &$attachments){

            $attachments['#attached']['library'][] = 'slideshow/slideshow';

}

Ive created the view, I've declared the js and added it to the website. Now I have to connect the two. This happens in the slideshow.js. I need to jquery the block that is created by drupal. The id begins with block-views-block-VIEWNAME-BLOCKNAME. Then I add a few cloud9carousel options including the itemClass. There I have to use 'field-content>img' to get images regardless of the resolution that is defined in carousel3d view. (The key 'mirror' belongs to the reflection.js and is optional.)

jQuery(document).ready(function ($) {
    var height = 150;
    var width = 600;
    var $block = $("[id^=block-views-block-carousel3d-block3d]");
    $block.css('visibility', 'hidden').Cloud9Carousel({
        autoPlay: 1,
        // bringToFront: true,
        itemClass: "field-content>img",
        farScale: 0.5,
        xRadius: width,
        yRadius: height,
        mirror: {
            gap: 12, /* 12 pixel gap between item and reflection */
            height: 0.2, /* 20% of item height */
            opacity: 0.4 /* 40% opacity at the top */
        },
        onLoaded: function () {
            // Show carousel
            $block.css('visibility', 'visible');
            $block.height(3 * height);
            $block.width(2 * width);
        }

    });
});

Now everything is done. After module installation, I can go to block layout and place my block carousel3d on where ever I like.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web