# Question
Title: Drush command not found after Drush completely installed
I installed Drush 8 with composer.

```
composer require drush/drush:~8

```

Unfortunately, error **drush command not found** after Drush completely installed.

Why's the drush command can't be use?

**Installing Drush for Drupal 8 with Composer**

https://www.youtube.com/watch?v=ZqY\_XsCTmAc

# Answer
> 27 votes
Once you have installed Drush, you need to ensure that you ~/vendor/bin directory is in your $PATH, as explained in the installation instructions **(Note: this link no longer works)**

Add `export PATH="$HOME/.composer/vendor/bin:$PATH"` to your ~/.bash\_profile or ~/.bashrc, and then run `source ~/.bash_profile` (or ~/.bashrc).

I would recommend against installing Drush 9 via `composer require` at this time. Sticking with Drush 8 is probably best; if you want to use Drush 9, use a simpler installation method, such downloading the .phar, or clone the repository and run `composer install` in the directory Drush was cloned to.

# Answer
> 21 votes
Using ssh on my server host I did add drush to my path following composer instructions: Add

```
    export PATH="$HOME/.composer/vendor/bin:$PATH"

```

to your ~/.bash\_profile or ~/.bashrc, and then run source ~/.bash\_profile (or ~/.bashrc)

However, I got this running

```
    drush status
    -bash: drush: command not found

    which composer
    /opt/cpanel/composer/bin/composer

    which drush
    /usr/bin/which: no drush in (/home/mth/.composer/vendor/bin:/home/mth/.composer/vendor/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/cpanel/composer/bin:/home/mth/bin:/home/mth/bin)

```

Using my cPanel file manager, I found I couldn't see the hidden composer folder, though I saw other hidden folders and files (dot prefix). It turns out composer was within the .config folder. I added the new folder to my $PATH and bingo!

```
    export PATH="$HOME/.config/composer/vendor/bin:$PATH"

```

Hours! Moral? Verify composer location.

# Answer
> 4 votes
Anyone on Ubuntu 20.04?

I noticed composer installs not in ~/.composer/vendor/bin, but ~/.config/composer..

Super weird, since nobody has mentioned that (important) fact.

I am sure with the proper path sourced, it will work now..

I changed my $PATH in ~/.bashrc to ~/.config/composer/vendor/bin like this: `export PATH="$HOME/.config/composer/vendor/bin:$PATH"`

..and it works!

# Answer
> 1 votes
You need to make sure that `drush` is located within your environmental `PATH` variable, or you can specify installation folder by setting `COMPOSER_BIN_DIR`, e.g.:

> `sudo COMPOSER_HOME=/var/lib/composer COMPOSER_BIN_DIR=/usr/local/bin COMPOSER_VENDOR_DIR=/var/lib/composer/vendor composer require drush/drush:~8`

Or it's much easier to install it via Phar, e.g. on Linux:

```
sudo install <(curl http://files.drush.org/drush.phar) /usr/local/bin/drush

```

On OS X, try: `brew install drush`.

# Answer
> 1 votes
Now, although my issues were with a Composer Scaffold install for Drupal 8 which needs Drush (in this case v.9) installed as a dependency, I too was having similar issues. Even though a global Drush is no longer recommended, due to dependency issues, it's very convenient to call upon Drush from anywhere.

What I would recommend installing is Drush Launcher, which will give you the option of calling Drush from anywhere, even if you are running different projects using different versions of Drush. I have it running both locally and on my Web servers, and it's running fine there. It fixed my issues of seeing `drush: command not found`, even when I had projects where Composer had correctly installed Drush.

# Answer
> 0 votes
What worked for me was to run `vendor/bin/drush <command>`.

---
Tags: drush
---


# Question
Title: Create solr_core for search_api_solr 4.x with docker solr 8
I used to create my solr instance for drupal like this:

```
docker run -v $PWD/solrconf:/asdentcore  -e SOLR_JAVA_MEM="-Xms2G -Xmx2G" --restart unless-stopped  -p 8984:8983 -t --name asdentDevSolr -d solr:8.11
docker exec -it --user=solr asdentDevSolr  bin/solr create_core -c asdentcore -d /asdentcore

```

Where ./solrconf contains the files from modules/contrib/search\_api\_solr/solr-conf-templates/7.x.

With the new 4.x branch of search\_api\_solr (and 8.x contents) I get

```
ERROR: Error CREATEing SolrCore 'asdentcore': Unable to create core [asdentcore] Caused by: solr.ICUCollationField

```

I've already tried https://stackoverflow.com/questions/53161034/solr-error-unable-to-create-core-mycore-caused-by-solr-icucollationfield \- didn't help.

Container logs shows the following errors:

```
Caused by: org.apache.solr.common.SolrException: Unable to create core [asdentcore]
Caused by: org.apache.solr.common.SolrException: Could not load conf for core asdentcore: Can't load schema /var/solr/data/asdentcore/conf/schema.xml: Plugin init failure for [schema.xml] fieldType "collated_de": Error loading class 'solr.ICUCollationField'
Caused by: org.apache.solr.common.SolrException: Can't load schema /var/solr/data/asdentcore/conf/schema.xml: Plugin init failure for [schema.xml] fieldType "collated_de": Error loading class 'solr.ICUCollationField'
Caused by: org.apache.solr.common.SolrException: Plugin init failure for [schema.xml] fieldType "collated_de": Error loading class 'solr.ICUCollationField'
Caused by: org.apache.solr.common.SolrException: Error loading class 'solr.ICUCollationField'
Caused by: java.lang.ClassNotFoundException: solr.ICUCollationField

```

# Answer
> 1 votes
Kudos to Kevin. The solution is

here https://stackoverflow.com/questions/62652640/error-when-initializing-solr-core-error-loading-class-solr-icucollationfield

and here https://www.drupal.org/project/search\_api\_solr/issues/3181819#comment-13899895

I had to replace in the downloaded `solrcore.properties` the line `solr.install.dir=../../..` with `solr.install.dir=/opt/solr`.

One day of my live ...

---
Tags: 9, search, virtual-machine
---


title: How to get bundle label from entity

text:
# Question
Title: How to get bundle label from entity
How do I get the human-readable label of a bundle from a loaded entity.

Lets say I want to get the label of a node of bundle *my\_article* which is *"My awesome article"*

```
// Load a node
$node = Drupal::entityManager()->getStorage('node')->load(4);

print $node->bundle(); // prints the machine-readable name. e.g. 'my_article'.

print $node->getEntityType()->getBundleLabel(); // prints 'content type'.

```

I am only able to get the machine-readable name of the bundle or the Label of entity ("content type", "taxonomy term", etc), but how do I get the Label of the bundle?

P.S. I would prefer it to not use deprecated classes/functions

# Answer
> 47 votes
I can't see anything direct, loading the node type entity itself is an option though:

```
$bundle_label = \Drupal::entityTypeManager()
  ->getStorage('node_type')
  ->load($node->bundle())
  ->label();

```

Or as Berdir pointed out in the comments, there's a quicker way through the entity reference field:

```
$bundle_label = $node->type->entity->label();

```

# Answer
> 16 votes
Note that the accepted answer applies specifically to node entities but all entities have bundles. Many entities, like `user` or `menu_link_content` (for custom menu links), only have one bundle which corresponds to the entity type itself.

The `entity_type.bundle.info` service, implemented by Drupal\Core\Entity\EntityTypeBundleInfo, provides access to entity bundle information. Its methods `getAllBundleInfo()` and `getBundleInfo($entity_type_id)` return an array keyed by entity type and bundle machine name respectively, with the former then containing an array of bundles keyed by bundle machine name. Each bundle has a `label` key with the translated bundle friendly name.

Below is an example that shows the difference between content entity machine names, labels, bundle machine names, and bundle labels. The code assumes that there is at least one custom menu link with an ID of `1`. It also assumes there is an `article` node type (bundle), that there is at least one node with an ID of `1`, and that the node is of the node type (bundle) `article`.

```
<?php

$entity_type_manager = \Drupal::entityTypeManager();
$bundle_info = \Drupal::service("entity_type.bundle.info")->getAllBundleInfo();

$current_user = \Drupal::currentUser()->getAccount();

// Prints "user".
print $current_user->getEntityTypeId() . PHP_EOL;

// Prints "User".
print $current_user->getEntityType()->getLabel() . PHP_EOL;

// Prints "user".
print $current_user->bundle() . PHP_EOL;

// Prints "User".
print $bundle_info[$current_user->getEntityTypeId()][$current_user->bundle()]['label'] . PHP_EOL;

$my_menu_link = $entity_type_manager->getStorage('menu_link_content')->load(1);

// Prints "menu_link_content".
print $my_menu_link->getEntityTypeId() . PHP_EOL;

// Prints "Custom menu link".
print $my_menu_link->getEntityType()->getLabel() . PHP_EOL;

// Prints "menu_link_content".
print $my_menu_link->bundle() . PHP_EOL;

// Prints "Custom menu link".
print $bundle_info[$my_menu_link->getEntityTypeId()][$my_menu_link->bundle()]['label'] . PHP_EOL;

$my_article = $entity_type_manager->getStorage('node')->load(1);

// Prints "node".
print $my_article->getEntityTypeId() . PHP_EOL;

// Prints "Content".
print $my_article->getEntityType()->getLabel() . PHP_EOL;

// Prints "article".
print $my_article->bundle() . PHP_EOL;

// Prints "Article".
print $bundle_info[$my_article->getEntityTypeId()][$my_article->bundle()]['label'] . PHP_EOL;

```

Be sure to use dependency injection where possible in your code rather than relying on the static methods of the `Drupal` class.

# Answer
> 11 votes
If you are not sure about the `$entity` type, it can be:

```
if ($entity->bundle()) {
  $bundle_type_id = $entity->getEntityType()->getBundleEntityType();
  $bundle_label = \Drupal::entityTypeManager()
    ->getStorage($bundle_type_id)
    ->load($entity->bundle())
    ->label();
}

```

# Answer
> 6 votes
The following works as well:

```
$type = NodeType::load($node->bundle());

```

# Answer
> 4 votes
None of the solutions here worked for me. I finally used the `entity_type.bundle.info` service like this.

```
$entity_type = $entity->getEntityTypeId();
$entity_bundle = $entity->bundle();
$bundle_info = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type);
$label = $bundle_info[$entity_bundle]['label'];

```

# Answer
> 0 votes
Not the most efficient solution, but for sake of completeness, this works:

```
\Drupal::token()->replace('[node:type-name]', ['node' => $node]);

```

or

```
\Drupal::service('token')->replace('[node:type-name]', ['node' => $node]);

```

---
Tags: 8, entities
---


# Question
Title: drush migrate:status comes back with SQLSTATE [hy000] [2002] No such file or directory
Using drush on my macOS. Using Nginx and MYSQL through brew. Website is running fine. The other drush commands work fine, drush cr, drush cc, etc. But running drush migrate:status or drush ms results in

```
 error SQLSTATE [hy000] [2002] No such file or directory

```

in terminal. same for all migrate drush commands.

Running Drupal 8.8.5 Lightning Distro.

My settings.php is set up 127.0.0.1 but still no luck.

# Answer
I had this problem when upgrading/downgrading php to work with drush migrate. Check if the mysql.sock is right in php.ini:

First check what php.ini file is used in terminal:

```
php --ini

```

Then open the php.ini file and change/add:

```
pdo_mysql.default_socket=/Applications/MAMP/tmp/mysql/mysql.sock

```

or the mysql.sock you are using. To find your mysql socket in terminal use:

```
mysql_config --socket

```
> 2 votes

# Answer
In my case I changed the `'host' => 'localhost'` to `'host' => '127.0.0.1'` and now all Drush command working.
> 0 votes

---
Tags: drush, migrations
---


# Question
Title: Custom block plugin: Give access to edit form
I am creating a custom block plugin with custom configuration options (`blockForm()`).

I would like to allow content editors to edit the configuration of this block, without giving them "administer blocks" permission. Also, I don't want them to create new block instances..

I would like to avoid installing an additional module like block\_permissions, as this would introduce a wide range of settings or permissions for all blocks, not just the specific block I am creating.

I also don't want this to be a "content" block with "fields". I want to know how this can be done with a custom plugin-based block.

**EDIT / UPDATE**
(based on responses and discussion)

The existing response is spot-on, if your goal is as described in the question.

However, the entire premise has one problem: The entire block placement will be exported to config, and overwritten on deployment. This means any user-controlled settings will be destroyed on next release. So this is not sustainable. A content block entity can be used to allow a proper split.

# Answer
> 1 votes
If a user doesn't have the admin permission for an entity type this doesn't mean you can't allow specific operations on specific existing entities.

For example, content editors you gave permission to administer content are also allowed to update blocks of your custom block plugin:

```
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;

/**
 * Implements hook_ENTITY_TYPE_access() for entity type "block".
 */
function mymodule_block_access(Block $block, $operation, AccountInterface $account) {
  if ($operation == 'update'
    && $block->getPluginId() == 'custom_block_plugin_id'
    // The last condition is only needed if the trick from below is used with
    // `$block_entity->createDuplicate()->access('update')`.
    && $block->id() !== NULL
  ) {
    return AccessResult::allowedIfHasPermission($account, 'administer nodes');
  }
  return AccessResult::neutral();
}

```

You also need to alter the form not to redirect on submit to a page the user has no access to. Or link to the edit form with a destination query string pointing to a different page.

---

(EDIT by @donquixote)

If you want to restrict access to other elements on the page, you could do it like below.
This is a bit fragile, because it assumes a very specific structure of the form.

```
/**
 * Implements hook_form_FORM_ID_alter() for 'block_form'.
 */
function mymodule_form_block_form_alter(array &$form, FormStateInterface $form_state, string $form_id) {
  $form_object = $form_state->getFormObject();
  if (!$form_object instanceof BlockForm) {
    return;
  }
  /** @var Block $block_entity */
  $block_entity = $form_object->getEntity();
  if ($block_entity->getPluginId() !== 'custom_block_plugin_id') {
    return;
  }
  if ($block_entity->createDuplicate()->access('update')) {
    // The user has general access to update this block.
    return;
  }
  // The user was only given access via mymodule_block_access().
  // They should only edit the specific plugin settings, not change where the
  // block is placed.
  foreach (['visibility', 'id', 'weight', 'region'] as $key) {
    if (isset($form[$key])) {
      $form[$key]['#access'] = FALSE;
    }
  }
  foreach (['label', 'label_display'] as $key) {
    if (isset($form['settings'][$key])) {
      $form['settings'][$key]['#access'] = FALSE;
    }
  }
  foreach (['delete'] as $key) {
    if (isset($form['actions'][$key])) {
      $form['actions'][$key]['#access'] = FALSE;
    }
  }
}

```

---
Tags: blocks, 9
---


title: How can I stop Webform Spam?

text:
# Question
Title: How can I stop Webform Spam?
We are getting spam from a simple Contact Us webform. We are running Drupal 7, using the Captcha and Honeypot modules. The Captcha challenge is set to image and the Honeypot time limit is set to 4 seconds and in enabled for all webforms. But we recently started to get spammed at the rate of 4 or 5 posts per minute, all from different gmail or ru addresses. For the time being we unpublished the offending webform. Does anyone have advice on what else we could try?

# Answer
> 2 votes
Here are some things to try

* Set the honeypot element name to something else
* Set submission limits by ip address on the webform
* Set submission limits by cookie on the webform
* Try reCaptcha v3 if you're still using an older style Captcha

The downside to submission limits is that people on shared computers/networks might get blocked, and if you use a cookie then you have to deal with cookie legislation.

The downside to ReCaptcha is you're letting Google decide who the spammers are. May not be a downside depending on your views toward Google.

# Answer
> 1 votes
I use the backend of https://www.stopforumspam.com/ together with honeypot very successful. But you have to check if the SFS metrics match your user risk profile.

Not sure if there's a module but here's my code if you like to copy paste and play with it...

```
  /**
   * @param $ip - ip or ip[]
   * @param $name - name or name[]
   * @param $email - mailadr or mailadr[]
   *
   * @return float|int - the average spam confidence of api.stopforumspam.org
   */
  public static function checkStopForumSpam($ip, $name, $email) {
    $client = Drupal::getContainer()->get('http_client');
    $endpoint = 'https://api.stopforumspam.org/api';
    $names = $mails = [];
    if (is_array($name)) {
      foreach ($name as $n) $names[] = urlencode($n);
    } else {
      $names[] = urlencode($name);
    }
    if (is_array($email)) {
      foreach ($email as $n) $mails[] = md5($n);
    } else {
      $mails[] = md5($email);
    }
    $postdata = [
      'form_params' => [
        'username' => $names,
        'emailhash' => $mails,
        'ip' => $ip,
        "badtorexit",
        "json",
        "unix"
      ],
      'headers' => [
        'Accept' => 'application/json',
      ]
    ];
    try {
      $response = $client->post($endpoint, $postdata);
      $response_data = json_decode((string) $response->getBody(), TRUE);
    } catch (\Exception $e) {
      $response_data = [];
      $response_data['success'] = 0;
      Drupal::logger('asdentbase')->error('stopforumspam exception '.$e->getMessage());
    }
    $confidence = 0.0;
    $conficount = 0;
    $confimax = 0;
    array_walk_recursive($response_data, function ($v, $k) use (&$confidence, &$conficount, &$confimax) {
      if ($k == 'confidence') {
        $confidence += $v;
        if ($v > $confimax) $confimax = $v;
      }
      if ($k == 'value') {
        $conficount++;
      }
    });

    $sumconfi = $conficount == 0 ? 0.0 : $confidence / $conficount;
    if ($confimax > 80) {
      if ($sumconfi < $confimax) $sumconfi = $confimax;
    }

    return $sumconfi;
  }

```

---
Tags: webforms, captcha
---


# Question
Title: How get the referenced entity inside a field link?
I'm on D9. I've a paragraph with a *Link* field. I need to get the referenced entity programmatically.

What I've managed so far is:

```
       $nid = NULL;
       $field_link = $paragraph->get('field_link')->first();
       $route_params = $field_link->getUrl()->getRouteParameters();

        if(isset($route_params['node'])) {
          $nid = $route_params['node'];
        }

       if($nid) {
               ....
       }

```

But looks quite convoluted to me - and rely on the fact that the entity linked is only of type 'node'. Isn't there some function that provide the data directly\ in a more robust way?

# Answer
> 2 votes
I am currently using this code for a very similar task:

```
/** @var $link \Drupal\Core\Url */
if (!$link->isRouted()) {
  /* internal unrouted or external link */
  return;
}

$route = $link->getRouteParameters();
$entityType = key($route);
$referencedEntity = NULL;
try {
  $referencedEntity = \Drupal::entityTypeManager()
    ->getStorage($entityType)
    ->load($route[$entityType]);
  $referencedEntity = \Drupal::service('entity.repository')
    ->getTranslationFromContext($referencedEntity);
}
catch (Throwable $t) {
  /* Don't know if this is necessary, but I don't know what is coming out of getRouteParameters() */
  return;
}

if (!($referencedEntity instanceof \Drupal\Core\Entity\ContentEntityBase)) {
  /* maybe you want to allow different kind of entities here, e.g. a webform is no content entity */
  return;
}

/* success */
doStuff($referencedEntity);

```

Basically the same you are already doing, but you can use the key to get the entity type and make it more generic.

---
Tags: entities, 9
---


# Question
Title: Hide and unhide custom block programatically drupal 9
Hello I have created a module to display a block. I would like to set the block visibity to visible and hidden in the homepage based on a condition.

like this:

edit:

```

//fetch status from database then check condition
if ($status = 1){
//make block visible
}else{
//hide block
}

```

I have searched all over the internet and could not find how to make it work.

I have a form that is used to add contents to a database and it has a checkbox to whether make the make the block visible or not.

So I would fetch the status(visible/hidden) of the block from the database and then make the block visible.

# Answer
> 3 votes
When you create a module to display a block this usually means you implement a block plugin. Then you can add the condition to the plugin directly:

```
  protected function blockAccess(AccountInterface $account) {
    $access = $this->languageManager->isMultilingual() ? AccessResult::allowed() : AccessResult::forbidden();
    return $access->addCacheTags(['config:configurable_language_list']);
  }

```

Example from core LanguageBlock::blockAccess().

---
Tags: blocks
---


# Question
Title: How to get parent paragraph from a child block preprocess
Searched all over the place but cannot find anything similar. Here's the issue...

The setup:

* 'Page' content type
* 'Block Content' paragraph type

The 'Page' content type has a field for referencing the 'Block Content' paragraph, called "Components" (set to unlimited). Now we can use paragraphs within our page. Simple.

The 'Block Content' paragraph type has a field for referencing blocks (using the module Block Field). It also has two other fields for title and subtitle. These fields need to be displayed on the referenced block template for this paragraph instance. This is the issue.

I have included some images to show the setup.

I am trying to use theme\_preprocess\_block to inject these fields (title and subtitle) into the correct block using $vars\['plugin\_id'\] from the block preprocess but the issue is that the block does not know its paragraph parent. And if I don't know the parent, I can't retrieve these fields that I need to inject into the block.

This answer suggests using the following to get the parent:

```
$parent   = $variables['elements']['#node']->_referringItem->getEntity();

```

But in my case I could not find anything equivalent in my block preprocess function.

**Update 1:**

I tried (from within theme\_preprocess\_block):

```
dpm($vars['elements']['content']['#block_content']->_referringItem->getEntity());

```

This give the following error:

```
Error: Call to a member function getEntity() on null in limit_preprocess_block__banner_block()

```

**Update 2:**

I just dumped the $vars from the preprocess function and noticed that there seems to be the paragraph id in there. But I don't know how to access it from the $vars variable. Is it possible? (see dump image below)

##

**IMAGE 1:**

---

**IMAGE 2:**

---

**IMAGE 3:**

---

**IMAGE 4:**

---

**IMAGE 5:**

# Answer
The linked answer was using this inside a node hook.

In case of `hook_preprocess_block(&$variables)` you can try `$variables['elements']['content']['#block_content']` instead. This should return the content entity, similar to the linked answer.

Notice: `$whateverEntity->_referringItem` is not cache-safe, so if you are referencing the same block on multiple paragraphs, this will not work reliably.
> 0 votes

# Answer
One solution worth trying is preprocessing the field to add a pre-render callback on the referenced item.

Preprocessing happens before rendering, so it works like this:

1. First go around, preprocessing the field, you add a pre-render callback to the item
2. Next go around, when the item is rendered, the pre-render callback takes effect
> 0 votes

---
Tags: 8, paragraphs
---


# Question
Title: Migrating an array of values into multiple entities
I'm using migrate/migrate\_plus modules to import CSV files and generate entities.

One of the columns in the CSV can contain multiple values like 1|2|3, which I'm converting to an array using:

```
_jurisdictions:
  plugin: explode
  source: jurisdiction
  delimiter: "|"

```

I'm then using the array to generate taxonomy terms with this:

```
field_political_district:
  -
    plugin: entity_generate
    source: "@_jurisdictions"
    entity_type: taxonomy_term
    bundle: political_district
    bundle_key: vid
    value_key: name
    ignore_case: true
    values:
      field_display_name: ???

```

I'm stuck on two things:

* I want to append a constant value to each name so that the taxonomy term name becomes "1, XYZ" where XYZ comes from constants/ABC
* I want field\_display\_name to be the the value prefixed with "District "

Any thoughts on how to accomplish those two things?

# Answer
> 1 votes
Here's the custom plugin I ended up writing:

```
<?php

namespace Drupal\mymodule\Plugin\migrate\process;

use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate_plus\Plugin\migrate\process\EntityGenerate;
use Drupal\migrate_plus\Plugin\migrate\process\EntityLookup;

/**
 * This plugin generates district entities within the process plugin.
 *
 * @MigrateProcessPlugin(
 *   id = "district_generate"
 * )
 *
 * @see EntityGenerate
 *
 * @code
 * process:
 *   field_political_district:
 *     plugin: district_generate
 *     source: jurisdiction
 *     district_set_name: constants/ABC
 *     district_set_value: "@_district_set"
 *     prefix: "District"
 *     ... other entityGenerate config
 * @endcode
 */
class DistrictGenerate extends EntityGenerate {

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrateExecutable, Row $row, $destinationProperty) {
    $this->row = $row;
    $this->migrateExecutable = $migrateExecutable;
    $name = $value;
    if (isset($this->configuration['district_set_name'])) {
      $suffix = $row->get($this->configuration['district_set_name']);
      $name = $name . ', ' . $suffix;
    }
    // Creates an entity if the lookup determines it doesn't exist.
    if (!($result = EntityLookup::transform($name, $migrateExecutable, $row, $destinationProperty))) {
      if (isset($this->configuration['district_set_value'])) {
        $this->configuration['values']['field_district_set'] = $this->configuration['district_set_value'];
      }
      $prefix = $this->configuration['prefix'] ?? 'District';
      $this->configuration['default_values']['field_display_name'] = $prefix . ' ' . $value;
      $result = $this->generateEntity($name);
    }
    return $result;
  }

}

```

---
Tags: migrations, 9
---


# Question
Title: Drupal::messenger() not working in shutdown function
Some time I need to use a shutdown function. Here is a stupid example:

```
...
drupal_register_shutdown_function('display_message');
...

function display_message() {
  Drupal::messenger()->addMessage('Hello world');
}

```

Is it possible to display this message as for now, it is not working (even at the nex page refresh)?

As requested I will explain why I need to use shutdown function.

**My use case**:

We are in the presave hook of `node A` which just got a new custom status (which needs to be saved), let say status 5.

When this kind of node reach status 5, I need to save/update another `node B` which contains a reference to node A.

In the presave hook of `node B`, calculations are made using the value of this status... this means the status of `node A` must be saved before starting the calculations.

This is why I am postponing the saving of `node B` with a shutdown function but all the end user messages are not displayed.

You can argue that in the presave hook of `node A`, I can call a function with the status and the `node A` as parameter which will do the job; but there are many exchanges/references like this one and to avoid a spaghetti code, I put all my business model code in the presave functions of those two kinds of nodes.

# Answer
> 2 votes
This is kind of pointless. You can't render the message, the page is already rendered. And you can't transfer the message to the next page refresh without a session. If a session existed, it would be closed by now. If there was no session, you couldn't open one now and send a session cookie after the response is already finished.

---
Tags: status-messages
---


title: Is by design, Drupal headless?

text:
# Question
Title: Is by design, Drupal headless?
If I understood correctly, "decoupling" a content management system means separating backend from frontend (having two "modules" for each under the same "program").

If I understood correctly, "headlessing" a content management system is making it to be possible to supply content to at least two different front-end interfaces at once ("two different programs" as may appear to an end user).

I would assume that by design, Drupal is not decoupled because it **does** have built in JavaScript (quick edit for a node, for example) but by design is it headless?

# Answer
> 2 votes
Headed means there is a GUI (Graphic User Interface). Headless means a system without that. Drupal can be run headless - there are headless distributions out there that can be used as the back-end for apps or a React site or whatnot. However, by default it's headed - Drupal core has a GUI out of the box. Decoupling refers to separating the head (GUI) and the back-end. Drupal is decoupled in that it separates the front end from the back-end.

> Drupal is by design not decoupled because it does have built in JavaScript almost anywhere

Drupal is decoupled, and it does have an extensive, powerful JavaScript API that ships with core: https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview

# Answer
> 1 votes
Drupal can be used decoupled, and as new features are added to Drupal 9, support for decoupling is given high priority under the API-First Initiative (ensure APIs are provided that allow programmers to select the parts of Drupal they want to use, and replace other parts as needed with custom solutions).

Historically, Drupal 7 + older versions are NOT decoupled (although older versions can e used headlessly with contributed modules), but starting from Drupal 8, decoupled use is supported by Drupal core itself.

Yes, Drupal does provide a frontend-- but this frontend is provided through modules that can be disabled. So Drupal can be used decoupled because it does not force you to use its UI; you can disable the Drupal UI and replace it with something else, like a React or Angular app.

**What does headless use actually look like?**

Contenta is a headless Drupal distribution that can give you an idea of how to set up Drupal for headless use.

To actually get at your content, Drupal provides support for JSON:API and REST in Core, and GraphQL is available via a contributed module. The JSON:API support is particularly great because it lets you expose all your content with Drupal permissions intact with very little additional configuration.

However, this is a pretty complex topic; there's a whole book on it:

There's also a great series of tutorials available at Drupalize.me.

---
Tags: 9
---


# Question
Title: The chosen directory does not exist and attempts to create it failed
When I tried to import the data it's showing as "The chosen directory does not exist and attempts to create it failed". What exactly is this I am not getting.

# Answer
I got the same error when I tried to create a Feed type using Feeds module. I selected the option `upload file` in Fetcher dropdown, and `CSV` in parser. And in Fetcher settings, in the textbox `Upload directory`, I put `public://feeds`.

To solve this error, I created a folder `feeds` in the location `.../web/sites/default/files` and changed the permission of the feeds folder to 777. And when I clicked on Save feed type, I no longer got the error.
> 1 votes

---
Tags: importing-exporting
---


# Question
Title: Is it possible to see which modules grants access rights?
Working on a Drupal 9 website the module Permissions by term gives an unexpected behaviour.

Trying to find the problem I got stuck. I think there might be another module overwriting the permissions. But I can’t find out which one.

Is there a way to see which module grants what right to a node/user?

Or is there another step I should do to debug these node permissions?

Thanks a lot!

# Answer
> 0 votes
Thanks for your help it was super informative! I found out that the Permissions by Entity module was creating the problem. Since I wasn't using it I could just uninstall it and all is working like expected now.

---
Tags: 9
---


# Question
Title: Make Title field "not required" or workaround how to prefill and hide it (drupal 9.2.8)
On my site (drupal 9.2.8) users add content and there is no need to put any information to 'Title' field. I was trying to make this field not required via form\_alter in custom module but it did not work:

```
function yourmodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id){
  if ($form['#form_id'] == 'node_somenode_form' or $form['#form_id'] == 'node_somenode_form'){
    $form['title']['widget']['0']['#required'] = FALSE;    // not working
    $form['title']['widget']['#required'] = FALSE;    // not working
    $form['title']['#required'] = FALSE;    // not working
  }
}

```

As a workaround in my module I prefill Title using form\_alter:

```
$form['title']['widget']['0']['value']['#default_value'] = 'any default title';

```

and then just hide this field from form. There are a few ways of doing that (In form alter what is the different way of hiding fields and what is the difference between them? ), I decided to use hide:

```
hide($form['title']);

```

but I am curies why `['#required'] = FALSE;` doesn't work for Title field but it works fine with other fields.

# Answer
You should use Automatic Entity Label:

> "Automatic Entity Label" is a small and efficient module that allows hiding of entity label fields.
> 1 votes

---
Tags: nodes, 9
---


# Question
Title: How to add query parameters in twig using path()?
I am following the code snippet provided here

```
{# Link to frontpage view. #} <a href="{{ path('view.frontpage.page_1') }}">{{ 'View all content'|t }}</a>

{# Link to user entity/profile page. #} <a href="{{ path('entity.user.canonical', {'user': user.id}) }}">{{ 'View user profile'|t }}</a>

{# Link to node page. #} <a href="{{ path('entity.node.canonical', {'node': node.id}) }}">{{ 'View node page'|t }}</a>

```

https://www.drupal.org/docs/8/theming/twig/functions-in-twig-templates#path

As the function says path($name, $parameters, $options), I want to add a link which has query parameters as shown in below

```
blue?destination=blue-search&cookie[blue_referrer]=3622&cookie[blue_type]=node

```

I tried implementing it as follows

```
<a href="{{
               path('blue_general.blue',
               {'node': node.id},
               {'query': [
                 'destination': path('view.blue_search.page_1'),
                 'cookie[blue_referrer]': node.id,
                 'cookie[blue_type]':'node'
                 ]
                }
               )
               }}">Find a Distributor</a>

```

but it gives an error

> The website encountered an unexpected error. Please try again later.Twig\_Error\_Syntax: An array element must be followed by a comma. Unexpected token "punctuation" of value ":" ("punctuation" expected with value ","). in Twig\_TokenStream-\>expect() (line 124 of /templates/node/node--product.html.twig). Twig\_ExpressionParser-\>parseArrayExpression() (Line: 205)

# Answer
Twig uses hashes where PHP uses associative arrays. So to fix your code just convert the array syntax to a hash literal:

```
{{
  path('blue_general.blue',
    {'node': node.id},
    {'query': {
      'destination': path('view.blue_search.page_1'),
      'cookie[blue_referrer]': node.id,
      'cookie[blue_type]':'node'
    }}
  )
}}

```
> 8 votes

# Answer
Here is an example of passing views contextual filter value into `path()` twig function:

```
{% set ical_path = path('view.events.ical', {arg_0: 12}) %}

```
> 3 votes

---
Tags: 8, theming
---


title: Time spent with custom module

text:
# Question
Title: Time spent with custom module
Can anyone help me with this, I want to activate time spent module on my custom form module, and store the time spent on a field on my form. I think that I could do it on the file `timespent.module` here.

```
function time_spent_init() {
  //if user has a role who is going to be tracked, add javascript code
  if (time_spent_user_has_role()) {
    $timer = variable_get('time_spent_timer', TIME_SPENT_TIMER);
    $limit = variable_get('time_spent_limit', TIME_SPENT_LIMIT);
    $mynide = -1; //false value
    //checks if this is a node page. taxonomy or views pages won't be counted.
    //confirm if this node type and user role will be tracked
    $nodetypes = variable_get('time_spent_node_types', 'all');
    if ($node = menu_get_object()) {
      if (($nodetypes == 'all' || $nodetypes[$node->type] === $node->type )) {
        $mynide = $node->nid;
      }
    }
    drupal_add_js(drupal_get_path('module', 'time_spent') . '/time_spent.js');
    drupal_add_js(array('time_spent' => array('timer' => check_plain($timer), 'limit' => check_plain($limit), 'nid' => $mynide, 'sectoken' => drupal_get_token())), 'setting');
  }
}

```

# Answer
Maybe this can help someone, I found how to do it.

I have a custom module to create a form and I want to make "timespent" work and track user time spend on this page.

I use URL to get it work.

```
$nodetypes = variable_get('time_spent_node_types', 'all');
if ($node = menu_get_object()) {
  if (($nodetypes == 'all' || $nodetypes[$node->type] === $node->type )) {
    $mynide = $node->nid;
  }
}

```

This code searches the id of the node, for my purposes I replace this like that

```
if (arg(0) == 'module' && arg(1) == 'form') {
  $mynide = arg(3); // for me arg(3) is the nid i want to track
}

```
> 0 votes

---
Tags: 7, forms
---


# Question
Title: How to add class to multiple view-blocks using twig?
I have four blocks to a view. I want to add class 'fancy-body' to all four blocks. How can I add that.

**view\_id** : categories

**display\_id's** of blocks : block\_1, block\_2, block\_3, block\_4

I know, I can add classes by creating individual twig template file for each block. like this,

```
block--views-block--categories-block-1.html.twig
block--views-block--categories-block-2.html.twig
block--views-block--categories-block-3.html.twig
block--views-block--categories-block-4.html.twig

```

Is it possible to achieve in single twig template file? How can I achieve that?

Need Help, Thank You!

# Answer
> 3 votes
If it's just classes you need, the modules mentioned in Joseph's answer should be enough.

If you need to make more alterations and definitely need a custom template file, you can implement `hook_theme_suggestions_HOOK()`.

Something like:

```
function module_theme_suggestions_block(array $variables) {
  $suggestions = [];

  if ($variables['elements']['#base_plugin_id'] == 'views_block') {
    if (preg_match('/views_block:categories-block_[0-9]+/', $variables['elements']['#plugin_id'])) {
      $suggestions[] = 'block__views_block__categories_all_blocks';
    }
  }

  return $suggestions;
}

```

With the above example your single template file would be named `block--views-block--categories-all-blocks.html.twig`.

# Answer
> 2 votes
There are several modules that allow you to add classes to blocks via admin, depending on where you're adding the blocks:

> The Layout Builder Component Attributes module allows editors to add HTML attributes to Layout Builder components (blocks). Attributes can be added to 1) the block (outer) element, 2) the block title, and 3) the block content (inner) element.

> Block Classes allows users to add classes to block title, content, and wrapper of any block through the block's configuration interface. This module extends the Block Class module features.

---
Tags: views, theming, blocks, css-js-aggregation
---


title: Altering custom template from code

text:
# Question
Title: Altering custom template from code
How can I programmatically remove blocks and other elements from a single custom template ?

In my custom modle I have defined a route to a controller which then loads a template which is also contained within the same module. I am successfully loading data from the controller into the template.

My issue is that the template inherits blocks and menu from the active theme which I need to remove but only in the modules template. For examples, the search and primary naviagation blocks are not supposed to display.

I had implemented :

```
function mymodule_export_preprocess_page(&$variables)
{
  unset($variables['page']['header']['searchform']);
  unset($variables['page']['menu_primary']);
}

```

But I later realised the seach and navigation blocks were removed not just in the module template but from every page on the site.

I also search for something like `hook_page_alter` that could not find anything appropriate for the task.

# Answer
I think you're confusing themes and templates.

A **theme** is a set of files that control the display of your site via the use of template files, CSS and Javascript (mostly that, there is more if you dig).

A **template file** is just one of those files in a theme.

So the reason that the logic in your `hook_preprocess_page` affects every page, is that you apply those modifications unconditionally. Those preprocess hooks (when defined in a module) are applied to the active theme for any page request, whichever theme is used.

It's a bit difficult to suggest a specific solution in your case without seeing further code, e.g. from your controller, but you can try something along these lines (pseudo-code):

```
function mymodule_export_preprocess_page(&$variables) {
  if (\Drupal::routeMatch()->getRouteName() === 'route.to.controller') {
    unset($variables['page']['header']['searchform']);
    unset($variables['page']['menu_primary']);
  }
}

```
> 2 votes

---
Tags: theming, 9
---


# Question
Title: Add a custom variable to an existing block
Is there a way to alter the render array of an existing block? I want to do it for a commerce block (cart block) but the question remains for any core or contrib module block.

When a block uses a custom #theme for it's block, is there a way to alter the definition?

Let's say the module defines the following theme :

```
/**
 * Implements hook_theme().
 */
function commerce_cart_theme($existing, $type, $theme, $path) {
  return [
    'commerce_cart_block' => [
      'variables' => [
        'icon' => NULL,
        'count' => NULL,
        'count_text' => '',
        'content' => NULL,
        'url' => NULL,
        'links' => [],
      ],
    ],
  ];
}

```

and the block uses `'#theme' => 'commerce_cart_block'` as it's build render array.

I'd like to know if there's a way to add a new custom variable to commerce\_cart\_block in a hook somewhere.

I tried to rewrite the commerce-cart-block.html.twig in my theme and add a new variable in it and I'd set my variable in THEME\_preprocess\_block\__commerce\_cart(), but it doesnt work.

I also tried to alter the block's content with HOOK\_block\_view\_alter in a module, but it also didn't work.

# Answer
You can do it with hook\_preprocess\_HOOK and the all caps `HOOK` part refers to the array key in `hook_theme()`.

```
/**
 * Implements hook_preprocess_HOOK().
 */
function mytheme_preprocess_commerce_cart_block(&$variables) {
  // Adds a custom variable
  $variables['hello_world'] = 'Hello World';
}

```

BTW, this hook can be used either in a custom theme or custom module.

and in **commerce-cart-block.html.twig**

```
{{ hello_world }}

```

should print "Hello World".
> 3 votes

---
Tags: blocks, hooks, 9
---


# Question
Title: Confirmation message on comment submission
When a comment is submitted, no confirmation message is shown and I would like to achieve this. I want to show the following message.

> Your comment has been added for review and approval.

I tried this code.

```
function mymod_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  else if ($form_id == 'comment_comment_form') {
    $form['actions']['submit']['#value'] = "your comment has been added for review and approval"
  }
}

```

This only edits the submission button text. Is there a way to add a submission confirmation message?

# Answer
Drupal already shows a similar message, to the users who has the *post comments* permission but not the *skip comment approval* permission.
There is no need to use custom code for that: Don't give the *skip comment approval* permission to the users whose comments must be approved.

If the purpose is changing the message shown by Drupal in that case (*Your comment has been queued for review by site administrators and will be published after approval.*), it's enough to change the message Drupal uses, for example adding the following line to the settings.php file.

```
$settings['locale_custom_strings_en'][''] = [
  'Your comment has been queued for review by site administrators and will be published after approval.' => 'Your comment has been added for review and approval',
];

```
> 4 votes

# Answer
You would need to add a custom submit handler, and set your message there.

```
function ahrq_form_alter(&$form, FormStateInterface $form_state, $form_id) {
    else if($form_id == 'comment_comment_form') {
       $form['actions']['submit']['#submit'][] = 'ahrq_comment_form_submit';
    }
}

function ahrq_comment_form_submit(&$form, FormStateInterface $form_state) {
  \Drupal::messenger()->addStatus->($this->t('Your comment has been added for review and approval'));
}

```
> 1 votes

---
Tags: forms, comments, 9
---


# Question
Title: get taxonomy terms of specified language
I know I can get list of of taxonomy of a vocabulary with

```
 $terms =  \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree('course_category');

 kint($terms);

```

but it returned all that vocabulary terms, I am working on multi language site,**I want retrieve terms of specified language.**

*my Drupal version is 8.1.3*

# Answer
> 3 votes
You can use the loadByProperties method to load all the entities that their fields correspond to certain values.

For a more advance selection you can use the entity query service.

```
\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
  'vid' => $vid,
  'langcode' => $language_id
]);

```

# Answer
> 3 votes
I had some difficulties with printing tags in the current language in code too. This is what worked to get ONLY the translated terms of a vocabulary:

```
$vocabulary = 'MY_VOCABULARY_NAME';
$language =  \Drupal::languageManager()->getCurrentLanguage()->getId();
$query = \Drupal::entityQuery('taxonomy_term');
$query->condition('vid', $vocabulary);
$query->sort('weight');
$tids = $query->execute();
$terms = \Drupal\taxonomy\Entity\Term::loadMultiple($tids);
$termList = array();

foreach($terms as $term) {
    if($term->hasTranslation($language)){
        $tid = $term->id();
        $translated_term = \Drupal::service('entity.repository')->getTranslationFromContext($term, $language);
        $termList[$tid] = $translated_term->getName();
    }
}

// To print a list of translated terms.
foreach($termList as $tid => $name) {
     print $name;
}

```

To link the tags, see https://drupal.stackexchange.com/a/243160/71941

# Answer
> 1 votes
This is similar to Stef Van Looveren's answer, but instead of loading all the taxonomy term IDs and then checking if each one has a specific translation, I include the language condition in the query.

```
$vocabulary = 'MY_VOCABULARY_NAME';
$language =  \Drupal::languageManager()->getCurrentLanguage()->getId();
$tids = \Drupal::entityQuery('taxonomy_term')
  ->condition('vid', $vocabulary)
  ->condition('langcode', $language)
  ->sort('weight')
  ->execute();
$terms = \Drupal\taxonomy\Entity\Term::loadMultiple($tids);
$termList = [];

foreach ($terms as $term) {
  $tid = $term->id();
  $translated_term = \Drupal::service('entity.repository')->getTranslationFromContext($term, $language);
  $termList[$tid] = $translated_term->getName();
}

```

This would return any term that has the `$language` translation, while `loadByProperties()` only returns the entity if the original language is `$language`.

---
Tags: 8, taxonomy-terms
---


# Question
Title: How to display the parent taxonomy term of the "Tags" field?
On my Drupal 9 site, I created taxonomy terms in the "tags" vocabulary (the vocabulary and the default field of the "article" content type) :

When I create a node, I select a taxonomy term in the form (for example "ICO"). The node displays the "tags" field :

I want that if the node has the term "ICO", it also displays the parent terms. Like this :

> Catégories : Culture geek, crypto, ICO

How can I do this? I did not find anything in the view mode regarding this field.

# Answer
> 2 votes
You need a field formatter for that, luckily there is one already that covers this use case, it's the Hierarchical Term Formatter that works with Drupal 9.

1. For the given taxonomy structure:

2. By selecting the terminal term (in this example 'jodri')

3. Installing the Hierarchical Term Formatter, with its default configuration

4. Results in the following display of the rendered node.

---
Tags: entities, taxonomy-terms, 9
---


# Question
Title: What causes a search_api based view to stay cached for anonymous users even after the index is updated?
I have a search api based view. The index this view is based on indexes new content as soon as it is published. When new contest is published, logged in users can indeed see the latest content at the top of the search results.

However, anonymous users don't see the newly published content until the cache is cleared. How do I avoid having to manually clear the cache? What should I check?

# Answer
Is it possible you have a "non Drupal" cache for anonymous users? Common such caches include Varnish (server based) or Cloudflare (cloud/cdn based)

Failing that, have you checked the cache settings on the view itself? I believe for search API the correct settings should be `Caching: Search API (tag based)` (under advanced in the view edit screen). If that doesn't work you could try turning off the views cache temporarily to at least determine if that is the root cause.

Finally check your modules are up to date, including Search API & check the issue queue to see if there are any patches that may solve your issue: https://www.drupal.org/project/issues/search\_api?categories

> “There are only two hard things in Computer Science: cache invalidation and naming things”
> 3 votes

# Answer
Looks like you should use the 'none' cache to option (if using solr or any facets) or 'Search API (tag-based)' option. There is a bug where this is not set automatically.

@see https://www.drupal.org/project/search\_api/issues/3196990

@see https://www.drupal.org/project/facets/issues/2939710
> 1 votes

---
Tags: 8, search
---


title: specific submit handler to user form

text:
# Question
Title: specific submit handler to user form
I have a submit handler to the user form. It redirect the user to a specific page.

```
use Symfony\Component\HttpFoundation\RedirectResponse as RedirectResponse;

/**
 * @file
 * Primary module hooks for EHESS Propositions 3 module.
 *
 * @DCG
 * This file is no longer required in Drupal 8.
 * @see https://www.drupal.org/node/2217931
 */
function ehess_propositions_3_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_form') {
    foreach (array_keys($form['actions']) as $action) {
      if (isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
        $form['actions'][$action]['#submit'][] = '_ehess_propositions_3_user_profile_submit';
      }
    }
  }
}

function _ehess_propositions_3_user_profile_submit($form, &$form_state) {
  $response = new RedirectResponse('/admin/content/reglement/');
  $response->send();
}

```

But the problem is that the submit handler is fired both for user update and user deletion. That block the redirection to "confirm deletion" page of user.

Is there a specific action to updating user form only and not user delete?

# Answer
$action can be `submit` and `delete`. You don't need the foreach loop if you don't want to attach the submit handler to all submit buttons.

Add the handler to the save button directly:

```
function ehess_propositions_3_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_form') {
    $form['actions']['submit']['#submit'][] = '_ehess_propositions_3_user_profile_submit';
  }
}

```

For the correct way to redirect in a form see https://drupal.stackexchange.com/a/191594/47547
> 0 votes

---
Tags: forms, 9
---


title: How to hook on paragraph deletion?

text:
# Question
Title: How to hook on paragraph deletion?
I'm on D9 and I've a paragraph inside a content type node.

I need to hook when the paragraph is deleted to do some custom logic and set up a queue.

I've tried `hook_entity_delete`, `hook_entity_predelete` or `hook_paragraph_delete`, `hook_paragraph_predelete`: those hooks run during cron, if the node containing the paragraph is deleted.

However, it seems to me those aren't triggered if an existing paragraph is deleted via the widget inside the edit form of the node.

Is there a way to intercept that case/solve my problem?

# Answer
You need to implement a node update hook and compare the paragraph IDs:

```
use Drupal\node\Entity\NodeInterface;

/**
 * Implements hook_ENTITY_TYPE_update() for node entities.
 */
function mymodule_node_update(NodeInterface $node) {
  if ($node->getType() == 'my_content_type') {
    $ids = array_column($node->field_paragraph->getValue(), 'target_id');
    $original_ids = array_column($node->original->field_paragraph->getValue(), 'target_id');
    // compare IDs
  }
}

```

If this is a nested paragraph then use hook\_paragraph\_update() for the parent paragraph type.
> 8 votes

---
Tags: entities, hooks, 9, paragraphs
---


# Question
Title: The sequence of actions upon cookie-authentication to Drupal site
There is a drupal 9 site that has a REST-endpoint that can be accessed for logged-in users (not for anonymous ones).

And, it's necessary to make a GET request to the endpoint using cookie authentication.

Could you clarify, what the correct algorithm of this request is?

I tried to do it two ways.

Way #1.

Send a POST-request to user/login with credentials and get CSRF-token, then use the token in headers of GET-request to my endpoint. No result.

Way #2.

Send a POST-request to user/login with credentials, then send a GET-request to session/token and get token from. After that, use this token from session/token in my GET request.

Also, no result.

In both cases, it returns the 401 error (from Insomnia) or the 403 error (from external JavaScript).

My headers to GET request are

```
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token
  }

```

The cookie authentication is checked for my REST endpoint in REST UI.

What am I doing wrong?

# Answer
When your endpoint requires cookie-auth, you should pass the cookie you get from your login-post with your following GET requests.

CSRF has nothing to do with auth, it's just a CSRF-attack defense.
> 1 votes

# Answer
You need to reference the stored cookie. Here's an example using curl:

```
curl --header "Content-type: application/json" --request POST -s -c cookie.txt --data-binary '{ "name": "username", "pass": "password" }' https://example.com/user/login

```

From that you will get your cookie in cookie.txt and your token in the json response.

Then you can do a get request like this (get requests don't need a CSRF token):

```
curl -s -X GET -b cookie.txt --header 'Content-type: application/json' ...

```

and a post request like this:

```
curl -s -X POST -b cookie.txt --header 'Content-type: application/json' --header "X-CSRF-Token: $token"

```
> 1 votes

---
Tags: 9, authentication-authorization, cookies
---


title: Set SameSite=strict for session cookies

text:
# Question
Title: Set SameSite=strict for session cookies
I'm trying to figure out how to set the SameSite cookie attribute for Drupal 8 session cookies, but I can't find a solution.

I thought SessionConfiguration might give me enough options, but it seems not.

Where can I perform such magic?

# Answer
Check this issue https://www.drupal.org/project/drupal/issues/3150614 You could set the following configuration in services.yml

```
parameters:
  session.storage.options:
    cookie_samesite: strict

```
> 1 votes

# Answer
Late to the party, but maybe still of use to someone.

In an environment where you can't control `services.yml`, e.g. because your organisation/client doesn't let you edit it (for security/compliance reasons for example) and if `ini_set('session.cookie_samesite', 'strict')` is not an option for you (PHP \> 7.3 according to https://www.php.net/manual/en/session.security.ini.php), you can use a session decorator.

You need a service definition, e.g. `my_modules.services.yml`

```
services:
  my_module.session_configuration:
    class: Drupal\my_module\Session\SessionConfiguration
    decorates: session_configuration
    decoration_priority: 9
    public: false
    arguments: ['@my_module.session_configuration.inner']

```

And the actual class, e.g. `my_module\src\Session\SessionConfiguration.php`:

```
namespace Drupal\my_module\Session;

use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Session\SessionConfigurationInterface;

class SessionConfiguration implements SessionConfigurationInterface {

  /**
   * Original service object.
   *
   * @var \Drupal\core\Session\SessionConfigurationInterface
   */
  protected $sessionConfiguration;

  /**
   * Constructs a new Session Configuration instance.
   *
   * @param \Drupal\core\Session\SessionConfigurationInterface $session_configuration
   *   The original session configuration service.
   */
  public function __construct(SessionConfigurationInterface $session_configuration) {
    $this->sessionConfiguration = $session_configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function hasSession(Request $request) {
    return $this->sessionConfiguration->hasSession($request);
  }

  /**
   * {@inheritdoc}
   */
  public function getOptions(Request $request) {
    $options = $this->sessionConfiguration->getOptions($request);
    $options['cookie_samesite'] = 'Strict';
    return $options;
  }
}

```

I saw variations of this decorator pattern in different examples on the web, sometimes extending the base service and sometimes implementing the appropriate interface instead. There is a good discussion about this here: https://www.phase2technology.com/blog/using-symfony-service-decorators-drupal-8
> 0 votes

---
Tags: 9, cookies
---


title: How to check if path alias exists

text:
# Question
Title: How to check if path alias exists
In Drupal 8, my custom module includes the code

```
if (\Drupal::service('path.alias_storage')->aliasExists($path, 'en')) {
    return true;
}

```

What should this look like in Drupal 9.2? Seems so simple, but I can't figure it out after reading every post I can find.

# Answer
This service was deprecated in Drupal 8 and removed in Drupal 9.

Path aliases were converted to an entity. You can use the entity storage and additionally the path\_alias.repository service for some common case-insensitive lookups:

```
$path_alias_repository = \Drupal::service('path_alias.repository');
if ($path_alias_repository->lookupByAlias($alias, 'en')) {
  return TRUE;
}

```

Interesting is that the service AliasRepository provides a pathHasMatchingAlias() method but not one for the other direction, so you have to check whether the lookup method above returns a result.
> 8 votes

---
Tags: path-aliases, 9
---


# Question
Title: How to use sql MAX() function in views plugin query
I am creating a custom views filter for a custom views field. The following SQL will accomplish the filter I seek:

```
select nid, moderation_state, content_entity_id, MAX(content_entity_revision_id)
  from node inner join content_moderation_state_field_revision on nid = content_entity_id
    where content_entity_id = 59 group by content_entity_id

```

In my views filter plugin php file, I have the following code to add the above sql to the filter query:

```
/**
   * Helper function that builds the query.
   */
  public function query() {
    if (!empty($this->value)) {
      $configuration = [
        'table' => 'content_moderation_state_field_revision',
        'field' => 'content_entity_id',
        'left_table' => 'node_field_data',
        'left_field' => 'nid',
        'operator' => '=',
      ];
      $join = Views::pluginManager('join')->createInstance('standard', $configuration);
      $this->query->addRelationship('content_moderation_state_field_revision', $join, 'node_field_data');
      $this->query->addExpression('MAX(content_moderation_state_field_revision.content_entity_revision_id)');
      $this->query->groupBy("content_moderation_state_field_revision.content_entity_id");
      $this->query->addWhere('AND', 'content_moderation_state_field_revision', $this->value, 'IN');
    }
  }

```

This error is generated when a query is executed:

> Error: Call to undefined method Drupal\views\Plugin\views\query\Sql::addExpression()

While **addExpression()** is part of core database api it is not supported in the views query plugin sql api,

So, I need a workaround to add the MAX() expression to the query. Any suggestions on how to do this?

# Answer
I found a workaround!

This SQL code essentially gets me what I am looking for:

```
select * from content_moderation_state_field_revision where (content_entity_id, content_entity_revision_id) in
  (
    select content_entity_id, MAX(content_entity_revision_id) from content_moderation_state_field_revision group by content_entity_id
  )
  and moderation_state = 'draft'

```

Note that the MAX and group by functions are all in the SQL subquery, which doesn't require any $row input. So, I created a function in my module to output the results of that subquery, and added it as a condition in the plugin filter query. Like this:

```
/**
   * Helper function that builds the query.
   */
  public function query() {
    if (!empty($this->value)) {
      // Views join handler plugin: https://api.drupal.org/api/drupal/core%21modules%21views%21src%21Plugin%21views%21join%21JoinPluginBase.php/group/views_join_handlers/8.2.x
      $configuration = [
        'type' => 'INNER',
        'table' => 'content_moderation_state_field_revision',
        'field' => 'content_entity_id',
        'left_table' => 'node_field_data',
        'left_field' => 'nid',
        'operator' => '=',
      ];
      $join = Views::pluginManager('join')->createInstance('standard', $configuration);
      $this->query->addRelationship('content_moderation_state_field_revision', $join, 'node_field_data');
      $this->query->addGroupBy("content_moderation_state_field_revision.content_entity_id");
      $revision_ids = sbn_moderation_state_sql_subquery(); // returns highest_entity_revision_id
      $this->query->addWhere('AND', 'content_entity_revision_id', $revision_ids, 'IN');
      $this->query->addWhere('AND', 'moderation_state', $this->value, 'IN');
    }
  }

```

I still have the addGroupBy clause, but I don't think it's necessary because the MAX function is in the subquery which takes care of returning the highest content\_entity\_revision\_id for each node.

Of course, I still don't know the correct syntax for adding MAX() function in views plugin query sql api syntax. If anyone knows, please add that answer as well.

Anyway, the filter works like a charm -- I can filter the view by published or draft nodes or both.
> 0 votes

---
Tags: views, 9, plugins
---


# Question
Title: Files in the file_managed table with status of 0, not deleting on cron
I have a form where I am uploading files to the "sites/default/files" directory. I am using the managed\_file type in my form API definition. This is all working fine and I notice that in the file\_managed table, I see my uploaded files with a status of 0 meaning they are temporary. I was under the impression that these files that have a status of 0 should get deleted, not only from the database, but physically from the server on cron run, but I do not see this happening. I see lots of files with a status of 0 and they are NOT in the file\_usage table, so they are not being used by anything, yet if I force a cron run, I don't get errors, yet the files do not get deleted either. Am I misunderstanding how this system works?

Thanks

# Answer
> 4 votes
They are deleted, but during cron tasks, and just when:

The code that deletes those temporary files is contained in `system_cron()`.

```
  // Remove temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
  // Use separate placeholders for the status to avoid a bug in some versions
  // of PHP. See http://drupal.org/node/352956.
  $result = db_query('SELECT fid FROM {file_managed} WHERE status <> :permanent AND timestamp < :timestamp', array(
    ':permanent' => FILE_STATUS_PERMANENT,
    ':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE
  ));
  foreach ($result as $row) {
    if ($file = file_load($row->fid)) {
      $references = file_usage_list($file);
      if (empty($references)) {
        if (!file_delete($file)) {
          watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->uri), WATCHDOG_ERROR);
        }
      }
      else {
        watchdog('file system', 'Did not delete temporary file "%path" during garbage collection, because it is in use by the following modules: %modules.', array('%path' => $file->uri, '%modules' => implode(', ', array_keys($references))), WATCHDOG_INFO);
      }
    }
  }

```

If the files are not getting deleted, and you don't see any error message in the watchdog log database, then you need to verify cron tasks are executed and the files are older than 6 hours. In the other cases, you should get an entry in the watchdog database table, even if that would be as generic as *Could not delete temporary file.*

# Answer
> 0 votes
Unused files are not converted anymore to temporary per default https://www.drupal.org/node/2891902 For them to be deleted after the cron, that conf must be added: $config\['file.settings'\]\['make\_unused\_managed\_files\_temporary'\] = TRUE;

Some module seems to interfere with that behaviour (see apaderno reply). In that case i used:

```
function amu_unused_file_delete_cron() {
  // get all files ids
  $fids = Drupal::entityQuery('file')->execute();
  $file_usage = Drupal::service('file.usage');
  // loop all fids and load files by fid
  foreach ($fids as $fid) {
    $file = Drupal\file\Entity\File::load($fid);
    $usage = $file_usage->listUsage($file);
    // check if file not used
    if (count($usage) == 0) {
      $file->delete();
    }
  }
}

```

---
Tags: files
---


# Question
Title: Set CSS class for a View programmatically in a hook
I want to set an extra class for the first page of a paged View.

```
/**
 * Implements hook_views_pre_render().
 */
function my_module_views_pre_render(ViewExecutable $view) {
  if ($view->id() === 'my_view' && $view->current_display === 'page_1') {
    // Somehow set/override the css class here.
  }
}

```

How can I change the CSS class set in the advanced settings of the View?

# Answer
> 4 votes
Something like this should do it:

```
$view->display_handler->setOption('css_class', 'your_class');

```

Good luck!

# Answer
> 2 votes
It works in drupal 9

```
 /**
 * Implements hook_views_pre_view().
 */
function MODULE_NAME_views_pre_view($view, $display_id, $args) {
  if ($view->id() == 'YOUR_VIEW_ID' && $display_id == 'YOUR_DISPLAY_NAME') {
    $view->display_handler->setOption('css_class', 'CLASS-NAME');
  }
}

```

---
Tags: views, 8, hooks
---


# Question
Title: Is it possible to create new node ID's when migrating content instead of using the existing one?
We're working on migrating a D7 site to D9 and will be creating new nodes (with new node ID's) before the site launches. The next time we sync the content from the D7 site to D9 the migration will overwrite the new nodes that we create on the new site.

Is there a way to run a migration and create new node ID's instead of using the old/existing ones?

# Answer
> 2 votes
Just don't specify the nid in your migration yaml files. The mapping between old and new is stored in your `migration_map_*` database table - so wherever else you reference a node - you will need to use the entity\_lookup plugin to "look up" what the new nids are.

For reference; https://www.drupal.org/node/1349696

> Fields
>
> nid - The Drupal node ID. Usually this will be unmapped - the nid will be automatically assigned when the node is created, and the map table will record the source key that generated this ID.

---
Tags: migrations
---


# Question
Title: Views, group by first letter as rest export (Alphabet/Glossary listing?)
Is there a way to group results by the first letter of their title in a REST export per the screenshot?

My first thought would be perhaps extending `Serializer` to process the results, at the expense of pagination. In my case, it is only about 100 records, and won't be paginated.

# Answer
I am not sure if this is the best solution, but I came up with this:

```
/**
 * The style plugin for serialized output formats.
 *
 * @ingroup views_style_plugins
 *
 * @ViewsStyle(
 *   id = "serializer_glossary",
 *   title = @Translation("Serializer Glossary"),
 *   help = @Translation("Serializes results and groups them by first letter, for small result lists only."),
 *   display_types = {"data"}
 * )
 */
class SerializerGlossary extends Serializer {

  /**
   * {@inheritdoc}
   */
  public function render() {
    $rows = [];

    foreach ($this->view->result as $row) {
      $entity = $this->view->rowPlugin->render($row);
      $title = $entity->label();
      $first_letter = Unicode::strtoupper($title[0]);
      $rows[$first_letter][] = $this->view->rowPlugin->render($row);
    }

    // Get the content type configured in the display or fallback to the
    // default.
    if ((empty($this->view->live_preview))) {
      $content_type = $this->displayHandler->getContentType();
    }
    else {
      $content_type = !empty($this->options['formats']) ? reset($this->options['formats']) : 'json';
    }

    return $this->serializer->serialize(['results' => ['items' => $rows, 'totalCount' => count($this->view->result)]], $content_type);
  }
}

```

I am open to other answers if there is a better way.

The frontend is processing this via ReactJS to do the screenshot in the original question. The reason it is wrapped with 'results' and then 'items' is from a request from the frontend developer to have it structured that way.
> 2 votes

# Answer
I haven't played with REST export so I don't know how it might affect your case, but I don't see why it wouldn't work if you set up your fields right.

## Use `Trim this field` and `Grouping field Nr.1`

1. Create the usual Views of your content showing the Title, Body summary or whatever is applicable in your case
2. Create another **Title** field or the one you want to group your Views by, even if you already have it from the first step.
3. Set that field to
**Don't!** Create a label
**Exclude from display**
**Formatter** **Plain text**
**Rewrite results:**

* **Trim this field to a maximum number of characters**
    * Maximum number of characters **1**
    * Untick “Trim only on a word boundary”
    * Untick “Add "…" at the end of trimmed text”

This is how you get the Title field to only display the first letter.
4. Now edit the **Format: Settings** and under the **Grouping field Nr.1** select that field (mind the order of fields since you'll have two)
5. **Add Sorting criteria** by **Title** field
6. Setup your REST exporter if you haven't already
> 0 votes

# Answer
There is an open issue at drupal.org to Add grouping to views REST Serializer formatter.

Check out the latest patch there, that allows to enable the grouping on REST views serializer formatter.

After activating the patch, you could do the regular grouping, as @prkos described in his answer.

If you do not want to patch the core, alternatively you can create a custom views style:

```
<?php

namespace Drupal\my_module\Plugin\views\style;

use Drupal\Core\Render\Element;
use Drupal\rest\Plugin\views\style\Serializer;

/**
 * The style plugin for grouped serializer output format.
 *
 * @ingroup views_style_plugins
 *
 * @ViewsStyle(
 *   id = "serializer_grouped",
 *   title = @Translation("Serializer Grouped"),
 *   help = @Translation("Provides possibility to groups serialized results."),
 *   display_types = {"data"}
 * )
 */
class SerializerGrouped extends Serializer {

  /**
   * {@inheritdoc}
   */
  protected $usesGrouping = TRUE;

  /**
   * {@inheritdoc}
   */
  public function render() {
    $rows = [];

    // Group the rows according to the grouping instructions, if specified.
    $sets = $this->renderGrouping(
      $this->view->result,
      $this->options['grouping'],
      TRUE
    );
    $groups = $this->renderGroupingSets($sets);

    foreach ($groups as $group) {
      foreach (Element::children($group) as $key) {
        $group_id = trim(strip_tags((string) $group['#title']));
        $rows[$group_id][] = $group[$key];
      }
    }

    // Get the content type configured in the display or fallback to the
    // default.
    if ((empty($this->view->live_preview))) {
      $content_type = $this->displayHandler->getContentType();
    }
    else {
      $content_type = !empty($this->options['formats']) ? reset($this->options['formats']) : 'json';
    }

    return $this->serializer->serialize($rows, $content_type, ['views_style_plugin' => $this]);
  }

  /**
   * {@inheritdoc}
   */
  protected function renderRowGroup(array $rows = []) {
    return $rows;
  }

}

```
> 0 votes

---
Tags: views, 8
---


# Question
Title: How do I omit webforms submission number (serial) from results?
We have webforms that use Webform Entity Print (PDF) Attachment. These PDFs have Submission number show up right after form's headline. How do I hide this number so it doesn't show up in the results?

# Answer
> 2 votes
In Submission General Settings area in "Submissions label" enter available token of your choice from "next submission number" area. For example adding \[webform\_submission:submitted-to\]: \[current-date:site\_short\_format\] to Submission label will show webform's title and date in the results.

---
Tags: webforms
---


# Question
Title: How to change the drush default timeout of 14400?
We face a major problem for our client.

To put it in context, this is a Drupal 8 with commerce that has a large number of products that come from a PIM. To be clearer, the products are not managed in Drupal but, on the other hand, we have to import them into Drupal every night. This is mass import that lasts quite a long time, a complete import of all products can take up to 14h, a differential import usually takes 2h. The trouble is that recently we have come across a recurring timeout problem of 14400s. After some research, we found that the problem came from a modification of Drush that adds a timeout of 14400s by default.

After much research we can not figure out how to change this default timeout, would anyone have an idea?

Here is a link: https://github.com/drush-ops/drush/blob/d6ca80437f74a7428f002e3f956e47977d39a17a/src/Drush.php#L76

Edit: We have no idea how to change this timeout properly.

Without solution, we will return to a previous version of Drush or we will apply a patch to change the value directly.

Edit 2: Here is the code of the DrushCommands:

```
<?php

namespace Drupal\custom_product_import\Commands;

use Drush\Commands\DrushCommands;
use Drupal\maps_suite\Log\Logger;
use Drupal\maps_import\Log\Observer\Mail as MailObserver;
use Drupal\maps_suite\Log\Context\Context;

/**
 * A Drush commandfile.
 *
 * In addition to this file, you need a drush.services.yml
 * in root of your module, and a composer.json file that provides the name
 * of the services file to use.
 *
 * See these files for an example of injecting Drupal services:
 *   - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php
 *   - http://cgit.drupalcode.org/devel/tree/drush.services.yml
 */
class CustomProductImportCommands extends DrushCommands {

  /**
   * Process the given operation.
   *
   * @param $pid
   *   The MaPS Import profile id.
    * @param array $options An associative array of options whose values come from cli, aliases, config, etc.
   * @option op
   *   Specify the operations to process (separated by commas)
   * @option list
   *   List the available operations
   * @option diff
   *   Differential import
   * @option thread
   *   Specify the thread to process
   * @option process
   *   Specify the process id to process
   * @usage maps-import 1
   *   Process all available operations for the profile with the pid 1.
   * @usage maps-import 1 --op=library_mapping,objects_fetch
   *   Process the "objects_fetch" operation, then the "library_mapping" operation, in the right order.
   * @usage maps-import 1 --list
   *   List all the available operations for the profile with the pid 1
   *
   * @command custom:maps-import
   * @aliases cust-m-i,custom-maps-import
   */
  public function mapsImport($pid, array $options = ['op' => null, 'list' => null, 'diff' => null, 'thread' => null, 'process' => null]) {
    // See bottom of https://weitzman.github.io/blog/port-to-drush9 for details on what to change when porting a
    // legacy command.
    if (is_null($pid)) {
      echo "You must specify a profile id as first parameter.\n";
      return FALSE;
    }

    // Are the thread specified in the parameters?
    $thread = NULL;
    if ($options['thread']) {
      $thread = $options['thread'];
    }

    // Are the process specified in the parameters?
    $process = NULL;
    if ($options['process']) {
      $process = $options['process'];
    }

    if ($options['list']) {
      drush_maps_import_list($pid);
      return;
    }

    $vars = \Drupal::config('maps_import.settings');

    // Check if there is already operations processing.
    if ($vars->get('maps_import_lock')) {
      drush_set_error('maps_import_lock', 'There is already a running operation.');
      drush_die("\n");
    }

    $profile = _maps_import_profile_load($pid);
    $operations = maps_import_get_import_operations($profile);

    // Are the operations specified in the parameters?
    if ($ops = $options['op']) {
      $ops = array_map('trim', explode(',', $ops));

      // Check if all the given operations exist.
      if ($invalid = array_diff($ops, array_keys($operations))) {
        echo 'The following operations are not defined: ' . implode(', ', $invalid) . "\n";
        return FALSE;
      }

      // Update the $operations variables with the specified operations.
      $operations = array_intersect_key($operations, array_flip($ops));
    }

    // Set up lock.
    \Drupal::configFactory()
      ->getEditable('maps_import.settings')
      ->set('maps_import_lock', 1)
      ->save();

    // Differential call.
    if ($options['diff']) {
      // Check if we can process the differential call.
      if ($profile->getOptionsItem('differential') && $vars->get('maps_import:configuration_full:' . $profile->getPid()) && $vars->get('maps_import:objects_full:' . $profile->getPid())) {
        \Drupal::configFactory()
          ->getEditable('maps_import.settings')
          ->set('maps_import_differential', 1)
          ->save();
      }
    }

    // Process chosen operations.
    $batch_operations = array();
    foreach ($operations as $name => $operation) {
      printf($operation['title'] . "\n");
      if ($operation['class'] == 'Drupal\custom_product_import\CustomProductImportMapping\CustomProductMapping') {
        $op = new $operation['class']($profile, $thread, $process);
      } else {
        $op = new $operation['class']($profile);
      }
      $batch_operations = array_merge($batch_operations, $op->batchOperations());
    }

    $batch = array(
      'operations'          => $batch_operations,
      'title'               => t('Update for the profile %title', array('%title' => $profile->getTitle())),
      'finished'            => '\Drupal\custom_product_import\CustomProductImportMapping\CustomProductMapping::ImportFinished',
      'maps_import_profile' => $profile->id()
    );

    batch_set($batch);
    drush_backend_batch_process();
  }
}

```

# Answer
You can use a script like this to specify the timeout. I run the Drush command to clear the cache and set a timeout of 1 second.

```
<?php

use Consolidation\SiteProcess\ProcessManager as ConsolidationProcessManager;

use Consolidation\SiteProcess\Util\Escape;
use Psr\Log\LoggerInterface;
use Consolidation\SiteAlias\SiteAliasInterface;
use Consolidation\SiteProcess\Factory\TransportFactoryInterface;
use Symfony\Component\Process\Process;
use Drush\Drush;
use Drush\Style\DrushStyle;
use Consolidation\SiteProcess\ProcessBase;
use Consolidation\SiteProcess\SiteProcess;
use Webmozart\PathUtil\Path;

echo Drush::getTimeout();
$process = new Process('drush cr');
$process->setTimeout(1);
$process->run();
echo $process->getOutput();
echo $process->getWorkingDirectory();
echo $process->getErrorOutput();

```

The cache rebuild command will timeout if it takes longer than 1 second. You can set it to 0 for no timeout.
> 2 votes

---
Tags: 8, drush
---


title: Read view field value in VBO action

text:
# Question
Title: Read view field value in VBO action
I have a view grid that has custom actions to perform based on selected grid row. To read the view fields from the grid dpm-ed the below command:

`dpm($this->view->result);`

The above command returns:

I am trying to specifically read the "field\_comment\_id\_comment\_field\_data\_nid" and I get null every time:

```
 dpm($this->view->result[0]->field_comment_id_comment_field_data_nid);

```

Update: I modified the dpm and I see the value correctly. Unsure of how to loop through the result array. still trying.

# Answer
Alright. Below solution works:

```
foreach($this->view->result as $result)
  dpm($result->field_comment_id_comment_field_data_nid);
}

```

thanks @clive for pointing on the array thing.
> 0 votes

---
Tags: 8, views
---


# Question
Title: How to prevend duplicates of content in a view?
On a multilingual drupal website (version 9.2.10) A block duplicates content on the landing page when 2 of more languages are enabled.

The block shows random published content for a custom content type in a grid system with a cards layout.

Due to 2 extra enabled languages, for example Dutch and German, Dutch content is duplicated and showed 3 times in this views grid now.

This duplication of content didn't appear when the languages where disabled.

Currently the view is rendering language by the selection "interface text language selected for page"

**Update** *current view settings*: **Note:**

* under **Advanced** --\> Query settings --\> Distinct is enabled

```
      query:
        type: views_query
        options:
          disable_sql_rewrite: false
          distinct: true
          replica: false
          query_comment: ''
          query_tags: {  }

```

View displays duplicates of *"Humo Dutch"*:

# Answer
> 4 votes
It's duplicated because you displaying now all the languages. You need to make sure on a multilingual site, that you list a single language per content in views. The suggested by me, to add a filter with Default translation (= True). Then set the rendering language to the correct type which you need. This will ensure that all contents will be displayed. However, if you have content that is not translated, it will be visible in the original language. If you need to display only the current language you can set the content language filter (Translation language (= interface text language selected for page)) instead of the original, but in this case, there's no fallback language.

---
Tags: views, 9
---


# Question
Title: Site stuck in 'headers have already been sent' login loop after D9 upgrade
Suggestions for debugging a site which is now stuck in a "headers have already been sent" loop after successful login (redirect back to login form)?

This is a production server for a site that's just been upgraded to Drupal 9. Session handling is absolutely fine on the development server so I'm sceptical about it being whitespace, closing tag issue (my one custom module is fine).

* Comparing against default.settings.php, I can't see anything obviously wrong with the settings.php file.
* Trusted sites array is set correctly.
* There's a valid temp directory (/tmp)
* All packages (Debian 10.11, Nginx, PHP 7.4, MariaDB, memcached) up to date.
* Restarted all the services. Restarted the server.
* No pending drush updates.
* The status report (before I logged out) was fine.
* Tried disabling the tfa/ga\_login modules, no difference.
* upgrade\_status was 100% ready before I did the D8-\>9 upgrade.
* I've run `composer upgrade` on production as well to look for any differences.
* Same PHP version on dev and production.

Any other ideas? (Anything in Drupal config I ought to look at, as there are likely to be differences between the two machines - config split etc.)

Thanks…

```
RuntimeException: Failed to start the session because headers have already been sent by "/var/www/sitename/vendor/symfony/http-foundation/Response.php" at line 384. in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start() (line 152 of /var/www/sitename/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php) #0 /var/www/sitename/web/core/lib/Drupal/Core/Session/SessionManager.php(162): Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start()
#1 /var/www/sitename/web/core/lib/Drupal/Core/Session/SessionManager.php(193): Drupal\Core\Session\SessionManager->startNow()
#2 /var/www/sitename/vendor/symfony/http-foundation/Session/Session.php(189): Drupal\Core\Session\SessionManager->save()
#3 /var/www/sitename/web/core/lib/Drupal/Core/StackMiddleware/Session.php(60): Symfony\Component\HttpFoundation\Session\Session->save()
#4 /var/www/sitename/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\Core\StackMiddleware\Session->handle()
#5 /var/www/sitename/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
#6 /var/www/sitename/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass()
#7 /var/www/sitename/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\page_cache\StackMiddleware\PageCache->handle()
#8 /var/www/sitename/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
#9 /var/www/sitename/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
#10 /var/www/sitename/web/core/lib/Drupal/Core/DrupalKernel.php(717): Stack\StackedHttpKernel->handle()
#11 /var/www/sitename/web/index.php(19): Drupal\Core\DrupalKernel->handle()
#12 {main}.

```

# Answer
Fixed by using `composer install --no-dev`, as I should have originally, which removed a lot of unnecessary packages. Unclear which of them would have broken it.

It's not that, it's this bug with the redirect\_after\_login module, for which there's a patch.

I *thought* I'd fixed it after getting rid of the dev packages, but I was getting confused because I used a one-time link (generated by `drush user`) which worked, and I could navigate freely and remain logged in.

The cookies were OK in that case, as redirect\_after\_login seemingly doesn't get called for one time links. I looked through the module list, saw it, realised I hadn't tried uninstalling it, did so and then checked the issue queue…

Also worth mentioning: should you uninstall the TFA module and later need to reinstall it, you'll need to grab the relevant rows from your backup of `users_data` (module=tfa) to reinstall people's seeds, saved one time codes etc.
> 1 votes

---
Tags: 9, sessions
---


# Question
Title: How to configure upload of webform document files to public folders?
When configuring the storage location of a webform document file,i am not proposed to changed it from private to public.

That is problématic to expose that URI to a distant server.

# Answer
On /admin/structure/webform/config/elements, select *Allow files to be uploaded to public file system*.

Enabling it isn't recommended, though. See Drupal file upload by anonymous or untrusted users into public file systems -- PSA-2016-003.
> 3 votes

---
Tags: webforms
---


title: Webform Table missing in Views

text:
# Question
Title: Webform Table missing in Views
I have a webform created on our site we want to be able to show the user its submitted values and everything seems to be working but the table, I have downloaded webform views. Please help i'm not very good with css but learning. My thinking is the css in the theme but unsure. We have a drupal 7 site and its seems to show up the way its suppose to.

# Answer
For Drupal 8/9 you need to install the Webform View Integration module.
> 0 votes

---
Tags: 7, webforms
---


# Question
Title: Submit form and disable submission button
I want to disable the submit button after it is clicked to prevent multiple submissions. I have:

```
$form['show'] = [
'#type' => 'submit',
    '#value' => $textforsubmitbutton,
    '#name' => 'submit',
    '#attributes' => array("onclick" => "javascript: this.disabled = true;"
    ),
];

```

but this disabled the button completely. Using "onsubmit" in place of "onclick" doesn't change anything on the button. So maybe I need something like:

```
    '#attributes' => array("onclick" => "javascript: this.submit; this.disabled = true;"
    ),

```

How do i fix this?

# Answer
I am using bootstrap - that's where i found the solution finally:

```
'#attributes' => array("onclick" => "
      jQuery(this).addClass('disabled');;
")

```

Works perfectly!
> 3 votes

# Answer
For those wanting to disable the form submit button on load, try this:

```
$form['actions']['submit']['#attributes']['disabled']  = 'disabled';

```
> 6 votes

# Answer
In your context, `this` refers to the submit input element which doesn't have the method `submit()`. You can use some jQuery to disabled the element on click then find the form element to submit.

```
'#attributes' => array("onclick" => "
      jQuery(this).attr('disabled', true);
      jQuery(this).parents('form').submit();
"

```
> 4 votes

# Answer
I faced the same issue. This works fine for me, although I don't like inlining this piece of js and would move it to a .js file.

```
$form['actions']['submit']['#attributes']['onclick'][] = "
  event.preventDefault();
  if (!form.reportValidity()) {
    form.reportValidity();
  }
  else {
    jQuery(this).after('<div class=\"ajax-progress ajax-progress-throbber\"><div
      class=\"throbber\"> </div></div>')
    jQuery(this).attr('disabled', true);
    jQuery(this).closest('form').submit();
  }
";

```

This adds the default ajax loading icon and disables the submit button. I did that for the custom Drupal commerce module. Also, this makes the default browser validation work using reportValidity(). Well, except for IE of course.
> 1 votes

# Answer
This might help https://www.drupal.org/project/inline\_entity\_form/issues/3160683

just simply insert drupal/core form JS to prevent the duplication issue.

```
$form['#attached']['library'][] = 'core/drupal.form';

```
> 0 votes

# Answer
For those wanting a separate JS file to better manage the JS, you can create a library and attach an event.

Add the library to your my\_module.libraries.yml file.

```
my_module.disable_on_click:
  version: VERSION
  dependencies:
    - core/drupal.form
    - core/jquery
  js:
    js/my_module_disable_on_click.js: { }

```

Attach the library to your form.

```
$form['#attached']['library'][] = 'my_module/my_module.disable_on_click';

```

Also add a class to your submit button. (You may wish to append or replace class, whatever you'd prefer).

```
$form['actions']['submit'] = [
    '#attributes' => [
      'class' => ['disable-on-click'],
    ],
  ];

```

Now you're free to edit js/my\_module\_disable\_on\_click.js how you want. This is mine that disables submit, adds the ajax loading gif and submits the form.

```
(function ($) {
  'use strict';

  Drupal.behaviors.disableOnClick = {
    attach: function (context, settings) {

      $('.disable-on-click').on('click', function(event) {
        $(event.target).prop('disabled', true);
        let button_text = $(event.target).text();
        $(event.target).html(button_text + ' <img src="/core/misc/throbber-active.gif">');
        $(event.target).parents('form').submit();
      });

    }
  };

}(jQuery));

```
> 0 votes

---
Tags: forms, 8
---


title: Automatically import fontyourface fonts

text:
# Question
Title: Automatically import fontyourface fonts
Is is possible to automatically import selected fontyourface fonts via config? Ideally, this would be just the fonts I've selected, not all from the source API.

I'm in the middle of a D7 -\> D9 migration, and I keep having to manually press that 'Import from fontsquirrel\_api' button each time I test the migration from scratch. And that entails downloading all 1021 FontSquirrel fonts, rather than just the 4 I'm using.

# Answer
A few minutes after posing the question, I found a potential solution: dump the entire `fontyourface_font` table, and then import it during deployment.

It's not quite as elegant as I had hoped, but it'll work if there's nothing better.

(https://www.drupal.org/project/fontyourface/issues/2978933#comment-13847846)
> 0 votes

---
Tags: 9
---