Skip to main content

Blog

Upcoming trainings

Over 8 years have passed since there was a DrupalCamp in tropical Nicaragua. With the help of a diverse group of volunteers, sponsors, and university faculty staff, we held our second one. DrupalCamp Lagos y Volcanes ("Lakes & Volcanoes") was a great success with over 100 people attending in 2 days. It was a big undertaking so we followed giants' footsteps to prepare for our event. Lots of the ideas were taken from some of the organizers' experience while attending Drupal events. Others came from local free software communities who have organized events before us. Let me share what we did, how we did it, and what the results were.

Sign up to be notified when Agaric gives a migration training:

Training FAQ

Here are some quick answers to Frequently Asked Questions about Agaric trainings.

Can I get a discount when purchasing more than one training?

Yes, a 15% discount is available for additional trainings. Contact Agaric after purchasing your first training to get the information needed to purchase second or third at a discount.

Are scholarships available?

Yes, partial and full scholarships are available! We are prioritizing people facing disadvantages, in particular survivors of historical and ongoing discrimination and people under-represented in technology. Contact Agaric with your circumstances and for more information. Agaric will review your submission internally only— we do not share the information you provide in determining your eligibility for a discount/scholarship with anyone outside Agaric.

What if my corporate employer cannot submit payment for my attendance before the training date? If your company sees the benefit of you getting training from Agaric but the accounts payable department will not be able to accommodate fulfilling payment prior to the training date, please contact us for an invoice due up to 90 days after the training.
I'm not sure I meet the prerequsites or will be able to set up a development environment.

Every training includes sessions ahead of time to help everybody get their local development environment set up before the training (if needed). We'll also direct you to other resources helpful for getting up to speed before the training. Contact Agaric to check in with where you are to get our help ensuring you get the full benefit of the training.

Can I cancel and get a refund?

No, we do not offer refunds but tickets are transferable and we'll help you re-sell the ticket, so please contact Agaric if you can't make the training.

Will I receive a recording or other materials?

Yes, all attendees will be sent recordings from the training as well as the curriculum and materials used in the training and additional books or other references and resources.

Will Agaric provide a certificate of completion?

Yes, all students who participate in the full training will receive a Certificate of Completion.

Have more questions? Ask Agaric!

Most companies are run hierarchically. Small companies might have a boss while larger companies usually have a CEO and board of directors or other form of corporate leadership.

We believe work can be done differently- organized democratically and transparently as a worker-owned cooperative. There are 7 principles that cooperatives follow:

  1. Open and Voluntary Membership
  2. Democratic Member Control
  3. Member Economic Participation
  4. Autonomy and Independence
  5. Education, Training, and Information
  6. Cooperation Among Cooperatives
  7. Concern for Community

This means that each employee is also an owner and we make decisions democratically, fostering a culture of honesty and accountability. Our focus on community ensures that quality work comes first, always contributing back to the software commons when possible. Finally, in a world of unprecedented wealth disparity, we hope to serve as a model for what a business should be.

Vestibulum iaculis consectetur convallis. Donec sit amet congue massa. Quisque nec eros eu dolor posuere fermentum et vel nulla. Donec mollis leo sit amet egestas vulputate. Etiam eget ante ante. Proin dignissim justo vel nibh tincidunt, a porta dui tincidunt. Ut varius, metus ac luctus molestie, turpis magna ultricies lectus, at ornare tellus diam sit amet risus. In hac habitasse platea dictumst. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus neque dui, placerat ac massa nec, rutrum rhoncus ex. Sed ut posuere urna.

In the previous article we provided a reference of available configuration options for migrate source plugins. In today’s article we are doing something similar for destination plugins. We will present a reference of available configuration options for migrate destination plugins provided by Drupal core and some contributed modules. Knowing which options are available might require some Drupal development knowledge. By providing this reference it should make the process of writing migrations easier.

List of configuration options for destination plugins

For each migrate destination plugin we will present: the module that provides it, the class that defines it, the class that the plugin extends, and any inherited options from the class hierarchy. For each plugin configuration option we will list its name, type, a description, and a note if it is optional.

DestinationBase (abstract class)

Module: Migrate (Drupal Core)

Class: Drupal\migrate\Plugin\migrate\destination\DestinationBase

Extends: Drupal\Core\Plugin\PluginBase

This abstract class is extended by many migrate destination plugins. This means that the provided configuration keys apply to any destination plugin extending it.

List of configuration keys:

  1. destination_module: An optional string value. Identifies the module handling the destination data. If not set, the Migrate API tries to read the value from the destination plugin definition.

Entity (abstract class)

Module: Migrate (Drupal Core) Plugin ID: entity:$entity_type See below.

Class: Drupal\migrate\Plugin\migrate\destination\Entity

Extends: Drupal\migrate\Plugin\migrate\destination\DestinationBase

Inherited configuration options: destination_module.

Related article: Drupal migrations reference: List of properties per content entity

This abstract class is extended by migrate destination plugins that want to import entities. It uses the MigrateEntity derivative to handle both content and configuration entities. The derivative sets the plugin ID to entity:$entity_type where $entity_type is the machine name of the entity. For example, entity:node and entity:node_type.

By default, content entities are handled by the EntityContentBase class while configuration entities use EntityConfigBase. Some entities like user (content) and node type (configuration) use specific classes for the import operation. The DefaultPluginManager::getDefinitions method triggers the search for classes that will override the default for a particular plugin ID. The override ultimately happens in the DerivativeDiscoveryDecorator::getDerivatives method.

In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys, which will be available to its derivatives:

  1. default_bundle: An optional string value. Gets the bundle for the row taking into account the default. If not present, the bundle entity key should be set in the process section for entities that can have more than one bundle. For example, the type property for nodes, the vid property for taxonomy terms, and the bundle property for media entities.

EntityContentBase

Module: Migrate (Drupal Core) Plugin ID: entity:$entity_type See below.

Class: Drupal\migrate\Plugin\migrate\destination\EntityContentBase

Extends: Drupal\migrate\Plugin\migrate\destination\Entity

Inherited configuration options: destination_module and default_bundle.

This class is used to handle import operations for all content entities, unless a specific class exists for the plugin ID being used. For example, nodes are handled by this class, but users leverage the EntityUser class. The MigrateEntity derivative sets the plugin ID to entity:$entity_type where $entity_type is the machine name of the content entity. For example, entity:node, entity:user, entity:taxonomy_term, entity:file, entity:media, entity:comment, entity:block_content, and entity:contact_message.

In addition to the keys provided in the parent class chain, this class provides the following configuration keys:

  1. translations: An optional boolean value. It indicates if the entity is translatable, defaults to FALSE. If set to TRUE, the corresponding langcode entity key will be added to the list of destination IDs that uniquely identifies each record. If the migration itself does not provide a value for the langcode property, the site’s default language is used.
  2. overwrite_properties: An optional array of string values. It is a list of properties or fields in the destination entity that will be overwritten if an entity with the same ID already exists. Any properties that are not listed will not be overwritten. Refer to the class documentation for an example.
  3. validate: An optional boolean value. It indicates whether an entity should be validated, defaults to FALSE. This was introduced in Drupal 8.8 and can be used to prevent invalid entities from being migrated. For example, a node with an empty title would fail validation. A required field that is not set by the migration will trigger a validation error, unless the field is configured to have a default value. Similarly, an integer field with minimum and maximum values will trigger an error if a value outside that range tries to be imported. Field API validations are triggered when this configuration option is set to TRUE.

EntityUser

Module: User (Drupal Core) Plugin ID: entity:user See below.

Class: Drupal\user\Plugin\migrate\destination\EntityUser

Extends: Drupal\migrate\Plugin\migrate\destination\EntityContentBase

Inherited configuration options: destination_module, default_bundle, translations, overwrite_properties, and validate.

This class provides a destination plugin for migrating user entities. It performs extra checks like preventing that the password for user 1 is overridden by the migration.

In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys:

  1. md5_passwords: An optional boolean value. If set to TRUE, the pass (password) property of the user entity is assumed to contain an MD5 hash. The passwords will be salted and re-hashed before they are saved to the destination Drupal database.

EntityRevision

Module: Migrate (Drupal Core) Plugin ID: entity_revision:$entity_type See below.

Class: Drupal\migrate\Plugin\migrate\destination\EntityRevision

Extends: Drupal\migrate\Plugin\migrate\destination\EntityContentBase

Inherited configuration options: destination_module, default_bundle, translations, overwrite_properties, and validate.

This class provides an entity revision destination plugin. Only revisionable entities, those that define a revision entity key, can use this destination plugin. It uses the MigrateEntityRevision derivative which sets the plugin ID to entity_revision:$entity_type where $entity_type is the machine name of the entity. For example, entity_revision:node whose revision key is vid and entity_revision:block_content whose revision key is revision_id.

Entity revisions can only be migrated after the entity to which they belong has been migrated. For example, revisions of a given node (entity_revision:node destination migration) can be migrated only after the current version of that node (entity:node destination migration) has been imported.

EntityReferenceRevisions

Module: Entity Reference Revisions module Plugin ID: entity_reference_revisions:$entity_type See below.

Class: Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions

Extends: Drupal\migrate\Plugin\migrate\destination\EntityRevision

Inherited configuration options: destination_module, default_bundle, translations, overwrite_properties, and validate.

Related article: Introduction to paragraphs migrations in Drupal

This class provides an entity revision revision destination plugin. It uses the MigrateEntityReferenceRevisions derivative which sets the plugin ID to entity_reference_revisions:$entity_type where $entity_type is the machine name of the entity. For example, entity_reference_revisions:node and entity_reference_revisions:paragraph. For example, entity_reference_revisions:node whose revision key is vid and entity_reference_revisions:paragraph whose revision key is revision_id.

This is the destination plugin used for migrating Paragraphs. Entity reference fields are no longer supported to reference Paragraphs. Instead, entity entity reference revisions must be used. Therefore, this class is used for paragraphs migrations with the entity_reference_revisions:paragraph plugin ID. See this article for an example on how to migrate paragraphs.

In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys:

  1. new_revisions: An optional boolean value. Flag to indicate if a new revision should be created instead of updating a previous default record. Only applicable when providing an entity id without a revision_id. Defaults to FALSE.

EntityConfigBase

Module: Migrate (Drupal Core) Plugin ID: entity:$entity_id See below.

Class: Drupal\migrate\Plugin\migrate\destination\EntityConfigBase

Extends: Drupal\migrate\Plugin\migrate\destination\Entity

Inherited configuration options: destination_module and default_bundle.

This class is used to handle import operations for all configuration entities, unless a specific class exists for the plugin ID being used. For example, taxonomy vocabularies are handled by this class, but node types leverage the EntityNodeType class. The MigrateEntity derivative sets the plugin ID to entity:$entity_type where $entity_type is the machine name of the content entity. For example, entity:node_type, entity:user_role, entity:taxonomy_vocabulary, entity:block, entity:comment_type, entity:block_content_type, entity:contact_form, entity:date_format.

In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys:

  1. translations: An optional boolean value. if TRUE, the destination will be associated with the langcode provided by the source plugin. Defaults to FALSE. For example: en for English, es for Spanish, and fr for French.

EntityNodeType

Module: Node (Drupal Core) Plugin ID: entity:node_type

Class: Drupal\node\Plugin\migrate\destination\EntityNodeType

Extends: Drupal\migrate\Plugin\migrate\destination\EntityConfigBase

Inherited configuration options: destination_module, default_bundle, and translations.

This class is used to import node types. It does not take extra configuration options. The plugin overrides the import method to attach the body field to the imported content type. This depends on the presence of certain destination properties in the imported row. That is, the following properties needs to be mapped in the process section of the migration:

  1. create_body: An optional boolean value. If TRUE, a body field will be added to the content type.
  2. create_body_label: An optional string value. If set and create_body is TRUE, the value for this destination property will be used as the label of the body field.

Config

Module: Migrate (Drupal Core) Plugin ID: config

Class: Drupal\migrate\Plugin\migrate\destination\Config

Extends: Drupal\migrate\Plugin\migrate\destination\DestinationBase

Inherited configuration options: destination_module.

This class persists data to the configuration management system. In addition to the keys provided in the parent class chain, this class provides the following configuration keys:

  1. store null: An optional boolean value. If TRUE, when a property is NULL, the NULL value is stored. Otherwise, the default value is used. Defaults to FALSE. Note that there is a space character in the configuration name.
  2. translations: An optional boolean value. if TRUE, the destination will be associated with the langcode provided by the source plugin. Defaults to FALSE.

Additionally, the plugin expects certain destination properties in the imported row. That is, the following properties needs to be mapped in the process section of the migration:

  1. config_name: A string value. The machine name of the configuration. For example: node.settings, node.type.article, user.settings, system.site, and core.extension.
  2. langcode: An optional string value. The language code of the configuration. For example: en for English, es for Spanish, and fr for French.

Table

Module: Migrate Plus Plugin ID: table

Class: Drupal\migrate_plus\Plugin\migrate\destination\Table

Extends: Drupal\migrate\Plugin\migrate\destination\DestinationBase

Inherited configuration options: destination_module.

This class allows you to write directly to a table not registered with Drupal Schema API. See this test for an example on how to use this plugin.

In addition to the keys provided in the parent class chain, this class provides the following configuration keys:

  1. database_key: An string value. Key for the database connection used for inserting records. See this documentation page for more information on database connection keys. We also covered the topic when explaining the SqlBase source plugin.
  2. table_name: An string value. Name of the table where records will be imported.
  3. batch_size: An optional integer value. Maximum number of rows to insert in one query. Defaults to 1.
  4. id_fields: An associative array value. Fields used to uniquely identify table rows. At least one field is required. See the class’s docblock for an example of the expected structure.
  5. fields: An optional associative array value. Mapping of column names to values set in the process section.

Available configuration for other migrate destination plugins

In Drupal core itself there are around 50 migrate destination plugins. And many more are made available by contributed modules. It would be impractical to document them all here. To get a list by yourself, load the plugin.manager.migrate.destination service and call its getDefinitions() method. This will return all migrate destination plugins provided by the modules that are currently enabled on the site. This Drush command would get the list:

# List of migrate destination plugin definitions. $ drush php:eval "print_r(\Drupal::service('plugin.manager.migrate.destination')->getDefinitions());" # List of migrate destination plugin ids. $ drush php:eval "print_r(array_keys(\Drupal::service('plugin.manager.migrate.destination')->getDefinitions()));"

To find out which configuration options are available for any destination plugin consider the following:

  • Find the class that defines the plugin and find out which configuration values are read. Some plugins even include the list in the docblock of the class. Search for a pattern similar to $this->configuration['option_name'] or $configuration['option_name']. The plugins can be found in the Drupal\module_name\Plugin\migrate\destination namespace. The class itself would be in a file under the /src/Plugin/migrate/destination/ directory of the module.
  • Look up in the class hierarchy. The destination plugin can use any configuration set directly in its definition class and any parent class. There might be multiple layers of inheritance.
  • Check if the plugin requires the presence of specific destination properties to be set in the process section. This is usually documented in the class’ docblock, but you can also look for code like $row->getDestinationProperty('property_name').

What did you learn in today’s article? Did you know that migrate destination plugins can inherit configuration keys from their class hierarchy? Were you aware that there are so many destination plugins? Other than the ones listed here, which destination plugins have you used? Please share your answers in the comments. Also, we would be grateful if you shared this article with your friends and colleagues.

Oh yeah!

I'm back! Because I knew this blog post wasn't quite long enough already...certainly not because it occurred to me that I left off another alternative I've used in a pinch.

The one-off

If you want to run a command using a different version of php, rather than rely on the shebang, you can always just call the desired script as an argument to php itself. When you run, e.g. drush status, your shell sees the #! on the first line, and uses that as the default interpreter for the script. In essence, your command is translated into /usr/bin/env php drush status.

Rather than accept this default, you can specify the specific version on your command line, such as php8.1 drush status. In this case the #! is just ignored. Unfortunately, this method can only get you so far - particularly with drush. Drush often figures out what your command requires and in turn will spawn other php scripts to do the dirty work. These spawned scripts are unaware of the interpreter that drush itself was started with.

Results

Thanks to the updated CoLab sites, NICHQ Staff are now collaborating more effectively. By including users from the beginning with surveys and interviews we were able to update their software with exactly the features they need. Doing so on a distribution has empowered their team to deploy new sites and improve existing ones as CoLabs grow.

Ben spoke most recently on this topic at DrupalCon Seattle and Drupal Camp Twin Cities, both in 2019, about how Drupal as a Service can save our livelihoods and our lives.  Here are some resources related to the talk and topic.

A graphic of the earth engulfed in flames.

The Earth is not dying, it is being killed, and those who are killing it have names and addresses.

Utah Phillips

As far as i can tell, the Internet hasn't sourced that quotation, but no one challenges that it was Phillips— and i thought i heard him say about the same thing on the radio, in a broadcast where he also slammed NPR, boosting community radio and, if needed, the do-it-yourself be-the-media ethos.  Maybe i'm part of a collective delusion, in which case, i'm sure Utah Phillips wouldn't care one way or another so long as we got on with working for the liberation of humanity and preserving life on planet Earth as part of that.

Sources cited

Crowd-sourcing this on Mastodon (itself interestingly compatible with LibreSaaS with instances like social.coop working on governance models, if not yet having the revenue to cover hosting, let alone contribute back to development:

Hello fediverse, what online tools do you know about that are LibreSaaS— you can pay to use them, but the full stack is free/libre open source software so you or others can totally host the entire thing yourself if you want?

I added whichever answers i didn't already have to the LibreSaaS list pad.

Tech Won't Build It

We'll keep looking for ways that cooperation can save us all (and technology can help) so sign up to get (extremely occasional, less than once a year) updates.

A screenshot shows the headline "Display Lists Naturally with the In Other Words Drupal Module", some whitespace, and then as a single line "By Clayton Dewey, Ben Melançon, and David Valdez".  Below that after another couple lines of whitespace one line of regular text beginning the article is visible.

Software Libre es un programa o aplicación con una licencia que tiene como intención principal la de mantener nuestra libertad. El propósito en mente está especificado en las cuatro libertades (numeración basada en cero en lenguajes de programación): 
 
   Libertad 0: La libertad de ejecutar el programa y utilizarlo para cualquier propósito.
 
  Libertad 1: La libertad para acceder y estudiar cómo funciona un programa y poder cambiarlo, adaptándolo a sus propias necesidades. 
 
   Libertad 2: La libertad de redistribuir copias para que pueda ayudar a otros usuarios.
 
   Libertad 3:La libertad de hacer cambios y distribuir las versiones modificadas a otros. 
 
Ejemplos como Linux, hasta Firefox y Drupal, tienen una fuerza impresionante en nuestro mundo. Al crear y desarrollar este tipo de software libre, ayudamos a construir uno único y en común, en el que todos podamos confiar y beneficiarnos.

¿Por qué Libre?

 
La libertades que otorga un software libre son más importantes que nunca.  Vivimos en un mundo donde gran parte de los programas o aplicaciones que usamos viola nuestra privacidad y manipula nuestro comportamiento. 
 
Por esta  razón creamos, mantenemos y promovemos proyectos de software libre siempre que es posible. De esta manera,  ampliamos las opciones que tenemos para mantenernos mas seguros y al mismo tiempo, los diferentes desarrolladores de software están obligados a elevar sus estándares de calidad.  
 
 

Una nota sobre otras palabras: Software Libre o Código Abierto

 
 
Existen términos semejantes utilizados para describir software similares. ‘Libre Software’ es sinónimo de ‘Free Software’. Esta denominación en idioma hispano también puede significar gratis y no necesariamente libre.  Al usar la palabra Libre, intentamos eliminar esta ambigüedad inherente que puede interpretarse como gratuito y no como la del respeto a nuestra libertad. 
 
El Código Abierto es muy similar al Software Libre. Surgió originalmente como una alternativa para eliminar la ambivalencia. Mas sin embargo, al hacerlo perdió el poder ético y político que caracteriza la filosofía del movimiento. 
 
El termino ‘Free Software’ se refiere a la libertad que implica su uso, mientras que Código Abierto se refiere solamente a la disponibilidad del acceso al código fuente. Entonces, si bien respetamos y usamos el término Código Abierto, apreciamos la expresión Software Libre y preferimos usarlo indistintamente porque enfatiza el valor de nuestra libertad. 
 

Otras lecturas

 
Los siguientes artículos son excelentes para obtener más información sobre el significado del Software Libre, algunas de las diferencias y debates entre los diversos términos.
 
    Artículo de Wikipedia sobre Software Libre
    Cómo acuñé el término 'código abierto' por Christine Peterson
    Por qué Open Source pierde el punto del Software Libre por Richard Stallman
    Cuando el software libre no es (prácticamente) superior por Benjamin Mako Hill