Maak zelf je CSV, TXT importer

Moeilijkheidsgraad

trefwoorden

Een uitdaging.. Complexe materie, eenvoudig overbrengen... ik ga dit proberen in kleine stapjes...De prints zijn Engelstalig, waarvoor excuses. Als Vlaming kan je toch makkelijk volgen hé.., omgekeerd is moeilijker...

We gaan lijst van honden importeren die we in ons 'hondenhotel' ontvangen. (Volledig fictief uiteraard). (Deze bespreking kan even goed gebruikt worden om een Webshop --type Basic Cart-- te maken uitgaande van een stocklijst.)

Maak een inhoudstype 'Honden' aan met naast de titel nog een bodyveld.

Screen000910.png

VERWIJDER BESTAANDE INHOUD

Ik ga nu eerst leren om alle nodes van een inhoudstype te verwijderen. We gaan dus telkens nieuwe nodes aanmaken. Later gaan we dit wel aanpassen en de bestaande nodes updaten, maar ik begin simpel..

Maak een eenvoudige pagina aan en zet de tekstfilter naar PHP (PHP module geïnstalleerd). PHP begint met een <?php en eindigt met ?> Daartussen gebeurt de magic.. Maak zeker dat je nooit rechten geeft aan iemand anoniem voor zulke pagina's.

De code opent een query die de nodes opzoekt van het type in de condition geplaatst. De handler voert de verwijdering uit. Sla op en uwen Tarzan is gaan vliegen... Telkens je de weergave van deze pagina gebruikt gebeurt de uitvoering..

<?php

//delete nodes from a content type
$result = \Drupal::entityQuery("node")
    ->condition("type", "dogs")
    ->execute();

$storage_handler = \Drupal::entityTypeManager()->getStorage("node");
$entities = $storage_handler->loadMultiple($result);
$storage_handler->delete($entities);

?>

Tarzan is niet meer...

Screen000911.png

Telkens we een import gaan doen, gaan we dus eerst de vorige nodes verwijderen. Onze CSV of TXT is dus de enige bron van inhoud op dit ogenblik. Nogmaals, later gaan we dit aanpassen.

Laten we nu zo'n CSV aanmaken.

In Excel maak ik bvb een bestandje aan

Screen000912.png

Ik sla dit op als CSV. De UTF-8 moet er voor zorgen dat namen als Adèle (met accenten en speciale tekens) ook worden weergegeven. Let daar op.
Screen000913.png

Je kunt dit bestand ook openen in Notepad++.


Screen000914.png

Een programma dat ik aanraad om ook je php pagina mee te maken. Sla op als php bestand.

Screen000915.png

Er zit een ingebouwde codedetector in. Je maakt er minder fouten mee.. Kleurcodes geven aan of je php correct is qua syntax.

Screen000916.png

LEES HET BESTAND

Plaat je CSV bestandje (of TXT --ook UTF8!!) op de server in een map en voeg onderstaande code toe aan je php bestand. Hou wel je ?> altijd onderaan.

We openen eerst het bestand op de server en we gaan per rij (r) openen. Hier staat er een limiet op van 1000. voor sommige van mijn import voor school is dit niet voldoende. Alle rijen worden ingelezen in de variabele $data. Variabelen in php beginnen et een $. Het is een matrix variabele (array). $data[0] is bvb de eerste kolom. Later gaan we nog meer kolommen hebben. We geven nu via 'echo' op het scherm weer wat er ingelezen wordt in deze matrix. Dit wordt later overbodig.

Merk ook de ";" op. Dit is het scheidingsteken als er meerder kolommen zullen zijn. Bij Excel is dit standaard puntkomma. Dit houdt in dat er nooit in een tekst een puntkomma mag staan. Je kunt de CSV bvb omzetten naar "|" dat minder gebruikt wordt in tekst.

$row = 0;
if (($handle = fopen("http://dirkgevorderd.drupal8cursisten.be/importingdogs.csv", "r")) !== FALSE) {
    while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
        $num = count($data);
       echo "<p> $num fields in line $row: <br /></p>\n";
      
        for ($c=0; $c < $num; $c++) {
          echo $row." ".$c." ".$data[$c] . "<br />\n";
         }
$row++;
    }
 fclose($handle);
}

Screen000917.png

IMPORTEER NODE TITEL

Er is nog niets geïmporteerd hoor...We hebben enkel het bronbestand ingelezen in een variabele...

We breiden de code uit. Onderaan is de import van de titel nu een feit. Telkens er dus een rij wordt ingelezen wordt er onmiddellijk een node bijgemaakt. Er is ook een regel bijgekomen $language om de taal van de node te kunnen bepalen. Deze regel staat boven de lus, om niet telkens te moeten worden bepaald. Logisch. Eén keer de taal bepalen is voldoende. De titel wordt geïmporteerd met $data[0]. Weet je nog dat dit de eerste kolom was? Hier speelt de kolomkop dus geen rol. daarom wordt die ook weggefilterd. Kijk maar in de code.

<?php

//delete nodes from a content type
$result = \Drupal::entityQuery("node")
    ->condition("type", "dogs")
    ->execute();

$storage_handler = \Drupal::entityTypeManager()->getStorage("node");
$entities = $storage_handler->loadMultiple($result);
$storage_handler->delete($entities);

$language = \Drupal::languageManager()->getCurrentLanguage()->getId();

$row = 0;
if (($handle = fopen("http://dirkgevorderd.drupal8cursisten.be/importingdogs.csv", "r")) !== FALSE) {
    while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
        $num = count($data);
       echo "<p> $num fields in line $row: <br /></p>\n";
      
        for ($c=0; $c < $num; $c++) {
          echo $row." ".$c." ".$data[$c] . "<br />\n";
         }

//first row => no import
if ($row!=0){

//start new node

$node = \Drupal\node\Entity\Node::create(array(
          'type' => 'dogs',
          'title' => $data[0],
          'langcode' => $language,
          'uid' => 1,
          'status' => 1,     
    ));
 
$node->save();
reset($nodes);      
}
$row++;

}
 fclose($handle);
}
?>

Je ziet bij inhoud, nu de titels verschijnen. Proficiat. De eerste import is een feit.

Screen000919.png

Hadden we een é gebruikt bij Cléopatra, dan mag dit geen probleem vormen aangezien we UTF-8 gebruiken.

Screen000935.png

IMPORTEER TEKSTVELDEN

Nu gaan we het body veld importeren. Pas je CSV bestandje aan met een 2e kolom.

Screen000921.png

Voeg dit toe onder 'status'=>1,

Velden gaan we met hun machinenaam aanspreken. Bij sommigen zoals body zijn er nog attributen (samenvatting, waarde en formaat). Je hoeft deze niet te gebruiken tenzij je ze nodig hebt.

   'body' => [
              'summary' => '',
              'value' =>  $data[1],
              'format' => 'full_html',
               ],

Dit zou dus ook gekund hebben als je geen opmaak of samenvatting nodig hebt. Value is het standaard argument van velden. Als je niets preciseert, bedoel je value..

'body'=> $data[1],

Andere tekstvelden (anders dan body) die je zou gebruiken volgen dezelfde redenering. Gebruik de machinenaam van het veld.

vb   'field_machine_name'=>$data[1],

Hier zie je dat ik wel wat opmaak gebruikt heb..

Screen000920.png

We voegen een tekstveld toe van het type tekst(lijst)

Screen000922.png

Voeg deze code toe. (Als je CSV geen gekende waarde bevat, dan wordt het veld leeg gelaten.)

 'field_payment'=>$data[2],


Screen000924.png

BOOLEAN VELDEN

Dit veldtype heeft eigenlijk maar 2 standen 0 en 1 (false en true). Via de instellingen van het veld kan je een meer begrijpelijke weergave maken.

Screen000926.png

Een keuzelijstje ziet er zo uit.

Screen000925.png

In je bestandje moet je werken met 1 of 0.

Screen000927.png

Voeg de code toe:

  'field_gender'=>$data[3],


Screen000928.png

Numbers

We gaan de prijs per dag weergeven. Maak een decimaal veldje aan.

Voeg het veld toe aan je bestand. Merk op dat het decimaal teken een punt is...

Screen000930.png

Voeg de code toe.

'field_price'=>$data[4],

Kassa...

Screen000929.png

DATUMVELDEN

Bij dat moet je een tussenbewerking doen. Dit komt omdat wij in Europa de data anders voorstellen dan bvb in de VS. We kiezen in het veld 'verjaardag' van de hond voor enkel datum. Bij de weergave kiezen we voor de Europese manier.

Screen000931.png
Screen000932.png

We breiden ons bestand uit.

Screen000934.png

In de code zie je drie nieuwe regels. De eerste 2 lezen de datum in als dag/maand/jaar (zoals in ons bestand aanwezig) en zetten het om naar jaar/maand/dag. Ik sla dit op als een variabele $NewDate. Uiteindelijk voeg ik dit veld toe bij de maken van de node. $data[5] is de datumkolom.

//start new node

$Readdate = \DateTime::createFromFormat('d/m/Y',$data[5]);
$newDate = $Readdate->format('Y-m-d');

$node = \Drupal\node\Entity\Node::create(array(
          'type' => 'dogs',
          'title' => $data[0],
          'langcode' => $language,
          'uid' => 1,
          'status' => 1,     
          'body' => [
              'summary' => '',
              'value' =>  $data[1],
              'format' => 'full_html',
               ],
           'field_payment'=>$data[2],
           'field_gender'=>$data[3],
           'field_price'=>$data[4],
           'field_birthday'=>$newDate,

    ));

Het resultaat.
Screen000933.png

MEERVOUDIGE VELDEN

We maken een tekstveldje bij 'colors' waar we tot 5 keer een kleur kunnen invullen. Meervoudig dus..

We gaan het ons moeilijk maken door in één kolom, met een komma gescheiden, de verschillende waarden te zetten. We hadden ook 5 nieuwe kolommen kunnen maken.

Screen000940.png
Als je deze code toevoegt,

 'field_colors'=>$data[6],

dan krijg je logischerwijs: brown,red,grey als waarde, maar in één veld.

De waarde wordt met een komma gescheiden in één veld bewaard. Dit was niet de bedoeling. De komma moet een nieuwe waarde genereren. We zullen dus onze eigen 'Tamper' maken. Een module die in Drupal 7 bestond om dit probleem aan te pakken.

Voeg deze code toe vóór $node = \Drupal\node\Entity\Node::create(array(

Het leest eerst de volledige string (=tekenreeks) in met komma's inbegrepen. Aangezien we 3 keer een veld voorzien, controleren we 2 X op een komma. We doen dit met strpos; een functie die de waarde detecteert en de plaats er van opslaat. We doen dit in een lusje. $i++ is de php manier om een variabele te verhogen met 1. Telkens we een komma detecteren, nemen we het eerste stukje er van en maken we onze zoekstring korter. Uiteindelijk is er geen komma meer en moeten we nog de laatste waarde opslaan.

//multiple field settings
unset($color);
$colorsource=$data[6];
$count=0;

for( $i = 0; $i<2; $i++ ) {           
           
         $pos = strpos($colorsource, ",",0);  //be carefull strrpos also exists
          
           if ($pos !== false) {
          $count++;
          $color[$count]=substr($colorsource,0,$pos);
          $colorsource=substr($colorsource,$pos+1,250);
          }
}
$count++;
$color[$count]=$colorsource;

Voeg nu aan de node creatie volgende code toe:

  'field_colors'=>array($color[1],$color[2],$color[3]),

Meervoudige waarden moeten in een matrix (=array) worden aangebracht voor de import. Onze lus heeft de waarden mooi in de $color variabele bewaard.

Zo krijgen we in één veld bvb drie meervoudige waarden.

Screen000941.png

AFBEELDINGSVELDEN

Afbeeldingen hebben een ID die je moet kennen om de afbeelding te kunnen aanspreken. Afbeeldingen in de map plaatsen op de server en daarna een link naar deze afbeelding leggen is geen oplossing. Het is niet gekend door Drupal. Het zit niet in zijn database. Plaats de code onder if($row!=0){

//first row => no import
if ($row!=0){

// start image import
  $imagedata[$row] = file_get_contents('public://importfolder/'.$data[7]);
  $file[$row] = file_save_data($imagedata[$row], 'public://dogimages/'.$data[7], FILE_EXISTS_RENAME);

$data[7] is de verwijzing naar de kolom in het bestandje.

Screen000945.png

Wat gebeurt er...

Ik heb een nieuwe map gemaakt: importfolder waarin ik alle afbeeldingen aanbreng die ik ooit wil importeren. Een soort bron map. De eerste regel van de code gaat die halen in die map.


Screen000946.png

De tweede regel maakt dat het bestand nu bewaard wordt in de map van het veld van inhoudstype. Merk op dat er per rij een andere variabele wordt aangemaakt met $row als index. Door deze handeling wordt de afbeelding in het Drupal systeem gebracht.


Screen000947.png

Bij de creatie van de node breng je nu dit aan: (ik heb het veld 'image' toegevoegd aan het inhoudstype.)

'field_image' => [
           'target_id' => $file[$row]->id(),
           'alt' => 'Dog',
           'title' => 'Dog'
           ],

Je kunt nu als je dit wilt ook de alt en title ingeven per rij, maar dan moet je daar ook kolommen voor voorzien natuurlijk.

Screen000948.png

Screen000949.png

 

TAXONOMIEVELDEN

Taxonomietermen kan men enkel toekennen aan de hand van hun term id (tid). Het is een referentieveld waarbij dat je eerst alle termen van een bepaald woordenboek gaat doorzoeken of de naam voorkomt zoals ze in je CSV bestand wordt weergegeven. Als je de termnaam gevonden hebt dan onthoud je de tid. Bij de nodecreatie moet je deze tid aan het veld toekennen. Is er geen match met de termnaam, dan bestaat de term nog niet en wordt er niets ingevuld. Je kunt deze script uitbreiden door dan de ontbrekende term aan te maken.

We maken een woordenlijst aan 'reproduction' (=voortplanting)
Screen000952.png

en passen het CSV bestand aan.

Screen000951.png

Bekijk de vetgedrukte regels. Alle termen van een bepaald woordenboek worden geladen in een variabele $terms. Voeg deze code toe. Het is niet nodig om dit in de lus  onder te brengen. Daarom staat het er boven.

// get language for the nodes
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();

// search taxonomy terms in vocabulary
$tids = \Drupal::entityQuery('taxonomy_term')->condition('vid','reproduction')->execute();
$terms = \Drupal\taxonomy\Entity\Term::loadMultiple($tids); // Loading the multiple terms by tid.


$row = 0;

Onderstaande code zoekt nu in alle $terms of er ééntje is die overeenkomt met $data[8]. Dit is de naam die voorkomt in het CSV bestandje. Als dit gevonden wordt, dan slaan we de tid op in $tidfound.

//first row => no import
if ($row!=0){

//search taxonomy name
$tidfound=NULL;
foreach ($terms as $term) {
  $termname = $term->name->value; 
  if($termname==$data[8]){          
         $tidfound= $term->id();
        echo "bingo".$termname." ".$tidfound;
     }
}

Tenslotte moeten we dit toekennen aan het veld in de node creatie..

 'field_reproduction'=>$tidfound,

Waarom kijkt hij triestig...

Screen000953.png

NODE REFERENTIE

Voor node referentie moeten we ongeveer hetzelfde doen als bij taxonomie termen. Hier wordt de nid (node id) gebruikt om in het referentieveld aan te brengen. Het is het cijfer dat je soms ziet achter de naam in het referentieveld. Maak een inhoudstype aan 'Dog breed' (=hondenras) en voeg enkele waarden toe.

Screen000954.png

Vul je bestandje ook aan.


Screen000956.png
 

Voeg nu deze code toe.Het haalt dus weer alle nodes uit een bepaald inhoudstype en bewaart die in de variabele $nodes.

//search nodes in content type
$nids = \Drupal::entityQuery('node')->condition('type','dog_breed')->execute();
$nodes =  \Drupal\node\Entity\Node::loadMultiple($nids);

$row = 0;

voeg nu de zoek script toe.$data[9] is onze kolom in het CSV bestandje

//first row => no import
if ($row!=0){

// search node id

$idfound=NULL;
foreach ($nodes as $node) {
   $nodetitle = $node->get('title')->value;
    if($nodetitle==$data[9]){          
         $idfound = $node->get('nid')->value;
        echo "bingo".$idfound;
     }
  }

Tenslotte toekennen aan het veld

  'field_dog_breed'=>$idfound,

Bingo

Screen000957.png

SLOT

CSV bestandje

Dogname;body;payment;gender;price;birthday;colors;image;reproduction;dog breed
Brutus;ugly as the night;cash;1;21.50;15/3/2016;grey,white;brutus.jpg;sterilized;Dalmatian dog
Cléopatra;beautifull but <strong>dangerous</strong>;Paypal;0;15.50;12/5/2014;chocolate;cleopatra.jpg;not sterilized;Golden Retriever
Ceasar;handle with care..aggressive;Bank Transfer;1;13.80;1/12/2010;brown,red,grey;ceasar.jpg;unknown;Border Collie

 

We hebben nu ongeveer alle veldtypes behandeld. Hieronder nog eens de volledige code. Met wat PHP kan je nu deze code uitbreiden door bijvoorbeeld bij het ontbreken van een referentie, die bij te maken. Ook hebben we hier een methode gebruikt die telkens de nodes verwijdert en daarna weer bijmaakt. Door een lus in te bouwen die kijkt of je reeds bvb de titel hebt, kan je dan deze node overslaan en hoef je ze niet eerst te verwijderen. Je moet dan eigenlijk nog gaan kijken of er geen veldwaarden werden aangepast. Je begrijpt dat dit wat scripting vergt. In deze cursus hou ik mij bij deze 'eenvoudige' aanpak.

<?php

//delete nodes from a content type
$result = \Drupal::entityQuery("node")
    ->condition("type", "dogs")
    ->execute();

$storage_handler = \Drupal::entityTypeManager()->getStorage("node");
$entities = $storage_handler->loadMultiple($result);
$storage_handler->delete($entities);

// get language for the nodes
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();

// search taxonomy terms in vocabulary
$tids = \Drupal::entityQuery('taxonomy_term')->condition('vid','reproduction')->execute();
$terms = \Drupal\taxonomy\Entity\Term::loadMultiple($tids); // Loading the multiple terms by tid.

//search nodes in content type
$nids = \Drupal::entityQuery('node')->condition('type','dog_breed')->execute();
$nodes =  \Drupal\node\Entity\Node::loadMultiple($nids);

$row = 0;
if (($handle = fopen("http://dirkgevorderd.drupal8cursisten.be/importingdogs.csv", "r")) !== FALSE) {
    while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
        $num = count($data);
       echo "<p> $num fields in line $row: <br /></p>\n";
      

        for ($c=0; $c < $num; $c++) {
          echo $row." ".$c." ".$data[$c] . "<br />\n";
         }

//first row => no import
if ($row!=0){

// zoek id van referentienode

$idfound=NULL;
foreach ($nodes as $node) {
   $nodetitle = $node->get('title')->value;
    if($nodetitle==$data[9]){          
         $idfound = $node->get('nid')->value;
        echo "bingo".$idfound;
     }
  }

//search taxonomy name
$tidfound=NULL;
foreach ($terms as $term) {
  $termname = $term->name->value; 
  if($termname==$data[8]){          
         $tidfound= $term->id();
        echo "bingo".$termname." ".$tidfound;
     }
}

// start image import
  $imagedata[$row] = file_get_contents('public://importfolder/'.$data[7]);
  $file[$row] = file_save_data($imagedata[$row], 'public://dogimages/'.$data[7], FILE_EXISTS_RENAME);


//start new node
// date settings
$Readdate = \DateTime::createFromFormat('d/m/Y',$data[5]);
$newDate = $Readdate->format('Y-m-d');

//multiple field settings
unset($color);
$colorsource=$data[6];
$count=0;

for( $i = 0; $i<2; $i++ ) {           
           
         $pos = strpos($colorsource, ",",0);  //be carefull strrpos also exists
          
           if ($pos !== false) { 
              $count++;
              $color[$count]=substr($colorsource,0,$pos);
              $colorsource=substr($colorsource,$pos+1,250);
          }
}
$count++;
$color[$count]=$colorsource;


//creating nodes

$node = \Drupal\node\Entity\Node::create(array(
          'type' => 'dogs',
          'title' => $data[0],
          'langcode' => $language,
          'uid' => 1,
          'status' => 1,     
          'body' => [
              'summary' => '',
              'value' =>  $data[1],
              'format' => 'full_html',
               ],
           'field_payment'=>$data[2],
           'field_gender'=>$data[3],
           'field_price'=>$data[4],
           'field_birthday'=>$newDate,
           'field_colors'=>array($color[1],$color[2],$color[3]),
           'field_image' => [
           'target_id' => $file[$row]->id(),
           'alt' => 'Dog',
           'title' => 'Dog'
           ],
           'field_reproduction'=>$tidfound,
           'field_dog_breed'=>$idfound,
    ));
 
$node->save();
reset($nodes);      

reset($data);
reset($file); 

}
$row++;

}
 fclose($handle);
}
?>