# Question
Title: Migrating content from live to development environment with Docker
I'm trying to create a localhost development server with a dockerized drupal 8 environment and need to transfer the content from the live site to localhost.

I have followed the instructions for dockerization here: https://www.drupal.org/docs/develop/local-server-setup/docker-with-solr-integration/docker-configuration

And now I have to migrate the content from the live site into the dev server, and I'm confused about the process.

Any information would be highly appreciated!

Edit:

One way to import into my local environment was to create a SQL dump from the live database and then upload that into my docker-compose file.

```
version: '3'
services:
  db:
    image: mariadb:10.3
    environment:
      MYSQL_DATABASE: drupal
      MYSQL_ROOT_PASSWORD: MyGreatPassword
    volumes:
      - db_data:/var/lib/mysql
    restart: always

  phpmyadmin:
    depends_on:
      - db
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - "8080:80"
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: MyGreatPassword
      UPLOAD_LIMIT: '500M'

  drupal:
    depends_on:
      - db
    build: .
    ports:
      - "2345:80"
      - "2443:443"
    volumes:
      - ./:/var/www/html
    restart: always

  solr:
    image: solr:8
    ports:
      - "8983:8983"
    volumes:
      - ./mycores/collection1:/mycores/collection1
    entrypoint:
      - docker-entrypoint.sh
      - solr-precreate
      - collection1
      - /mycores/collection1
volumes:
  db_data:

```

Now, I'm having this error.

```
In Statement.php line 59
  SQLSTATE[42S22]: Column not found: 1054 Unknown column 'tags' in 'field list'

In Connection.php line 701:

  SQLSTATE[42S22]: Column not found: 1054 Unknown column 'tags' in 'field list': INSERT INTO {cache_bootstrap} (cid, expire, created, tags,
   checksum, data, serialized) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder
  _3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6) ON DUPLICATE KEY UPDATE cid = VALUES(cid), expire = VAL
  UES(expire), created = VALUES(created), tags = VALUES(tags), checksum = VALUES(checksum), data = VALUES(data), serialized = VALUES(serial
  ized); Array
  (
      [:db_insert_placeholder_0] => hook_info
      [:db_insert_placeholder_1] => -1
      [:db_insert_placeholder_2] => 1626559961.843
      [:db_insert_placeholder_3] =>
      [:db_insert_placeholder_4] => 0
      [:db_insert_placeholder_5] => a:39:{s:10:"token_info";a:1:{s:5:"group";s:6:"tokens";}s:16:"token_info_alter";a:1:{s:5:"group";s:6:"to
  kens";}s:6:"tokens";a:1:{s:5:"group";s:6:"tokens";}s:12:"tokens_alter";a:1:{s:5:"group";s:6:"tokens";}s:10:"views_data";a:1:{s:5:"group";
  s:5:"views";}s:16:"views_data_alter";a:1:{s:5:"group";s:5:"views";}s:13:"views_analyze";a:1:{s:5:"group";s:5:"views";}s:22:"views_invalid
  ate_cache";a:1:{s:5:"group";s:5:"views";}s:26:"views_plugins_access_alter";a:1:{s:5:"group";s:5:"views";}s:24:"views_plugins_area_alter";
  a:1:{s:5:"group";s:5:"views";}s:28:"views_plugins_argument_alter";a:1:{s:5:"group";s:5:"views";}s:36:"views_plugins_argument_default_alte
  r";a:1:{s:5:"group";s:5:"views";}s:38:"views_plugins_argument_validator_alter";a:1:{s:5:"group";s:5:"views";}s:25:"views_plugins_cache_al
  ter";a:1:{s:5:"group";s:5:"views";}s:36:"views_plugins_display_extender_alter";a:1:{s:5:"group";s:5:"views";}s:27:"views_plugins_display_
  alter";a:1:{s:5:"group";s:5:"views";}s:32:"views_plugins_exposed_form_alter";a:1:{s:5:"group";s:5:"views";}s:25:"views_plugins_field_alte
  r";a:1:{s:5:"group";s:5:"views";}s:26:"views_plugins_filter_alter";a:1:{s:5:"group";s:5:"views";}s:24:"views_plugins_join_alter";a:1:{s:5
  :"group";s:5:"views";}s:25:"views_plugins_pager_alter";a:1:{s:5:"group";s:5:"views";}s:25:"views_plugins_query_alter";a:1:{s:5:"group";s:
  5:"views";}s:32:"views_plugins_relationship_alter";a:1:{s:5:"group";s:5:"views";}s:23:"views_plugins_row_alter";a:1:{s:5:"group";s:5:"vie
  ws";}s:24:"views_plugins_sort_alter";a:1:{s:5:"group";s:5:"views";}s:25:"views_plugins_style_alter";a:1:{s:5:"group";s:5:"views";}s:26:"v
  iews_plugins_wizard_alter";a:1:{s:5:"group";s:5:"views";}s:25:"views_query_substitutions";a:1:{s:5:"group";s:15:"views_execution";}s:24:"
  views_form_substitutions";a:1:{s:5:"group";s:15:"views_execution";}s:14:"views_pre_view";a:1:{s:5:"group";s:15:"views_execution";}s:15:"v
  iews_pre_build";a:1:{s:5:"group";s:15:"views_execution";}s:16:"views_post_build";a:1:{s:5:"group";s:15:"views_execution";}s:17:"views_pre
  _execute";a:1:{s:5:"group";s:15:"views_execution";}s:18:"views_post_execute";a:1:{s:5:"group";s:15:"views_execution";}s:16:"views_pre_ren
  der";a:1:{s:5:"group";s:15:"views_execution";}s:17:"views_post_render";a:1:{s:5:"group";s:15:"views_execution";}s:17:"views_query_alter";
  a:1:{s:5:"group";s:15:"views_execution";}s:16:"field_views_data";a:1:{s:5:"group";s:5:"views";}s:22:"field_views_data_alter";a:1:{s:5:"gr
  oup";s:5:"views";}}
      [:db_insert_placeholder_6] => 1
  )

```

I've read some solutions to update the entities, but the drush command is no longer been maintained.

```
Run drush updb

Run drush entup

```

# Answer
There are two parts to the migration: the database and the static files.

The most important part is the database. You'll need a database backup (aka `sqldump`) of the production database, and you'll need to import that into your local database. There are several ways to accomplish this, depending on your site hosting and whether or not you can use `drush`.

The local site should be somewhat functional after a database import, but it may look odd if it is missing static assets such as image files. Depending on how the production site is configured, you can probably find most of these in the public files directory. The most common location for these files is `./web/sites/default/files` but the path may differ. You'll need to copy these files from the production web host to your local environment using a tool like `rsync`, `scp`, or `sftp`.

If you have installed Drush (via `composer require drush/drush`) then you may be able to use `drush sql:sync` to synchronize databases.

Before you do that, you might want to set up Drush aliases for local and remote environments.

Keep in mind, also, that `drush migrate` is used for a different purpose -- migrating site contents from another non-Drupal platform into your Drupal site.

As a thought experiment, let's say you had three copies of your Drupal site -- Dev, Stage, and Prod -- on one server (using three different Apache2 `VirtualHosts` with `DocumentRoot` set to `/var/www/dev`, `/var/www/stg`, and `/var/www/prd`).

In that case, you could use the `local_sync` function defined below to sync the database and files from a higher environment to a lower environment:

```
#!/bin/bash

function local_drush () {
  if [[ $# -eq 0 ]]; then
    echo "local_drush (set environment and execute project-local drush)"
    echo "Usage: local_drush [dev stg prd] [drush-args]"
    echo "Sets environment to dev stg or prod, then passes argv to local drush."
  fi
  if [[ $1 == 'dev' ]] || [[ $1 == 'stg' ]] || [[ $1 == 'prd' ]]; then
    RETURN_DIR=${PWD}
    cd /var/www/$1
    ./vendor/bin/drush ${@:2}
    cd ${RETURN_DIR}
  else
    echo "Invalid usage: first arg must be dev, stg, or prd."
  fi
}

function local_sync () {
    if [[ $# -lt 1 ]] || [[ $# -gt 2 ]]; then
    echo "local_sync (migrate db and files to a lower environment)"
    echo "Usage: local_sync <SOURCE> <TARGET>"
    echo "Set source and target environments, then sync db and files."
  fi
  if [[ $1 == 'prd' ]] || [[ $1 == 'stg' ]]; then
    SOURCE=$1
    if [[ $2 == 'stg' ]] || [[ $2 == 'dev' ]]; then
      TARGET=$2
      if [[ ( $1 = 'prd' && $2 == 'stg' ) || ( $1 = 'prd' && $2 == 'dev' ) || ( $1 = 'stg' && $2 == 'dev' ) ]]; then
        RETURN_DIR=${PWD}
        BACKUP_DIR=$HOME/sqldumps/${SOURCE}
        BACKUP_FILE=${BACKUP_DIR}/${SOURCE}_$(date -I).sql
        mkdir -p ${BACKUP_DIR}
        SOURCE_FILES=/var/www/${SOURCE}/web/sites/default/files/
        TARGET_FILES=/var/www/${TARGET}/web/sites/default/files/
        EXCLUDE_CSS=${SOURCE_FILES}css/
        EXCLUDE_JS=${SOURCE_FILES}js/
        EXCLUDE_PHP=${SOURCE_FILES}php/

        echo "Creating temporary database backup of ${SOURCE} environment ..."
        local_drush ${SOURCE} sql:dump > ${BACKUP_FILE}

        if [[ -s "${BACKUP_FILE}" ]]; then
          echo "Importing database backup into ${TARGET} environment ..."
          local_drush ${TARGET} sqlc < ${BACKUP_FILE}
          echo "Deploying staged configuration for ${TARGET} environment ..."
          local_drush ${TARGET} deploy
          echo "Copying public files from ${SOURCE} to ${TARGET} ..."
          rsync --exclude ${EXCLUDE_CSS} \
          --exclude ${EXCLUDE_JS} \
          --exclude ${EXCLUDE_PHP} \
          --delete --progress -av ${SOURCE_FILES} ${TARGET_FILES}
          echo "Rebuilding Drupal caches for ${TARGET} environment ..."
          local_drush ${TARGET} cache:rebuild
          echo "Import from ${SOURCE} to ${TARGET} completed successfully."
          echo "Removing database backup of ${SOURCE} environment ..."
          rm ${BACKUP_FILE}
          cd ${RETURN_DIR}
        else
          echo "Error: ${BACKUP_FILE} not found, cannot continue."
        fi
      else
        echo "Invalid usage: cannot copy from ${SOURCE} to ${TARGET}."
      fi
    else
      echo "Invalid usage: second arg must be stg or dev."
    fi
  else
    if [[ $1 == 'dev' ]]; then
      echo "Invalid usage: cannot migrate from dev to higher environment."
    else
      echo "Invalid usage: first arg must be prd or stg."
    fi
  fi
}

```

Note that the `local_sync` command depends on the `local_drush` command, which in turn depends on the local and remote projects containing a site-local Drush in `./vendor/bin/drush`, which is the recommended method of installing Drush. The shell script is non-standard, but it has the advantage of working whether or not you have configured Drush aliases properly.

I'll leave it to you as an exercise to figure out how to get the files from your remote server into your local environment. There's nothing wrong with doing all these steps manually, or with using Filezilla to transfer files via SFTP over SSH, but consider that the `rsync` command (on lines 49-52) can be used with a different `${SOURCE}` which can be on a remote server. The only other change you'd need to make would be to also `rsync` the `${BACKUP_FILE}` to your local (around line 42).

Ha! I see that you edited your question while I was writing my answer.

Your new question is a duplicate, answered here by Clive.

From the comments on that answer:

> The official way is for modules relying on the old way to explicitly write the code to make the updates happen in `hook_update_N`. All the gritty details are here: https://drupal.org/node/3034742

The workaround is to install the Devel Entity Updates module, which makes the old `drush entup` command available.

You're not supposed to use this in production, but it's OK to use in your local development environment as long as you don't promote your local DB to the production environment.

It sounds like you are only having this problem in your local, not in production, so this option may work for you.

Good luck!
> 3 votes

# Answer
Ok, I was able to do it quite easily with drush dumps.

First, I did a dump on the live server with:

```
drush sql:dump > db.sql

```

And then in my docker environment.

First I made sure I drop the tables from the database from Phpmyadmin.

Then I entered the docker image.

```
docker container exec -it <docker-container-id> bash

```

And then run drush with the name of the database I dumped from live(I use filezilla to transfer it to my local dev).

```
drush sql:cli < db.sql

```

Make sure that you download the public files with the images and add them to the files folder in local.

I first created the site and settings with an empty site. Then, added only the images since replacing the generated css, js and php in files created problems.

Hope this helps.
> 2 votes

---
Tags: 8, migrations
---


# Question
Title: Add custom field in exposed form in view
I have a view with exposed form. I want to add some **custom select field** in this exposed form. I have tried with **hook\_form\_alter()** but the field is rendered in form **submit** button wrapper which is strange. How can I add a custom field to another one filters ?

# Answer
You can define the field as a widget by messing with the `#info` part of the form in `hook_form_alter` \- it's not very elegant but hey.

```
$form['text_search'] = array(
  '#type' => 'textfield',
  '#description' => t('Or start typing here to find your competition.'),
  '#default_value' => '',
);
// Create widget from new form item.
$new_form_info = array(
  'filter-text_search' => array('value' => 'text_search'),
);
// Put new widget definition at top, so widget will appear
// first (change order to change to desired position).
$form['#info'] = $new_form_info += $form['#info'];

```
> 0 votes

# Answer
Yes, the answer is correct, but not clear You need to do so

```
/**
 * Implements hook_form_alter().
 */
function hayly_helper_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $query = Drupal::request()->query;
  $parent_category = $query->get('parent');

  if ($form_id == 'views_exposed_form') {
    $form['parent'] = [
      '#type' => 'hidden',
      '#title' => t('Parent'),
      '#default_value' => $parent_category ?? '',
    ];

    __hayly_insert_field_to_exposed_form($form, 'filter-parent', $parent_category, 0);
  }
}

function __hayly_insert_field_to_exposed_form(&$form, $field_key, $value, $insert_index) {
  // this value determines the position of the new element.
  $element_to_insert = array();
  $element_to_insert[$field_key] = array(
    'value' => $value,
  );

  $form_info_top = array_slice($form['#info'], 0, $insert_index);
  $form_info_bottom = array_slice($form['#info'], $insert_index);
  $new_form_info = $form_info_top + $element_to_insert + $form_info_bottom;
  $form['#info'] = $new_form_info;
}

```

If you have custom exposed filter twig - **don't forget** to print your new field in your `views-exposed-form.html.twig` `{{ form.parent }}` Also you can read **this answer** for more info
> 0 votes

---
Tags: views
---


# Question
Title: How do I migrate a nested paragraph field?
I have a wrapper paragraph, *Layout (para\_layout)*, which has the following fields.

>

I created the same paragraphs and field structure in a Drupal 9 website. I created the following custom migration plugin.

```
id: custom_para1
label: Custom paragraph migration
migration_group: custom
migration_tags:
  - node
source:
  plugin: d7_paragraphs_item
  bundle: para_layout
process:
  field_left_column: field_left_column
  field_right_column: field_right_column
destination:
  plugin: 'entity_reference_revisions:paragraph'
  default_bundle: para_layout

```

`drush mim custom_para1` shows it's processing the items, but I couldn't see any data moved to the respective field tables.

It works fine when the fields are normal text fields instead of paragraph fields.

# Answer
> 4 votes
Here is how I was able to achieve it.

**Paragraph Migration:**

```
id: fup_balance_csv_import

....
....

process:
  field_upload: field_upload
  field_download: field_download
  field_total: field_total

destination:
  plugin: entity_reference_revisions:paragraph
  default_bundle: fup_balance

```

**Node Migration:**

```
id: subscription_list_csv_import

.............
........

process:
  # Paragraphs field.
  pseudo_field_fup_details:
    -
      plugin: migration_lookup
      migration: fup_balance_csv_import
      source: title # Unique idetifier.
  field_fup_details:
    -
      plugin: sub_process
      source:
        - '@pseudo_field_fup_details'
      process:
        target_id: '0'
        target_revision_id: '1'

```

This is by using the standard Migration Process. Sometimes it might not be helpful then you need to write your own migration plugin. Below is the way to do it.

```
# Paragraphs Field.
field_country_time_zones:
  -
    plugin: country_timezones_paragraphs
    source:
      field_1: source_field_1
      field_2: source_field_2

```

**Migration Plugin :**

```
<?php

namespace Drupal\countries_list_migration\Plugin\migrate\process;

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\paragraphs\Entity\Paragraph;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;

/**
 * Provides a countries_timezones migration plugin.
 *
 * Usage:
 *
 * @code
 * process:
 *   bar:
 *     plugin: country_timezones_paragraphs
 *     source: source_field_name
 * @endcode
 *
 * @MigrateProcessPlugin(
 *   id = "country_timezones_paragraphs",
 *   handle_multiples = TRUE
 * )
 */
class CountryTimezonesParagraphs extends ProcessPluginBase implements ContainerFactoryPluginInterface {

  /**
   * Logger service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $logger;

  /**
   * Constructs a CountriesTimezones plugin.
   *
   * @param array $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin ID.
   * @param mixed $plugin_definition
   *   The plugin definition.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
   *   The logger service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerChannelFactoryInterface $logger) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->logger = $logger->get('countries_list_migration');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('logger.factory')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {

  $paragraphs =[];

   if (isset($value)) {
      $paragraphs[] = $this->createCountryTimezonesParagraphsItem($value);
    }

    return $paragraphs;
  }

  /**
   * {@inheritdoc}
   */
  public function multiple(): bool {
    return TRUE;
  }

  protected function createCountryTimezonesParagraphsItem(array $item): array {

    $paragraph = Paragraph::create([

      'type' => 'country_timezones',

      'field_1' => [
        'value'  => $item['field_1'],
      ],
      'field_2' => [
        'value'  => $item['field_2'],
      ],
    ]);

    $paragraph->save();

    return [
      'target_id' => $paragraph->id(),
      'target_revision_id' => $paragraph->getRevisionId(),
    ];
  }

}

```

Hope its Helpful.

# Answer
> 0 votes
As I said in the comment(s), the plugins currently ensure field collections are migrated to Paragraph bundles and same for d7 Paragraphs. Field data is not moved, this is a gap you currently need to fill yourself. So you have to write YAML migration definitions to do this.

I recently did a d7 to d9 migration that had this requirement (with Field Collections in the mix too that we ported).

You will need to create migration(s) with source plugins (the SQL queries that fetch the data you need to plug in) to do the following:

1. Migrate all actual paragraphs and their field data
2. Migrate paragraphs that hold paragraphs (holding the field data)
3. Migrate the records of the entities that reference the paragraphs holding the paragraphs from step 2 (use migration\_lookup to find their new destination ids in D9).

---
Tags: 8, migrations, paragraphs
---


# Question
Title: How can I index arbitrarily related entity data in a Search API index the same as if it were an entity reference?
I have a site with three content types:

* Collection (references a paragraph that references Articles)
* Article (references Authors)
* Author

I have listing page displays for each of these content types. The displays have facets based on the above relationships.

* The Collection listing page has a filter by Author. This relationship is not a direct relationship, rather a chain of references: Collection \> Paragraph \> Article \> Author.
* The Article listing page has a filter by Collection. This relationship is also not a direct reference, rather a chain of reverse references: Article \< Paragraph \< Collection.
* The Author listing page also has a filter by Collection which is also a chain of reverse references: Author \< Article \< Paragraph \< Collection.

As far as I know, because these facets are not relying on direct relationships, I can't index the data / create the facets with what search\_api provides out of the box.

I'd like to index this related entity data with the entity. For example, index a Collection's Articles as though it were a direct entity reference. I want to index an Article's Collections and an Author's Collections in the same way. I'd like to do it this way as it should naturally play nicely with facets and other components. Additionally, this will put most of the processing in the background while content is being indexed rather than as a display is being built or a query is executed

How can this be achieved?

# Answer
This is possible with custom search\_api processors.

First, I created an abstract class to use as a base for the shared functionality. I.e. a method to index arbitrary entity data with a piece of content.

```
namespace Drupal\my_module\Plugin\search_api\processor;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Processor\EntityProcessorProperty;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Utility\Utility;

/**
 * Base plugin class for indexing arbitrarily related entity data.
 *
 * This can be helpful to index properties of entities referencing an entity or
 * entities related in some other arbitrary way.
 *
 * @package Drupal\my_module\Plugin\search_api\processor
 */
abstract class RelatedEntityBase extends ProcessorPluginBase {

  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
    $plugin_definition = $this->getPluginDefinition();
    $properties = [];

    if (!$datasource || $datasource->getEntityTypeId() !== $this->getIndexedEntityTypeId()) {
      return $properties;
    }

    $definition = [
      'label' => $plugin_definition['label'],
      'description' => $plugin_definition['description'],
      'type' => 'entity:' . $this->getRelatedEntityTypeId(),
      'processor_id' => $this->getPluginId(),
      'is_list' => TRUE,
    ];
    $property = new EntityProcessorProperty($definition);
    $property->setEntityTypeId($this->getRelatedEntityTypeId());
    $properties[$this->getPluginId()] = $property;

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public function addFieldValues(ItemInterface $item) {
    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $item->getOriginalObject()->getValue();

    $to_extract = [];
    foreach ($item->getFields() as $field) {
      $datasource = $field->getDatasource();
      $property_path = $field->getPropertyPath();
      [$direct, $nested] = Utility::splitPropertyPath($property_path, FALSE);
      if ($datasource && $datasource->getEntityTypeId() === $entity->getEntityTypeId() && $direct === $this->getPluginId()) {
        $to_extract[$nested][] = $field;
      }
    }

    foreach ($this->getRelatedEntities($entity) as $relation) {
      $this->getFieldsHelper()
        ->extractFields($relation->getTypedData(), $to_extract, $item->getLanguage());
    }
  }

  /**
   * Get an array of related entities.
   *
   * This should return an array of fully loaded entities that relate to the
   * $entity being indexed.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being indexed.
   *
   * @return array
   *   An array of entities related to $entity.
   */
  abstract protected function getRelatedEntities(ContentEntityInterface $entity): array;

  /**
   * Get the entity type id of the entity being indexed.
   *
   * This is the entity type of the $entity passed to
   * $this->getRelatedEntities().
   *
   * @return string
   *   An entity type id string, e.g. 'node', 'media', or 'taxonomy_term'.
   */
  abstract protected function getIndexedEntityTypeId(): string;

  /**
   * Get the entity type id of the related entities.
   *
   * This is the entity type of the items returned from
   * $this->getRelatedEntities().
   *
   * @return string
   *   An entity type id string, e.g. 'node', 'media', or 'taxonomy_term'.
   */
  abstract protected function getRelatedEntityTypeId(): string;

}

```

Next, I created plugin classes that extended my abstract class for each case (Collection's Authors, Article's Collections, Author's Collections). For example, to index data from an Article's Collections as part of the Article's indexed data:

```
namespace Drupal\my_module\Plugin\search_api\processor;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\my_module\Plugin\search_api\processor\RelatedEntityBase;

/**
 * Index properties from Collections referencing an Article.
 *
 * @SearchApiProcessor(
 *   id = "my_module_article_collections",
 *   label = @Translation("Article's Collections"),
 *   description = @Translation("Index properties from Collections referencing this Article."),
 *   stages = {
 *     "add_properties" = 0,
 *   },
 * )
 */
class ArticleCollections extends RelatedEntityBase {

  /**
   * {@inheritdoc}
   */
  protected function getRelatedEntities(ContentEntityInterface $entity): array {
    return my_function_to_get_article_collections($entity)
  }

  /**
   * {@inheritdoc}
   */
  protected function getIndexedEntityTypeId(): string {
    return 'node';
  }

  /**
   * {@inheritdoc}
   */
  protected function getRelatedEntityTypeId(): string {
    return 'node';
  }

}

```

This allowed me to index data from a Collection as part of an Article's data, for example the Article's Collection Ids (i.e. the Ids of Collections referencing the Article). I can index any field from the Collection - by selecting the field I want in the UI - the same as if the Article had an entity reference field referencing the Collection. (Note: before you can index any fields with the custom processor, you must first enable it on the Processor tab for your index.)

This all worked great, however, my indexed data did not stay synced with reality. For example, if I added a new Article to a Collection, the indexed data for that new Article would not get updated with information for the new Collection. I.e. the Article was not getting re-indexed if a Collection referencing it was updated. I resolved this with a hook\_ENTITY\_TYPE\_update() implementation that marks dependent Articles to be re-indexed when a Collection is saved.

```
use Drupal\node\NodeInterface;

/*
 * Implements hook_ENTITY_TYPE_update().
 */
function my_module_node_update(NodeInterface $node) {
  if ($node->bundle() == 'collection') {
    $articles = [];

    // Gather all Articles that this Collection references.
    $articles = my_function_to_get_collection_articles($node);
    // Also gather any Articles that were referenced before this save, but are
    // no longer referenced.
    $original_node = isset($node->original) ? $node->original : NULL;
    if ($original_node instanceof NodeInterface) {
      $articles += my_function_to_get_collection_articles($original_node);
    }

    // Mark the articles to be re-indexed.
    foreach ($articles as $article) {
      /** @var \Drupal\search_api\Plugin\search_api\datasource\ContentEntityTrackingManager $tracking_manager */
      $search_api_tracking_manager = \Drupal::service('search_api.entity_datasource.tracking_manager');

      $indexes = $search_api_tracking_manager->getIndexesForEntity($article);
      if (!empty($indexes)) {
        $item_ids = [];
        foreach ($article->getTranslationLanguages() as $langcode => $language) {
          $item_ids[] = $article->id() . ':' . $langcode;
        }
        foreach ($indexes as $index) {
          $index->trackItemsUpdated('entity:node', $item_ids);
        }
      }
    }
  }
}

```

After all of this, I can safely index data from arbitrarily related entities.
> 3 votes

---
Tags: search
---


title: User role permissions per language

text:
# Question
Title: User role permissions per language
Drupal 8 supports Multilingual (i18n) in core. I was wondering, is it possible to restrict users to be able to add/edit content for only one specific language?

# Answer
> 3 votes
Yes, you can use `hook_node_access` to control access to add/edit for the node.

For example, you can check the value of:

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

```

and return the access result accordingly

# Answer
> 1 votes
Working code example:

```
use Drupal\node\NodeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;

/**
 * Implements hook_node_access().
 *
 * Prevent editor role to edit and/or delete fr/en nodes. Quick and dirty.
 */
function MYMODULE_node_access(NodeInterface $node, $op, AccountInterface $account) {

  $roles = $account->getRoles();

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

  if (!in_array('administrator', $roles) && !in_array('publisher', $roles)) {
    if ($op == 'update' || $op == 'delete') {
      if ($lang == 'en' || $lang == 'fr') {
        return AccessResult::forbidden();
      }
    }
  }

  // No opinion.
  return AccessResult::neutral();
}

```

# Answer
> 0 votes
The Allowed Languages module makes it possible to set restrictions on which content a user can edit based on language.

https://www.drupal.org/project/allowed\_languages

Once installed against each user profile you can check which languages the user can edit as shown below.

---
Tags: 8, users, i18n-l10n
---


title: How to make a vertical menu properly?

text:
# Question
Title: How to make a vertical menu properly?
Good morning all,

Today I would like to ask a simple question about the menus. How do you make a clean sidebar that takes up the entire length of the page? In general, the themes offer only natively horizontal menus that occupy the entire width.

To have a sidebar, you must put the menu in a block side position and make the necessary adjustments with CSS?

# Answer
Yes, theme regions and CSS are what you need. Basically, from `admin/structure/block` you place your menu block into one of your theme's region. And then, with CSS you adjust the style of the region to your needs.

If the HTML structure does not match your needs, you can also alter the page template of your theme (`page.html.twig` file). If this file does not exist in your theme, just copy it from `\core\themes\classy\templates\layout\page.html.twig` to `themes\yourtheme\templates\optionalsubfolder\page.html.twig`). This file allows you to modify the HTML structure of your page and consequently, the position of your page regions within the HTML.

e.g. If I place my Menu Block into "Sidebar first" region, I know that `{{ page.sidebar_first }}` twig variable is the one that contains the display of the menu.
> 3 votes

---
Tags: theming, navigation, 9, bootstrap-theme
---


title: Add CSS classes to theme function

text:
# Question
Title: Add CSS classes to theme function
I'm implementing `hook_theme()` in a custom module.

```
function monitor_chart_theme() {
  return array(
    'chart_monitor' => array(
      'template' => 'monitor_chart',
      'variables' => array(
        'container' => null,
        'theme' => null,
        'classes' => null,
        'chart' => null,
      ),
    ),
  );
}

```

On the template file, I would like to add additional CSS classes the theme function gets in its parameters. I declared a preprocess function with the following code.

```
function monitor_chart_preprocess_chart_monitor(&$vars) {
  if ($vars['classes']) {
    foreach ($vars['classes'] as $class) {
      $vars['classes_array'][] = $class;
    }
  }
}

```

The template file contains the following code.

```
<div id="<?php print $container; ?>" class="<?php print $classes; ?>"></div>

```

If I don't use the preprocess function, the *class* attribute of `<div>` contains just `'chart-monitor'`, the name of the theme function.

Is there a more elegant way to pass extra variables to a template file?

# Answer
Yes, arguments can be passed when calling the theme function:

```
$tpl_data = array(
    'data1' => $data1,
    'data2' => $data2,
);

return theme('my_theme', $tpl_data);

```

Then in the template $data1 and $data2 will be available.

To add a variable passed here to the default drupal class variable in the template we could add the line:

```
$class .= " $data1";

```

before the $class variable is printed.

However I think it's questionable whether or not this method is preferable to your original.
> 0 votes

# Answer
The only way to pass values to a template file used for a theme function is adding them to the array passed as second argument of `theme()`. In the case of that *chart\_monitor* theme function, `theme('chart_monitor', array('classes' => array('new_chart_class')));` would add *new\_chart\_class* to the CSS classes.

`monitor_chart_preprocess_chart_monitor()` doesn't need a `foreach()` loop.

```
function monitor_chart_preprocess_chart_monitor(&$vars) {
  if (is_array($vars['classes']) && !empty($vars['classes'])) {
    $vars['classes_array'] = array_merge($vars['classes_array'], $vars['classes']);
  }
}

```

As side note, *chart\_monitor* is the CSS class added by `template_preprocess()` which is called for every theme function for which preprocess functions aren't disabled.

```
// Initialize html class attribute for the current hook.
$variables['classes_array'] = array(
  drupal_html_class($hook),
);

```

`template_process()` then initializes the `$classes` variable a template file gets.

```
// Flatten out classes.
$variables['classes'] = implode(' ', $variables['classes_array']);

```
> 0 votes

---
Tags: 7, theming
---


title: How to instal Aegir without root access

text:
# Question
Title: How to instal Aegir without root access
The Aegir documentation states that root access is required for installation, but I have seen comments suggesting it is possible to instal without root. If this is correct can anyone point me in the right direction?

Background: I host a few non profit sites with an Aegir service provider who has provided great service. But the sites have grown and the cost has become a challenge for them. I don't have the skills for a self-managed server, so I am hoping to be able to find someone to instal on a fully managed server where someone else can maintain it and keep it secure etc.

# Answer
> 2 votes
> The Aegir documentation states that root access is required for installation, but I have seen comments suggesting it is possible to instal without root. If this is correct can anyone point me in the right direction?

In addition to the installation and configuration of web and database servers, the standard installation of Aegir requires the creation of a user account and the deployment of one or more `sudoers` files.

While I'm sure there are creative ways to work around some of this, each step down that path gets you further and further from a standard setup. The more bespoke your deployment becomes the more time and effort will be required to ensure its security and reliability.

I highly recommend against pursing this path.

> I host a few non profit sites with an Aegir service provider who has provided great service. But the sites have grown and the cost has become a challenge for them.

Have you offered to cover the costs for your current Aegir service provider to scale up their hardware, move your sites to a dedicated server, etc.? This is likely to be the most cost-effective approach, since they're presumably already tooled up for the deployment and management of Aegir servers.

---
Tags: aegir
---


# Question
Title: Is there a way to add a variable to the variable table through the Admin menu
Can I set a new key/value pair for the variable table from the Administrator `admin/config` menu in a Drupal 7 site? I don't want to have to create a one-time custom module that only contains a `variable_set()` function.

# Answer
> 2 votes
I don't know about your PHPMyAdmin installation, but mine won't allow me to insert the data into the `value` column in the `variable` table, as it's a `blob` column. The data in that column is serialized as you can see in the implementation. As a result, adding it via the "Insert" tab is problematic. ***Instead***, I'll explain how you can add it in the "SQL" tab.

If you use an online serializer site, you can generate the proper data for the variable named `foo`

* if the `value` column is the string `bar`, it will give you `s:3:"bar";`
* if the `value` column is the array `['bar','baz']` it will give you
```
a:2:{i:0;s:3:"bar";i:1;s:3:"baz";}

```

Then, use the "SQL" tab (instead of the "Insert" tab) and craft the appropriate insert statement.

If the value is a string:

```
INSERT INTO `variable`(`name`, `value`) VALUES ('foo','s:3:"bar";')

```

If the value is an array:

```
INSERT INTO `variable`(`name`, `value`)
VALUES ('foo','a:2:{i:0;s:3:"bar";i:1;s:3:"baz";}')

```

# Answer
> 1 votes
```
drush vset <variable-name> <variable-value>

```

# Answer
> 1 votes
You can override a persistent variable in the settings.php file. default.settings.php file contains example code for achieving that.

```
// Change the site name to 'My Drupal site'.
$conf['site_name'] = 'My Drupal site';

```

---
Tags: 7
---


# Question
Title: Can we add custom CSS class in \Drupal::messenger()->addStatus()?
I did some research regarding adding custom CSS classes in the message but could not find the actual solution.

As per Drupal 9 documentation:

```
\Drupal::messenger()->addMessage(t($message));

public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE);

```

I don't find the option to add CSS class. On the frontend, I would like to add custom CSS class `custom-messages--status` in the outputted `<div>` so that I can format it as desired:

```
<div class="col-md-12 messages custom-messages--status">Successfully submitted.</div>

```

# Answer
`addMessage()` accepts a string or a MarkupInterface as its argument. If you intend to pass HTML to `addMessage()`, you need to construct a render array for that markup then explicitly render it. For example:

```
$build = [
  '#type' => 'container',
  '#markup' => t('Successfully submitted'),
  '#attributes' => ['class' => ['custom-messages--status']],
];
$message = \Drupal::service('renderer')->renderPlain($build);
\Drupal::messenger()->addMessage($message);

```

Of course, you should inject these services when possible rather than using static `\Drupal::` calls.
> 5 votes

---
Tags: theming, 9, status-messages
---


# Question
Title: Override theme template from module without implementing a theme
I have a project on D8 where I don't use any custom themes, I use default seven theme for everything because there is no need of any visual theming.

So My question: Is it possible to override theme template using module? without implementing theme at all?

I have a case where I want to add custom third party generated value in node's template.

This is what I have done:

Implemented custom template:

```
function MY_MODULE_theme($existing, $type, $theme, $path) {
  return array(
    'video_teaser' => [
      'template' => 'video_teaser',
      'variables' => NULL,
    ]
  );
}

```

Implemented hook\_theme\_suggestions\_alter:

```
function MY_MODULE_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
  if(in_array('node__teaser', $suggestions)){
    $suggestions[] = 'video_teaser';
  }
}

```

And this, works, instead of default template, my `video_teser` template is used.

BUT: I have a problem passing default node variables to my custom template, they aren't passed automatically and I couldn't find the way to do this using current approach.

# Answer
> 21 votes
You can do this in `hook_theme()` with the base hook 'node':

```
function MY_MODULE_theme($existing, $type, $theme, $path) {
  return [
    'node__video_teaser' => [
      'template' => 'node--video-teaser',
      'base hook' => 'node',
    ],
  ];
}

```

You don't need to define the base hook. Drupal can discover this automatically. What's important is that you use the double underscore, because this works exactly like the theme suggestions. After Drupal found the base hook it will clone the preprocess functions of this hook and will provide the variables for the node.

# Answer
> 1 votes
Yes, but for some cases it is not clear, I find out that for every cases you can do something like that. You need to do something like that in your **custom\_module\_preprocess**

```
/**
 * Implements hook_preprocess().
 */
function MY_MODULE_preprocess(&$variables, $hook, &$info) {
  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name == 'custom_route_name') {
    $module_path = drupal_get_path('module', 'MY_MODULE');
    if ($hook == 'html' && $variables['theme_hook_original'] == 'html') {
      $info['theme path'] = $module_path;
      $info['path'] = $module_path . '/templates';
    }
    if ($hook == 'page' && $variables['theme_hook_original'] == 'page') {
      $info['theme path'] = $module_path;
      $info['path'] = $module_path . '/templates';
    }
  }
}

```

It tells drupal to suggest ***html.html.twig*** and ***page.html.twig*** templates for exact route from your custom module's /templates directory.

**Additionally** *MY\_MODULE\_theme\_registry\_alter(&$theme\_registry)* will work only once, only after clear caches and will not be dynamic, so you shouldn't use it!

---
Tags: nodes, 8, entities, theming
---


# Question
Title: How do I enable developer/debugging mode?
I'm installing Drupal 8 core (using the standard profile). Whenever I get an exception/error, I get a blank page with "The website has encountered an error. Please try again later." (As shown in the image below.) This is shown as text and has no details or tracing about the error.

How do I enable developer/debugging mode?

# Answer
> 64 votes
On a development site I would suggest uncommenting the following lines in the `settings.php` file. `emacs sites/default/settings.php`

```
if (file_exists(__DIR__ . '/settings.local.php')) {
  include __DIR__ . '/settings.local.php';
}

```

and then copying the file `example.settings.local.php` from `/sites` folder to `/sites/default` folder and rename it to `settings.local.php`

```
cp sites/example.settings.local.php sites/default/settings.local.php

```

In addition to adding the following setting

```
$config['system.logging']['error_level'] = 'verbose';

```

it also adds a few other settings which will help you in debugging and making development easier. If you don't want any of them in particular, you can always comment them out.

Note : If you think adding a `file_exists` call to each page will slow down the site, you can always remove it in the production code.

# Answer
> 20 votes
Add the following line in the settings.php file.

```
$config['system.logging']['error_level'] = 'verbose';

```

See Make developer tricks / tools for D8 more discoverable/obvious for more details.

# Answer
> 17 votes
An easy option, if you have access to Drupal Console, is to enable the development/debugging environment using `drupal site:mode dev`; when you are ready to change it back to production, use `drupal site:mode prod`.

# Answer
> 13 votes
Here is the variable with the different values :

```
$config['system.logging']['error_level'] = 'all'; // hide|some|all|verbose

```

These error levels are defined in the Drupal Core Bootstrap, here is the details for each one :

```
/**
 * Error reporting level: display no errors.
 */
const ERROR_REPORTING_HIDE = 'hide';

/**
 * Error reporting level: display errors and warnings.
 */
const ERROR_REPORTING_DISPLAY_SOME = 'some';

/**
 * Error reporting level: display all messages.
 */
const ERROR_REPORTING_DISPLAY_ALL = 'all';

/**
 * Error reporting level: display all messages, plus backtrace information.
 */
const ERROR_REPORTING_DISPLAY_VERBOSE = 'verbose';

```

# Answer
> 4 votes
To get basic error messages in Drupal 8 you don't need to touch any code at all. Just go to:

> /admin/config/development/logging

and set "Error Messages to Display" to an appropriate level.

You can also view your recent errors through the admin interface:

> /admin/reports/dblog

That said, other answers about settings files **are** relevant – you should only ever make errors visible on your development site, not production.

Overwritten $config objects/keys in settings.php won't be written to the sync directory when exporting, so providing your settings.php is in .gitignore, you can continue to safely use the configuration system to sync between clones of the same site.

# Answer
> 2 votes
If you only have access to Drush (no Drupal console) and you are working on a remote server, you can set the config to show all errors:

> drush MYSITE.MYENV config-set system.logging error\_level all -y

This is useful if you are getting a WSOD on a remote server and can't troubleshoot without more info.

# Answer
> 1 votes
Adding `$config['system.logging']['error_level'] = 'verbose';` to the *sites/default/settings.php* should log the error.

I will rather suggest the following steps.

* Enable Twig debug and disable the cache for development purpose
* Copy the *sites/example.settings.local.php* file to *sites/default/settings.local.php*
* Un-comment the `$settings['cache']['bins']['render'] = 'cache.backend.null';` line in the *settings.local.php* file
* On the *site/default/settings.php* file, un-comment the following lines

```
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
  include $app_root . '/' . $site_path . '/settings.local.php';
}

```
* Add the following lines to the *sites/development.services.yml* file

```
parameters:
  http.response.debug_cacheability_headers: true
  twig.config:
    debug: true
    auto-reload: true
    cache: false
services:
  cache.backend.null:
    class: Drupal\Core\Cache\NullBackendFactory

```

when all steps completed change directory to root dir and rebuild cache \`\`\`

* Run `drush cr`

You should now see the debug output. Don't forget to undo the changes before deploying to production.

# Answer
> 0 votes
You can also install the the Devel and Devel kint modules. This will let you debug using `kint(xyz)`.

# Answer
> 0 votes
Blank page with "The website has encountered an error. Please try again later." won't take you anywhere. To get more information out of your Drupal 8 site you can enable following configs.

```
$config['system.logging']['error_level'] = 'verbose';

```

For local Drupal 8 development, you should also enable error reporting, display errors and display startup error to help you further debugging and fixing major runtime error.

```
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);

```

You can find more information about TWIG debugging and enabling debugging with Drupal Console in the blog post below.

https://www.drupixels.com/blog/enable-debug-mode-and-error-reporting-local-development-drupal-8

---
Tags: 8, debugging
---


# Question
Title: Jquery to move field above the table - changes during page refresh
I have the below jquery code to move a multiselect field and a button above the table.

```
jQuery(document).ready(function($){
  $('#views-form-resources-block-1 .form-item-ka-types').insertBefore($('#views-form-myblock-block-1 table'));
  $('#views-form-resources-block-1 #edit-actions--2').insertAfter($('#views-form-myblock-block-1 .form-item-ka-types'));
});

```

In this code I am moving the ka-types field above myblock view. Pictorial representation as below:

This code works fine when I load the page at first. But when I refresh the page, the multiselect comes down and shows messed up. again when I refresh the page it works fine. not sure what the problem is. Any help on how this can be fixed?

# Answer
> 1 votes
The below is my solution for this:

```
(function($, Drupal) {
  Drupal.behaviors.searchResults = {
    attach: function() {
      $('#views-form-resources-block-1 .form-item-ka-types')
        .insertBefore($('#views-form-myblock-block-1 table'));
      $('#views-form-resources-block-1 #edit-actions--2')
        .insertAfter($('#views-form-myblock-block-1 .form-item-ka-types'));
    }
  };
}(jQuery, Drupal));

```

@Kevin is correct. When the form rebuilds the DOM changes are lost. Better understanding of Drupal behaviors link has great explanation and examples of Drupal Behavior.

---
Tags: views, javascript, ajax, 9
---


# Question
Title: How do you set the file extension while uploading images through file_save_data?
I am trying to create a product with a product image on drupal using API. I have created a custom module for the same under which following code to upload my image to the Drupal:

```
$path = base64_decode($encodedData['content']);
$data= $this->grabImage($path);
$file = file_save_data($data, null, FileSystemInterface::EXISTS_REPLACE);

$id = $file->id();

```

I am using file\_save\_data drupal method to upload the image successfully and it gets uploaded to the following drupal directory:

> mystore2/web/sites/default/files

Now, I am using this **$file-\>id() //say 53** value in my product array to connect this image to my product.

```
$image['field_picture'] = [[
    "target_id" => 53,
    "alt" => 'abcd',
    "width"=> 224,
    "height"=> 225,
    "title" => '',
    "target_type" => "file"
]];

```

Follow response I am receiving from API:

```
> message:
>     Unprocessable Entity: validation failed.
>     field_picture.0: Only files with the following extensions are allowed: png gif jpg jpeg.\n

```

So, How can I pass extension in **file\_save\_data()**? Or how can I add this image to my product?

I am using Drupal version ^9.

# Answer
You need to pass the file path, including its name and extension, as the `$destination` parameter:

```
$file = file_save_data($data, 'public://path/to/file.ext', FileSystemInterface::EXISTS_REPLACE);

```
> 0 votes

---
Tags: files, 9
---


# Question
Title: Programmatically create a paragraph entity reference field
I need to create a config page, that is able to add social media information ( name and icon ), the user must be able to add as many social medias as it wants. To do so, I'we created a paragraph with two fields: name & icon, then I want to add this paragraph as an entity reference field programmaticaly in my config page, the paragraph machine name is social\_media.

I tried this, but it didn't work:

```
public function buildForm(array $form, FormStateInterface $form_state) {

    $form['social_media_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Social Media Configuration'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    ];

    $field = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Type'))
      ->setDescription(t('The Paragraphs type.'))
      ->setSetting('target_type', 'paragraph')
      ->setSetting('handler', 'default')
      ->setSetting('handler_settings',['target_bundles'=>
                      ['social_media'=>'social_media']
          ]);

    $form['social_media'] = $field;

    return parent::buildForm($form, $form_state);
  }

```

the page is shown but without a form:

# Answer
> 1 votes
The following code did the trick for me when adding the field to custom entity. This provides a reference to the paragraph and the revision. Just to note out that when the paragraph is edited, the reference field will still refer to the old revision. The field type is **entity\_reference\_revisions** and my paragraph type

```
$fields['social_media_id'] = BaseFieldDefinition::create('entity_reference_revisions')
  ->setLabel(t('Skill'))
  ->setDescription(t('The ID of the skill'))
  ->setRevisionable(TRUE)
  ->setSetting('handler', 'default:paragraph')
  ->setSetting('handler_settings', ['target_bundles' => ['social_media' => 'social_media']])
  ->setSetting('handler_settings', ['negate' => 0])
  ->setTranslatable(TRUE)
  ->setDisplayOptions('form', [
    'type' => 'entity_reference_autocomplete',
    'weight' => 5,
    'settings' => [
      'match_operator' => 'CONTAINS',
      'size' => '60',
      'autocomplete_type' => 'tags',
      'placeholder' => '',
    ],
  ])
  ->setDisplayConfigurable('form', TRUE);

```

# Answer
> 0 votes
The code below uses the Paragraphs Classic widget for the form display and allows to add as many paragraph items as you need

```
$fields['my_paragraph'] = BaseFieldDefinition::create('entity_reference_revisions')
  ->setLabel(t('My paragraph'))
  ->setDescription(t('My paragraph description.'))
  ->setCardinality(-1)
  ->setRevisionable(TRUE)
  ->setSetting('target_type', 'paragraph')
  ->setSetting('handler', 'default:paragraph')
  ->setSetting('handler_settings', ['target_bundles' => ['my_paragraph' => 'my_paragraph']])
  ->setTranslatable(TRUE)
  ->setDisplayOptions('form', [
    'type' => 'entity_reference_paragraphs',
    'weight' => 12,
    'settings' => [
      'title' => 'Paragraph',
      'title_plural' => 'Paragraphs',
      'edit_mode' => 'open',
      'add_mode' => 'dropdown',
      'form_display_mode' => 'default',
      'default_paragraph_type' => '',
    ],
  ])
  ->setDisplayConfigurable('form', TRUE)
  ->setDisplayConfigurable('view', TRUE);

```

---
Tags: 8, entities, paragraphs
---


# Question
Title: How can I set the language of nodes in bulk to existing content when making a site multilingual?
I'm making an existing site with a few thousand nodes multiligual. Source is English. How can I set all of the existing content to langauge:English instead of neutral all in one bulk command?

# Answer
Here's one way to do this that doesn't involve writing any code.

1. Install the VBO module.
2. Create a new view showing **Content** of whatever content types you need to convert. Use the **Fields** display method.
3. Add a field **Bulk operations: content**.
4. Check **Modify entity values** and select **Language**.
5. Choose **Apply**.
6. Save the view (add any other filters if necessary to limit the selection) and go to the created view page.
7. From the view page, choose the option to select all items on all pages and execute the **Modify entity values** action.
8. Choose the language and click next.

If you have an extremely large number of nodes, you may need to play around with the queuing settings so that not everything is executed at once, but I would try doing it in one pass first because hey, if it works, you're done (note: always test on a backup first!)
> 12 votes

# Answer
Here's how I bulk set the language on my site:

1. Using Views Bulk Optimization, select all nodes you want to change, use the 'Change value' option and then change the value to language you want.
2. After you've done this, the field values will not appear when editing the nodes, so you need to run a script, e.g. `UPDATE database.field_data_body SET language='en' WHERE bundle='page'` (use your db name, field name, and bundle as appropriate).

IMPORTANT: If you have any custom url aliases, they will be overwritten with this approach, due to a problem with pathauto, but there's a work-around: first go to /admin/config/search/path/settings, and change the 'Update action' option to 'Do nothing. Leave the old alias intact.', then change this value back once you've run your Views Bulk Operations action.

(I've now answered three Stack questions with this answer, hopefully it won't be considered spam).
> 4 votes

# Answer
In reference to the accepted answer here is a views export that does just that. https://gist.github.com/dasginganinja/7bfa862f379ade6e0f0f

Do keep in mind that this just changes the node's language. You will also need to update the affected database fields' language (i.e. body, tablefield, etc).
> 1 votes

# Answer
I wrote this little script that replaces all **langcode** fields with a new language code **across all tables** in your Drupal 9 (and possibly 8) installation.

* Place it in your Drupal ROOT/web folder
* Change the `$newlang` and `$oldlang` variables.
* Optionally add more conditions
* Execute it: `php myscript.php`

Be aware that this does not do any checking or confirmations so be sure that you know what you are doing :-)

```
<?php
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;

$autoloader = require_once 'autoload.php';

$kernel = DrupalKernel::createFromRequest(Request::createFromGlobals(), $autoloader, 'prod');
$kernel->boot();

/** @var \Drupal\Core\Database\Driver\mysql\Connection $connection */
$connection = \Drupal::service('database');

$tables = $connection->query('SHOW TABLES');

$newlang = 'en';
$oldlang = 'nl';

foreach ($tables as $table) {
  $tableName = $table->Tables_in_db;
  $cols = $connection->query('SHOW COLUMNS FROM '. $tableName);
  foreach ($cols as $col) {
    if ($col->Field == 'langcode') {
      echo 'Found langcode field in '. $tableName .'. Updating...' . "\n";
      try {
        $connection->update($tableName)
          ->fields([
            'langcode' => $newlang
          ])
          ->condition('langcode', $oldlang, '=')
          ->execute();
      }
      catch (\Exception $e) {
        echo 'Got exception: '. $e->getMessage() . "\n";
      }
    }
  }
}

```
> 1 votes

# Answer
In drupal 9, here is my solution : Here is what I done : in sql :

```
UPDATE node SET langcode='fr'
UPDATE node_field_data SET langcode='fr'
UPDATE node_field_revision SET langcode='fr'

```

now you have to update all the fields contains in the node type.

```
UPDATE node__body SET langcode='fr'
UPDATE node__comment SET langcode='fr'
UPDATE node__field_background_image SET langcode='fr'
UPDATE node__field_category SET langcode='fr'
UPDATE node__field_client_name SET langcode='fr'
UPDATE node__field_date SET langcode='fr'
UPDATE node__field_image SET langcode='fr'
UPDATE node__field_images SET langcode='fr'
UPDATE node__field_intro SET langcode='fr'
UPDATE node__field_paragraph SET langcode='fr'
UPDATE node__field_position SET langcode='fr'
UPDATE node__field_tags SET langcode='fr'
UPDATE node__field_video SET langcode='fr'
UPDATE node__simplenews_issue SET langcode='fr'

```

Then i redefine all the pattern in FR in pathauto.

I installed VBO modules, I created a view for listing all nodes, I added a vbo action field and run update url alias. Done !
> 0 votes

---
Tags: 7, i18n-l10n
---


title: Join with Like in Views Plugin

text:
# Question
Title: Join with Like in Views Plugin
Attempting to do a join with a LIKE operand in a Views filter plugin. The query looks similar to below; however, when it executes, the LIKE operand is not taken into account and the join is done with '='. Is there a way to do this type of join?

```
$configuration = [
     'type' => 'INNER',
     'table' => 'path_alias',
     'field' => 'CONCAT(\'%\', path_alias.path)',
     'left_table' => 'redirect',
     'left_field' => 'redirect_redirect__uri',
     'operator' => 'LIKE',];

   $join = Views::pluginManager('join')->createInstance('standard', $configuration);
   $this->query->addRelationship('path', $join, 'path_alias');

```

This is the query we are trying to execute:

```
SELECT *
FROM redirect AS redirect
INNER JOIN path_alias AS path ON
  redirect.redirect_redirect__uri LIKE CONCAT('%', path.path);

```

# Answer
> 1 votes
There is a patch available that provides a solution to this problem: https://www.drupal.org/project/redirect/issues/2981544.

---
Tags: 8, views, database
---


# Question
Title: Configuration synchronization page throwing 504 timeout error
This issue is happening on every level above my vagrant local box. It's a brand new website and a few days back I had to copy my database from local to staging environment. Once done, I ran drush -cim to import the files from config/sync directory into the database.

no error while running the drush -cim command and also all the UUIDs match from local to staging environment. However, on loading the config/sync page in the front end, I constantly see 504 timeout error. Screenshot as below:

The watchdog does not have any error and I saw just one error message:

This error is not constant and I am unable to reproduce this error as well. any help on how I can bring the config/sync page back up??

# Answer
> 0 votes
Finally, I got this fixed. Apache log did not have any evidence for this issue. However, on the config/sync page in the 'Import' tab, I saw an error as "/var/www/html/config/sync" folder is not writable. Making this folder permission to 760 fixed the issue for me.

---
Tags: configuration, 9
---


# Question
Title: How to unset fieldset in hook_form_alter?
I want to unset a **fieldset** programmatically which is available in the content type. I tried to unset like this in `hook_form_alter`,

```
 unset($form['field_first_deposit']);

```

But it doesn't work, where as hiding individual fields works in the same format. I tried hide() too , but still it doesn't work.

Any help will be much appreciated and Thanks in advance.

# Answer
> 3 votes
The mentioned solution `['#access'] = false` will be a possible solution, but if it's not working you may try:

```
/**
 * Implements hook_form_FORM_ID_alter().
 */
function YOUR_MODULE_form_FORM_ID_alter(&$form, &$form_state) {

  $form['#after_build'][] = 'YOUR_MODULE_form_FORM_ID_after_build';

}

/**Replace MACHINE_NAME with your fieldset group machine name.**/

function YOUR_MODULE_form_FORM_ID_after_build(&$form, &$form_state) {

  // Hide the B2B group.
  field_group_hide_field_groups($form, array('group_MACHINE_NAME'));

  return $form;
}

```

# Answer
> 1 votes
The Drupal form API has the `#access` element for fields that toggles visibility. If you need hide a fieldset, you'll need to do both.

```
$form['field_first_deposit']['#access'] = false;
hide($form['field_first_deposit']);

```

# Answer
> 1 votes
You can try below code :

```
$form['field_first_deposit']['#access'] = '0';

```

It will hide the field

# Answer
> 0 votes
For **Drupal 8/9** this works

```
$form['field_first_deposit']['#access'] = false;

```

---
Tags: 7, forms
---


# Question
Title: Access list of view modes available for nodes
In my code, I need to get a list of all the view modes defined for nodes.

In Drupal 7, it was available via `entity_get_info()`. What is the equivalent method for producing a list of available view modes in Drupal 8?

# Answer
View modes are config entities, so an entity query would do the trick:

```
$view_modes = Drupal::entityQuery('entity_view_mode')
  ->condition('targetEntityType', 'node')
  ->execute();

```
> 8 votes

# Answer
You can use `entity_display.repository` service (`EntityDisplayRepositoryInterface`).

```
// Getting all display modes configured for nodes.
\Drupal::service('entity_display.repository')->getViewModeOptions('node')

// Getting all display modes configured for nodes with content type article
\Drupal::service('entity_display.repository')
    ->getViewModeOptionsByBundle('node', 'article')

```
> 0 votes

---
Tags: 8, entities
---


# Question
Title: Unable to check if variable is empty in views twig
I am trying to overwrite `views-view-unformatted.html.twig` file and want to check if **title** variable **is empty** or not, but whatever I do, it always acts just like it is **not empty**.

I know title variable is an object, I tried dpm() it but no modifiers work in checking if it is empty.

My code:

```
{#
/**
 * @file
 * Default theme implementation to display a view of unformatted rows.
 *
 * Available variables:
 * - title: The title of this group of rows. May be empty.
 * - rows: A list of the view's row items.
 *   - attributes: The row's HTML attributes.
 *   - content: The row's content.
 * - view: The view object.
 * - default_row_class: A flag indicating whether default classes should be
 *   used on rows.
 *
 * @see template_preprocess_views_view_unformatted()
 *
 * @ingroup themeable
 */
#}
{{ dpm(title) }}
{% if title|render|trim|length is not empty %}

{% set row_id = 1 %}

{% for key, row in rows %}
    {% set row_id = key+1 %}
{% endfor %}
<a data-toggle="collapse" href="#collapse-{{ row_id }}" role="button" aria-expanded="false" aria-controls="collapse-{{ row_id }}">
  <h3>{{ title }}</h3>
</a>
<div class="collapse" id="collapse-{{ row_id }}">
{% endif %}
{% for row in rows %}
  {%
    set row_classes = [
      default_row_class ? 'views-row views-row-' ~ loop.index
    ]
  %}
  <div{{ row.attributes.addClass(row_classes) }}>
    {{- row.content -}}
  </div>
{% endfor %}
{% if title|render|trim|length is not empty %}
</div>
{% endif %}

```

# Answer
> 1 votes
`{% if title|render|striptags|trim %}` should work.

1. `title|render` to render, i.e. convert from render array to html string.
2. `|striptags` to remove any html tags from the rendered title.
3. `|trim` to remove any whitespace from beginning and end.

If `title` is already an html string (i.e. not a render array) then you can omit `|render` and `{% if title|striptags|trim %}` should be good enough.

# Answer
> 0 votes
Okay, so It looks like this works:

```
{% if title|trim|spaceless|striptags|length != 0 %}

```

Which looks incredibly stupid but well...

---
Tags: views, theming
---


# Question
Title: Simple Pie Chart not working in custom module. Do you see any errors in my code?
I am running Drupal 9 in Lando. I have enabled the Charts and Highcharts modules and installed the Highcharts JS library with composer. I wrote a simple module to display a Pie Chart but the chart won't display. My code is below. Do you see any glaring errors or ways I can debug this? I ported this from an example that runs fine in D7 in DevDesktop.
The info yaml file.

```
name: My Test Chart
description: This is an example use of the Charts module.
package: Custom
type: module
core_version_requirement: ^9
dependencies:
  - chart

```

The router yaml file.

```
example_chart:
  path: 'mychart-example'
  defaults:
    _title: 'My Example Chart'
    _controller: '\Drupal\mychart\Controller\MyChartController::example_page'
  requirements:
    _permission: 'access content'

```

The library yaml file.

```
highcharts:
  js:
    libraries/highcharts/highcharts.js: {  }
  dependencies:
    - core/jquery

```

The Controller.

```
namespace Drupal\mychart\Controller;

use Drupal\Core\Controller\ControllerBase;

class MyChartController extends ControllerBase {
  /**
   * @return array
   */
  function example_page() {
    $chart = array(
      '#attached' => array(
        'library' => array('mychart/highcharts'),
      ),
      '#type' => 'chart',
      '#chart_type' => 'pie',
      '#chart_library' => 'highcharts',
      '#title' => t('Simple Pie Chart'),
    );
    $chart['pie_data'] = array(
      '#type' => 'chart_data',
      '#title' => t('Gender'),
      '#data' => array(array('Male', 10), array('Female', 20)),
    );
    return  $chart;
  }
}

```

# Answer
> 0 votes
The solution was upgrading the Charts module to version 5.0.x. Now my simple pie chart works!

---
Tags: charts
---


title: Check if a field is empty

text:
# Question
Title: Check if a field is empty
How to you check that a field is empty, when you have the $node object?.

I found this as possible solution:

```
$field = field_get_items('node', $node, 'field_post_image');

```

I can check now:

**UPDATE**.Ups! This not reliable, althought I promise that it worked for me yesterday.

```
isset($field);

```

but I wonder if there is a better way.

Enlighten me

# Answer
If you read up on the documentation the function returns `FALSE` if there is no data. `isset()` would actually fail because you are assigning `FALSE` to the variable so it's actually been set to something. The correct syntax would be:

```
$field = field_get_items('node', $node, 'field_post_image');
if ($field) {
   //Do something with the field
}
else{
   //There are no results
}

```
> 30 votes

# Answer
You could use the following snippet to verify if a field is empty.

```
$info = field_info_field($field_name);
$function = $info['module'] . '_field_is_empty';

if (function_exists($function)) {
  $value = field_get_items('node', $node, $field_name);
  $is_empty = $function($value[0], $info);
}

```

This code is safer, as different fields are considered empty under different conditions. See, for example, the difference between file\_field\_is\_empty(), number\_field\_is\_empty(), and taxonomy\_field\_is\_empty(): `taxonomy_field_is_empty()` checks the *tid* property of the `$item` parameter, while `number_field_is_empty()` checks the *value* property of the same parameter. Custom fields could require a more complex condition to be verified, in order to consider the field empty.

The description for hook\_field\_is\_empty() given in the documentation is the following one:

> Define what constitutes an empty item for a field type.
> 8 votes

# Answer
### Drupal 8

If somebody need the same answer in D8, that is what I do to test my field:

```
if ($entity->hasField('field_event_date_start')
  && !$entity->get('field_event_date_start')->isEmpty()) {
  // Do something ...
}

```
> 3 votes

# Answer
We found the raw value in (`$content['field_name']['#items'][0][value]`), so you can determine if the field is empty or not with the expression

> (!empty($content\['field\_name'\]\['#items'\]\[0\]\[value\]))

where `field_name` matches the name of the desired field.
> 2 votes

# Answer
If needs to check for a field in a custom webform, or existing form in Drupal 8:

```
if (is_null($form_state->getValue('field_name')[0]['value']) {
    $form_state->setErrorByName('field_name', t('Please select a value'));
}

```
> 0 votes

---
Tags: 7, entities
---


# Question
Title: Webform submissions to 3rd party API with Refresher Authentication token
How can you send the submitted information in a webform to a 3rd party API?

I have read this: https://www.drupal.org/docs/8/modules/webform/webform-cookbook/how-to-programmatically-create-and-update-a-submission

But it looks this recipe is more about updating a submission rather that doing requests.

Any help?

**Note**:

I realized I can use handlers for submission, but need to use a refresh authentication token for submission. Are there any hooks for that?

I'm looking for hooks grab the data submitted from a specific form, then creates a POST request to create an access code and finally makes a POST request for a 3rd party API.

Using Zoho CRM API

# Answer
> 0 votes
I have found this article creating a extension of a post handler to target specific webforms on submission.

https://www.chapterthree.com/blog/drupal-8-how-to-extending-a-webform-remote-post-handler

Code: https://github.com/chapter-three/drupal-hook-webform-handler-invoke-alter-example/blob/master/snippet.php

This is a very hard-coded solution, so I might think this might be a good idea to create a contrib module to extend the post request handler with multiple submissions.

---
Tags: 8, webforms
---


title: How to verify if a custom logo is used?

text:
# Question
Title: How to verify if a custom logo is used?
How can I check whether the site is using the default theme logo or a custom logo?

When I use the Twig debug tools to dump the `site_logo` variable, I only get the path of the logo, which doesn't tell me if that is the default theme logo.

# Answer
In a preprocess function, you can get that value with the following code:

```
$use_default = \Drupal::config('theme_machine_name.settings')->get('logo.use_default');

```

You can then stick that Boolean in the `$variables` array and make use of it in a custom Twig template.
> 2 votes

---
Tags: theming, 9
---


# Question
Title: How can I import data from XML into a content type using the Migrate module?
I have activated the Migrate, the Migrate Plus, and the Migrate Tools modules. I have then imported a migration file at /admin/config/development/configuration/single/import.

```
id: beetween_jobs
label: 'Migrate beetween jobs from XML file'
migration_group: default
source:
  plugin: xml
  data_fetcher_plugin: http
    # 'simple_xml' is configured to be able to use the 'http' fetcher.
    data_parser_plugin: simple_xml
    urls:
      - https://mywebsite/beetween.xml
    item_selector: /root/jobs
process:
  title: title
  field_diffusion: diffusion
  field_faculty : faculty
destination:
  plugin: 'entity:node'
  default_bundle: job
migration_dependencies:
  required: {  }
  optional: {  }

```

I could not find any official documentation, so I created that file basing on a tutorial.

On /admin/structure/migrate/manage/default/migrations, it doesn't list the migration I added. I just says:

> Il n'y a pas encore de Entités migration.
>
> Échec de connexion au serveur de base de données. Le serveur a renvoyé le message suivant : No database connection configured for source plugin variable.
>
> Est-ce que le serveur de base de données fonctionne ?
> Est-ce que la base de données existe et avez-vous saisi le bon nom de base ?
> Avez vous saisi les bons nom d'utilisateur et mot de passe ?
> Avez vous saisi le bon nom d'hôte de la base de données ?

# Answer
As Kevin suggested, inspired by the

> migrate\_plus.migration.wine\_role\_xml.yml

file from the migrate\_plus module , i have managed to make it working as such:

```
id: beetween_jobs
label: 'Migrate beetween jobs from XML file'
migration_group: default
source:
  # We use the XML data parser plugin.
  plugin: url
  data_fetcher_plugin: http
  data_parser_plugin: xml
  # Normally, this is one or more fully-qualified URLs or file paths. Because
  # we can't hardcode your local URL, we provide a relative path here which
  # hook_install() will rewrite to a full URL for the current site.
  urls:
    - https://my_website/files/ressources_docs/beetween.xml
  # Visit the URL above (relative to your site root) and look at it. You can see
  # that <response> is the outer element, and each item we want to import is a
  # <position> element. The item_xpath value is the xpath to use to query the
  # desired elements.
  item_selector: /root/job
  # Under 'fields', we list the data items to be imported. The first level keys
  # are the source field names we want to populate (the names to be used as
  # sources in the process configuration below). For each field we're importing,
  # we provide a label (optional - this is for display in migration tools) and
  # an xpath for retrieving that value. It's important to note that this xpath
  # is relative to the elements retrieved by item_xpath.
  fields:
    -
      name: title
      label: 'title'
      selector: title
    -
      name: diffusion
      label: 'diffusion'
      selector: diffusion
  # Under 'ids', we identify source fields populated above which will uniquely
  # identify each imported item. The 'type' makes sure the migration map table
  # uses the proper schema type for stored the IDs.
  ids:
    title:
      type: string
process:
  # Note that the source field names here (machine_name and friendly_name) were
  # defined by the 'fields' configuration for the source plugin above.
  title: title
  field_diffusion: diffusion
destination:
  plugin: 'entity:node'
  default_bundle: job
migration_dependencies:
  required: {  }
  optional: {  }

```
> 0 votes

---
Tags: migrations
---


# Question
Title: How to prevent theme change caused by $rendererService->renderPlain()
I'm trying to re-use the output from a heavily customized field formatter as `<select><option>` text in a Webform. I managed to re-use field display, but it has the side-effect of switching the theme used for rendering.

Below is a simplified code, the lines `$dateRenderArray = ...` and `$dateHtml = ...` are original and cause the problem:

```
function mymodule_webform_options_alter(array &$options, array &$element, $options_id = NULL) {
  foreach ($options as $value => $text) {
    $entity = getEntityByOption($value);

    //reuse custom field formatter output
    $dateRenderArray = $entity->get('field_daterange')->view('teaser');

    //the following line causes the theme change
    $dateHtml = \Drupal::service('renderer')->renderPlain($dateRenderArray);

    $datePlaintext = str_replace(["\n", " "], ["", " "], trim(strip_tags($dateHtml)));
    $muchBetterOptionText = $someOtherPlaintextFromEntity . '  ' . $datePlaintext;
    $options[$value] = $muchBetterOptionText;
  }
}

```

This works excellent in the frontend form, for the submission emails and also in the admin webform submission list.

But in a single webform submission detail view, those lines cause an unwanted theme change from admin theme to frontend theme.

Question:
How can I prevent the theme change from `renderPlain()` and keep the admin theme when viewing a single webform submission?

Optional bonus question:
Why does my hook work correctly in the webform submission list and not in webform submission detail view? The submission list uses exactly the same function, it renders the same processed option text in the corresponding column, but I do correctly stay inside the admin theme when viewing the list. But only in the detail view switches to the frontend theme.

# Answer
Found out there is some very Webform-specific logic involved (@see WebformSubmission entity using a custom theme and render logic).

My custom rendering is confusing webform's logic. I switched to Webform's own renderer, and enforced the admin theme after my custom rendering.

```
function mymodule_webform_options_alter(array &$options, array &$element, $options_id = NULL) {
  //webform's own rendering service
  $webformRenderer = \Drupal::service('webform.theme_manager');

  foreach ($options as $value => $text) {
    $entity = getEntityByOption($value);

    //reuse custom field formatter output
    $dateRenderArray = $entity->get('field_daterange')->view('teaser');

    //the following line causes a theme change on route entity.webform_submission.canonical
    $dateHtml = $webformRenderer->renderPlain($dateRenderArray);

    $datePlaintext = str_replace(["\n", " "], ["", " "], trim(strip_tags($dateHtml)));
    $muchBetterOptionText = $someOtherPlaintextFromEntity . '  ' . $datePlaintext;
    $options[$value] = $muchBetterOptionText;
  }

  //route name is null, must use request instead
  if (strpos(\Drupal::request()->getRequestUri(), '/admin/structure/webform/manage/MY_WEBFORM_ID/submission/') === 0) {
    //only admins can view webform submissions
    $webformRenderer->setCurrentTheme(\Drupal::config('system.theme')->get('admin'));
  }
}

```

Looks a bit hacky, but it does the trick.
> 1 votes

---
Tags: forms, webforms
---


# Question
Title: Editing the "Send private message" link to include the username
I'm using the Private Message module version 8.x-2.0-beta16 and I want to change the message from `'Send private message'` to `'Send private message to @username'` which should display the username from the user's page that we are visiting and contacting.

The module uses the following code.

```
'#title' => t('Send private message'),

```

Is there are way to override this line directly and insert the username? I tried without success.

The relevant code is located in: private\_message\src\Service\PrivateMessageService.php#L336-382.

```
public function createRenderablePrivateMessageThreadLink(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  if ($display->getComponent('private_message_link')) {
    if ($entity instanceof UserInterface) {
      $author = $entity;
    }
    else {
      $author = $entity->getOwner();
    }
    $current_user = \Drupal::currentUser();
    if ($current_user->isAuthenticated()) {
      if ($current_user->hasPermission('use private messaging system') && $current_user->id() != $author->id()) {
        $members = [$current_user, $author];
        $thread_id = $this->mapper->getThreadIdForMembers($members);
        if ($thread_id) {
          $url = Url::fromRoute('entity.private_message_thread.canonical', ['private_message_thread' => $thread_id], ['attributes' => ['class' => ['private_message_link']]]);
          $build['private_message_link'] = [
            '#type' => 'link',
            '#url' => $url,
            '#title' => t('Send private message'),
            '#prefix' => '<div class="private_message_link_wrapper">',
            '#suffix' => '</div>',
          ];
        }
        else {
          $url = Url::fromRoute('private_message.private_message_create', [], ['query' => ['recipient' => $author->id()]]);
          $build['private_message_link'] = [
            '#type' => 'link',
            '#url' => $url,
            '#title' => t('Send private message'),
            '#prefix' => '<div class="private_message_link_wrapper">',
            '#suffix' => '</div>',
          ];
        }
      }
    }
    else {
      $url = Url::fromRoute('user.login');
      $build['private_message_link'] = [
        '#type' => 'link',
        '#url' => $url,
        '#title' => t('Send private message'),
        '#prefix' => '<div class="private_message_link_wrapper">',
        '#suffix' => '</div>',
      ];
    }
  }
}

```

# Answer
I think you're asking, "How can I create a translated string 'Send a private message to @username' where @username is replaced with the name of the user currently being viewed?"

If you're viewing the user's page, you can do this like:

```
// Get the user object currently being viewed from the route parameters.
$user = \Drupal::routeMatch()->getParameter('user');
// If we have a valid user object.
if ($user instanceof Drupal\user\Entity\User) {
  $message = t('Send a private message to @username', [
    '@username' => $user->getDisplayName(),
  ]);
}

```

However, looking at the specific code you posted, you're not guaranteed to be on the user's page. Additionally, the user that is to receive the message is already determined and stored in `$author`:

```
// I.e. user's page.
if ($entity instanceof UserInterface) {
  $author = $entity;
}
// I.e. a node or other entity authored by a user.
else {
  $author = $entity->getOwner();
}

```

In this case, you could just use something like:

```
$message = t('Send a private message to @username', [
  '@username' => $author->getDisplayName(),
]);

```
> 2 votes

---
Tags: 8, message-subscribe
---