Esportare le categorie di Magento in un file CSV

Come anticipato nell’ultimo articolo sui profili d’importazione ed esportazione avanzati vedremo un’applicazione pratica, un modulo per l’esportazione delle categorie.

Cosa ci serve per esportare i dati delle categorie? Riprendendo i concetti spiegati nello scorso articolo avremo bisogno di un “Adapter” personalizzato che ci servirà a ottenere i dati delle categorie dal database e di un “Parser” personalizzato che prenda i dati caricati dall’Adapter e li traduca in un formato leggibile dal Parser di default di Magento che si occuperà di creare il CSV. Non creeremo un “Mapper” personalizzato, dato che quello già presente in Magento è più che sufficiente per il nostro scopo.

Partiamo dalla struttura base di un modulo per Magento, definiamo il Model aggiungendo le righe necessarie al file config.xml.

<?xml version="1.0"?>
<config>
  <modules>
    <Artera_Exportcategories>
      <version>0.1.0</version>
    </Artera_Exportcategories>
  </modules>
  <global>
    <helpers>
      <exportcategories>
        <class>Artera_Exportcategories_Helper</class>
      </exportcategories>
    </helpers>
    <models>
      <exportcategories>
    <class>Artera_Exportcategories_Model</class>
      </exportcategories>
    </models>
  </global>
</config>

Ora creiamo le due nostre classi Adapter e Parser:

  • Artera_Exportcategories_Model_Convert_Adapter_Categories che estenderà la classe Mage_Eav_Model_Convert_Adapter_Entity
  • Artera_Exportcategories_Model_Convert_Parser_Categories che estenderà la classe Mage_Eav_Model_Convert_Parser_Abstract

Analizziamole singolarmente.

Adapter

Il nostro Adapter personalizzato servirà per un’esportazione, quindi avremo bisogno di implementare la funzione “load()” che si occuperà di estrarre dal database i dati delle categorie.

È da notare che è stata estesa la classe Mage_Eav_Model_Convert_Adapter_Entity al posto di Mage_Dataflow_Model_Convert_Adapter_Abstract, come mai? La risposta è molto semplice, stiamo lavorando con dati EAV di Magento, questa classe estende Mage_Dataflow_Model_Convert_Adapter_Abstract, fornendo una base per lavorare più facilmente con dati di questo tipo. Se un giorno avrete bisogno di un vostro modulo personalizzato che esporterà dei dati che avete creato e non seguiranno il modello EAV di Magento, il vostro Adapter dovrà estendere Mage_Dataflow_Model_Convert_Adapter_Abstract.

Come detto in precedenza la funzione principale di cui avremo bisogno è “load()”, vediamone l’implementazione:

public function load()
{
    $attrFilterArray = array();
    $attrFilterArray ['store_id'] = 'eq';

    //applico eventuali filtri
    parent::setFilter($attrFilterArray);

    return parent::load();
}

Per prima cosa impostiamo i filtri che vorremo applicare ai dati da esportare, creiamo quindi l’array “$attrFilterArray” che avrà questa struttura: ‘identificatore attributo’ = ‘operatore filtro’, nel nostro modulo avremo bisogno solo di un filtro per “store”, con cui utilizzeremo l’operatore di uguaglianza “eq”. Per ogni filtro che vogliamo inserire dovremo quindi specificare quale operatore verrà applicato. Gli operatori applicabili sono quelli che vengono utilizzati per filtrare le “Collection” in Magento.

Dopo aver definito i filtri chiameremo la funzione appartenente al padre della nostra classe “setFilter”, che genera automaticamente il codice per filtrare i dati della collezione.

Infine chiamiamo la funzione “load()” del padre. In essa è già implementato il processo per l’estrazione dei dati, quindi non abbiamo bisogno di scriverlo noi. Attenzione: quest’ultima frase vale solo se stiamo lavorando con dati EAV, mentre se lavoreremo con dati personalizzati dovremo implementarci il processo da soli.

La funzione “load()” resituirà un array contenente tutti gli ID delle categorie.

La nostra classe non si compone solo della funzione “load()”, infatti è stato necessario implementare altre funzioni per specificare che vogliamo estrarre i dati delle categorie.

public function __construct()
{
    $this->setVar('entity_type', 'catalog/category');
    if (!Mage::registry('Object_Cache_Category'))
    {
            $this->setCategory($this->_getCategoryModel());
        }
}

Nel costruttore indicheremo l’entità che andremo a elaborare, nel nostro caso “catalog/category”

public function setCategory(Mage_Catalog_Model_Category $object)
{
        $id = Mage::objects()->save($object);
        Mage::register('Object_Cache_Category', $id);
}

protected function _getCategoryModel()
{
        if (is_null($this->_categoryModel)) {
            $categoryModel = Mage::getModel('catalog/category');
            $this->_categoryModel = Mage::objects()->save($categoryModel);
        }
        return Mage::objects()->load($this->_categoryModel);
}

Queste funzioni non sono strettamente necessarie per il funzionamento dell’esportazione, ma migliorano la performance sfruttando la sessione di Magento per evitare di eseguire le stesse query al database più volte.

Parser

Descritto l’Adapter” passiamo al Parser. Come già spiegato nel precedente articolo esso si occupa di prendere i dati estratti dall’Adapter, elaborarli e infine convertirli in un formato che permetta di salvarli in un file CSV.

Il primo passaggio per la creazione della classe è di estendere Mage_Eav_Model_Convert_Parser_Abstract. Anche in questo caso, come per l’Adapter, Magento ci viene in auto fornendoci una classe che implementa Mage_Dataflow_Model_Convert_Parser_Abstract per i dati EAV.

La funzione che si occupa dell’elaborazione dei dati è “unparse”, vediamone l’implementazione

public function unparse()
{
    $categoryIds = $this->getData();

    foreach($categoryIds as $i => $cat_id)
    {
        //ciclo tutti gli id di categoria che ho ottenuto
        $category = $this->getCategoryModel()
                    ->setStoreId($this->getStoreId())
            ->load($cat_id);

        //escludo Root
        if($category->getLevel() == 0)
            continue;

        $position = Mage::helper('catalog')->__('Line %d, Name: %s', ($i+1), $category->getName());
                $this->setPosition($position);

                //genero l'array $row che verr passato al parser IO per il salvataggio su csv
        $row = array(
            'store_id'        => $category->getStoreId(),
            'entity_id'        => $category->getId(),
            'parent_id'        => $category->getParentId(),
            'path'            => $category->getPath(),
            'position'        => $category->getPosition(),
            'include_in_menu'    => $category->getIncludeInMenu(),
            'name'            => $category->getName(),
            'url_key'        => $category->getUrlKey(),
            'is_active'        => $category->getIsActive(),
            'display_menu'        => $category->getDisplayMode(),
            'description'        => $category->getDescription(),
            'meta_keywords'        => $category->getMetaKeywords(),
            'meta_description'    => $category->getMetaDescription(),
            'is_anchor'        => $category->getIsAnchor()
        );

        //landing page
        $category_display = $category->getDisplayMode();
        if($category_display == self::DISPLAY_BLOCK || $category_display == self::DISPLAY_BOTH)
        {
            $landing_id = $category->getLandingPage();
            $row['landing_page'] = Mage::getModel('cms/block')->load($landing_id)->getIdentifier();
        }

        //images
        if($image = $category->getImage())
            $row['image'] = $image;

        if($thumbnail = $category->getThumbnail())
            $row['thumbnail'] = $thumbnail;

        //salvo la righa da passare al parser IO
        $batchExport = $this->getBatchExportModel()
                        ->setId(null)
                        ->setBatchId($this->getBatchModel()->getId())
                        ->setBatchData($row)
                        ->setStatus(1)
                        ->save();
    }
    return $this;
}

I passaggi che esegue questa funzione sono:

  • Recupera gli ID delle categorie che sono stati estratti dall’Adapter recuperandoli dalla sessione, dove erano stati salvati in attesa che il Parser inizi la sua esecuzione.
    $categoryIds = $this->getData();
  • Esclude dalla esportazione la categoria “Root”.
    if($category->getLevel() == 0)
        continue;
  • Estrae i dati di ogni categoria e li converte in array, tipologia di dato che può essere salvata in un file CSV.
    $row = array(
        'store_id'        => $category->getStoreId(),
        'entity_id'        => $category->getId(),
        'parent_id'        => $category->getParentId(),
        'path'            => $category->getPath(),
        'position'        => $category->getPosition(),
        'include_in_menu'    => $category->getIncludeInMenu(),
        'name'            => $category->getName(),
        'url_key'        => $category->getUrlKey(),
        'is_active'        => $category->getIsActive(),
        'display_menu'        => $category->getDisplayMode(),
        'description'        => $category->getDescription(),
        'meta_keywords'        => $category->getMetaKeywords(),
        'meta_description'    => $category->getMetaDescription(),
        'is_anchor'        => $category->getIsAnchor()
    );

    In questa fase è importante stare attenti ad alcuni campi particolari, dove i valori ottenuti dal Model non sono quelli che magari vogliamo avere sul file d’esportazione. Ognuno di questi casi deve essere trattato singolarmente, come vedremo in seguito.

  • Salviamo l’array nel “Batch” che il “Parser IO” leggerà per compilare il file CSV.
    $batchExport = $this->getBatchExportModel()
            ->setId(null)
            ->setBatchId($this->getBatchModel()->getId())
            ->setBatchData($row)
            ->setStatus(1)
            ->save();

    “Batch” non è altro che una tabella del database che viene compilata con tutte le righe interessate dall’esportazione o l’importazione, in parole povere è una tabella ponte che verrà poi utilizzata dalle prossime “action”, per recuperare i dati. Questa tabella viene svuotata ad operazione terminata.

Per quanto riguarda alcuni campi particolari, nel modulo ho gestito il campo “landing_page”, cioè il blocco statico che deve essere visualizzato nella pagina della categoria nel caso che sia impostata la visualizzazione della stessa in:“Solo blocco statico” o “Blocco statico e prodotti”.

const DISPLAY_BLOCK = 'PAGE';
const DISPLAY_BOTH = 'PRODUCTS_AND_PAGE';

...

//landing page
$category_display = $category->getDisplayMode();
if($category_display == self::DISPLAY_BLOCK || $category_display == self::DISPLAY_BOTH)
{
    $landing_id = $category->getLandingPage();
    $row['landing_page'] = Mage::getModel('cms/block')->load($landing_id)->getIdentifier();
}

Nel Model della categoria la “landing_page” è salvata come l’identificatore numerico del blocco statico. Preferiremmo invece avere il codice letterale del blocco statico che risulta più consistente come dato, per esempio vogliamo in un futuro importare l’albero delle categorie su un’altra installazione di Magento, dove i blocchi statici hanno un ordine di creazione diverso da dove abbiamo esportato le categorie. In un caso simile gli identificatori numerici saranno sicuramente diversi e risulterebbe un’importazione errata delle impostazione delle categorie. Utilizzando l’identificatore letterale, nel caso che siano stati “chiamati” in modo diverso, basterà modificare il dato da backend, invece che intervenire direttamente nel database.

L’implementazione della logica è molto semplice, viene controllata la modalità di visualizzazione della categoria, se è “Solo blocco statico” o “Blocco statico e prodotti” viene estratto l’ID del blocco statico, caricato il Model dello stesso e estratto l’identificatore letterale.

Ho voluto inserire un controllo ulteriore sulle immagini associate alle categorie, dato che questi campi non vengono sempre valorizzati. La funzione controlla che per ogni riga esistano questi dati e in caso affermativo li salva:

//images
if($image = $category->getImage())
    $row['image'] = $image;

if($thumbnail = $category->getThumbnail())
    $row['thumbnail'] = $thumbnail;

Con questo abbiamo terminato l’implementazione del modulo, vediamo ora le azioni XML per il profilo d’esportazione che utilizzerà le classi appena create.

Azioni XML

Andiamo in Sistema > Importa/Esporta > Dataflow – Profili Avanzati. Clicchiamo su “Aggiungi Nuovo Profilo” e nell’area di testo scriviamo queste azioni:

Estrazione degli ID di categoria

<action type="exportcategories/convert_adapter_categories" method="load">
    <var name="store"><![CDATA[1]]></var>
</action>

Richiamiamo la funzione “load” dell’Adapter del nostro modulo che estrarrà gli id delle categorie, filtrati per store.

Elaborazione dei dati delle categorie

<action type="exportcategories/convert_parser_categories" method="unparse">
    <var name="store"><![CDATA[1]]></var>
</action>

Richiamiamo la funzione “unparse” del Parser del nostro modulo. Essa utilizzando gli id ottenuti tramite l’Adapter elaborerà i dati delle categorie e li salverà nel Batch dedicato a questa esportazione.

Mappatura dell’intestazione del file CSV

<action type="dataflow/convert_mapper_column" method="map">
</action>

Anche se non abbiamo la necessita di mappare in modo particolare i nomi dei campi degli attributi della categoria, l’azione che richiama il Mapper deve comunque essere definita, dato che genera la prima riga, quella che contiene i nomi dei campi, del file CSV.

Conversione dei dati in formato CSV

<action type="dataflow/convert_parser_csv" method="unparse">
    <var name="delimiter"><![CDATA[,]]></var>
    <var name="enclose"><![CDATA["]]></var>
    <var name="fieldnames">true</var>
</action>

Utilizzando questo Parser standard di Magento i dati salvati nel Batch verranno convertiti nel formato più adatto per la creazione del file CSV.

Salvataggio file d’esportazione

<action type="dataflow/convert_adapter_io" method="save">
    <var name="type">file</var>
    <var name="path">var/export</var>
    <var name="filename"><![CDATA[export_all_categories.csv]]></var>
</action>

Viene creato e salvato il file CSV contenente i dati delle categorie da esportare.

Il modulo per l’esportazione dell’albero delle categorie potete trovarlo qui.

Lascia un commento

Tutti i campi sono obbligatori.
L'indirizzo email non verrà pubblicato

 

Commenti

  1. Pingback: Import/export in Magento: i profili avanzati - blog.artera.it

  2. avatarAntonio

    Ciao Maurizio,
    ho provato ad seguire quanto scritto in questo articolo ma quando avvio il profilo le uniche cose che mi escono sono:
    Starting profile execution, please wait…
    Warning: Please do not close the window during importing/exporting data

    poi il processo termina, ed anche il browser finisce il caricamento istantaneamente.
    Quale potrebbe essere la causa?

  3. avatarMaurizio Piatti Autore

    Ciao Antonio, così non ho abbastanza elementi per poter provare a “ricreare” l’errore. Potresti controllare nei log se c’è qualche segnalazione particolare riguardo al modulo? Se la cartella dei log è vuota controlla che siano attivi da Sistema->Configurazione->Sviluppa->Impostazioni Log.
    Dalla situazione che mi descrivi sembra che non viene eseguita la funzione load dell’adapter.

  4. avatarAntonio

    Non so perchè ma adesso dopo aver attivato i lob, la cosa ha funzionato senza alcuna modifica, mi sfugge il motivo, approfondirò… grazie cmq per l’attenzione.

    Un’altra cosa, se adesso vorrei effettuare l’import delle categorie da csv?

  5. avatarMaurizio Piatti Autore

    Strano comportamento, indagherò. Per quanto riguarda l’importazione delle categorie più o meno è sulla falsa riga del modulo d’esportazione, quindi estendere Mage_Eav_Model_Convert_Adapter_Entity implementando la funzione saveRow, che si occuperà di prendere i dati dal Batch d’importazione (questa fase è già gestita dalla classe padre) e ricostruire l’albero delle categorie.
    Per quanto riguarda l’azione xml ti rimando al mio post precedente, https://blog.artera.it/ecommerce/import-export-magento-profili-avanzati, dove viene mostrata la action del profilo d’importazione dei prodotti, la costruzione è molto simile.
    Naturalmente l’importazione, dal punto di vista del codice, sarà un po’ più complessa, dato che devi tenere conto della forma dell’albero.

  6. avatarAntonio

    Ciao Maurizio, ho trovato un articolo su un blog che spiegava come creare la funzione che si occupa di ricreare l’albero delle categorie con annesso azione xml, purtroppo però riscontro un errore quando avvio il profilo, mi segnala che non trova il metodo “parse” nella classe che ho definito, ma il metodo ovviamente c’è… data la tua esperienza se puoi dare un’occhiata all’articolo te ne sarei grato. (dove posso mandarti il link?)

  7. avatarMauro

    Ciao,
    ho trovato molto interessante il post.
    Volevo capire se la stessa procedura si può applicare per scaricare gli ordini di un gruppo di clienti.
    Forse c’è un metodo più semplice … ma mi sto avvicinando da poco a Magento e mi piacerebbe evitare l’uso di plugin.
    grazie

  8. avatarMaurizio Piatti Autore

    Cioa Mauro. Sì la stessa procedura è applicabile agli ordini, come ad altre tipologie di dato salvato da Magento. Un consiglio, dato che da poco lavori su Magento, leggi bene un po’ di documentazione riguardo alla struttura dati di Magento, che ti può aiutare molto nella realizzazione di ciò che ti sei prefessato.