# Question
Title: Using the off-canvas dialog, how can I submit and close it, without redirect?
I use this as link:

```
<a
  class="use-ajax"
  data-dialog-options="{"width":800}"
  data-dialog-renderer="off_canvas"
  data-dialog-type="dialog"
  href="/group/1/content/create/group_node%3Apage"
>
  Create page
</a>

```

When submitting the page, I like the dialog to close without redirecting the new page created.

How can this be accomplished? If I add ?destination= with the current URL, then the URL of the off-canvas page is used, not the current page.

# Answer
> 1 votes
Add an Ajax callback to the submit button:

```
$form['actions']['submit'] = [
  '#type' => 'submit',
  '#value' => $this->t('Submit'),
  '#ajax' => [
    'callback' => '::ajaxCloseForm',
  ],
];

```

Which sends a CloseDialogCommand:

```
/**
 * AJAX callback to close the off-canvas dialog
 */
public function ajaxCloseForm(array&$form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  $response->addCommand(new CloseDialogCommand('#drupal-off-canvas'));
  return $response;
}

```

You can also add additional Ajax commands to update the DOM of the page to see the effect of the submit without reloading the page.

---
Tags: 9
---


# Question
Title: Issues updating from 9.4.3 with composer
I have a project that wasn't updated since a few months and I am stuck on 9.4.3 with composer refusing to update.

Here is my composer.json

```
{
    "name": "drupal-composer/drupal-project",
    "description": "Project template for Drupal 9 projects with Composer",
    "type": "project",
    "license": "GPL-2.0-or-later",
    "authors": [
        {
            "name": "",
            "role": ""
        }
    ],
    "repositories": [
        {
            "type": "composer",
            "url": "https://packages.drupal.org/8"
        }
    ],
    "require": {
        "php": ">=7.3",
        "buzzingpixel/twig-switch": "^1.0",
        "composer/installers": "^1.9",
        "cweagans/composer-patches": "^1.7",
        "drupal/admin_toolbar": "^3.0",
        "drupal/auto_entitylabel": "^3.0@beta",
        "drupal/better_exposed_filters": "^5.0@beta",
        "drupal/better_social_sharing_buttons": "^3.2",
        "drupal/ckeditor_media_embed": "^1.11",
        "drupal/ckeditor_youtube": "^1.3",
        "drupal/components": "^2.2",
        "drupal/console": "~1.0",
        "drupal/core-composer-scaffold": "^9.1",
        "drupal/core-recommended": "^9.1",
        "drupal/devel": "^4.1",
        "drupal/editor_advanced_link": "^2.0",
        "drupal/eu_cookie_compliance": "^1.19",
        "drupal/fontawesome": "^2.18",
        "drupal/gin": "^3.0@beta",
        "drupal/gin_toolbar": "^1.0@beta",
        "drupal/google_analytics": "^4.0",
        "drupal/jquery_minicolors": "^1.3",
        "drupal/mailchimp": "^2.0",
        "drupal/migrate_plus": "^6.0",
        "drupal/migrate_source_csv": "^3.5",
        "drupal/migrate_tools": "^6.0",
        "drupal/paragraphs": "^1.12",
        "drupal/pathauto": "^1.8",
        "drupal/quick_node_clone": "^1.14",
        "drupal/rabbit_hole": "^1.0@beta",
        "drupal/radix": "^4.10",
        "drupal/recaptcha": "^3.0",
        "drupal/search_api": "^1.20",
        "drupal/search_api_db": "^1.20",
        "drupal/search_api_page": "^1.0@beta",
        "drupal/signature_field": "^2.1",
        "drupal/token": "^1.9",
        "drupal/views_bootstrap": "^4.3",
        "drupal/views_bulk_edit": "^2.5",
        "drupal/views_bulk_operations": "^4.1",
        "drupal/views_simple_math_field": "^3.0",
        "drupal/webform": "^6.0",
        "drupal/weight": "^3.3",
        "drush/drush": "^10.3",
        "kint-php/kint": "^3.3",
        "twig/twig": "2.15.2",
        "vlucas/phpdotenv": "^5.1",
        "webflo/drupal-finder": "^1.2"
    },
    "require-dev": {
        "drupal/core-dev": "^9.1",
        "zaporylie/composer-drupal-optimizations": "^1.2"
    },
    "conflict": {
        "drupal/drupal": "*"
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "config": {
        "discard-changes": true,
        "sort-packages": true,
        "allow-plugins": {
            "composer/installers": true,
            "dealerdirect/phpcodesniffer-composer-installer": true,
            "drupal/console-extend-plugin": true,
            "cweagans/composer-patches": true,
            "drupal/core-composer-scaffold": true,
            "zaporylie/composer-drupal-optimizations": true
        }
    },
    "autoload": {
        "classmap": [
            "scripts/composer/ScriptHandler.php"
        ],
        "files": [
            "load.environment.php"
        ]
    },
    "scripts": {
        "pre-install-cmd": [
            "DrupalProject\\composer\\ScriptHandler::checkComposerVersion"
        ],
        "pre-update-cmd": [
            "DrupalProject\\composer\\ScriptHandler::checkComposerVersion"
        ],
        "post-install-cmd": [
            "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"
        ],
        "post-update-cmd": [
            "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"
        ]
    },
    "extra": {
        "drupal-scaffold": {
            "locations": {
                "web-root": "web/"
            }
        },
        "installer-paths": {
            "web/core": [
                "type:drupal-core"
            ],
            "web/libraries/{$name}": [
                "type:drupal-library"
            ],
            "web/modules/contrib/{$name}": [
                "type:drupal-module"
            ],
            "web/profiles/contrib/{$name}": [
                "type:drupal-profile"
            ],
            "web/themes/contrib/{$name}": [
                "type:drupal-theme"
            ],
            "drush/Commands/contrib/{$name}": [
                "type:drupal-drush"
            ]
        },
        "composer-exit-on-patch-failure": true,
        "patchLevel": {
            "drupal/core": "-p2"
        },
        "patches": {
            "drupal/devel": {
                "Brings back the Available Methods & Properties tabs for Kint dumps — Issue #221": "https://raw.githubusercontent.com/politsin/snipets/master/patch/kint.patch"
            },
            "drupal/better_exposed_filters": {
                "Fix error with exposed filters arguments": "https://www.drupal.org/files/issues/2020-09-18/3159875-8.patch"
            },
            "drupal/better_social_sharing_buttons": {
                "HTMLSpecialChars error on login page": "https://www.drupal.org/files/issues/2021-06-22/3216304-4.patch"
            },
            "drupal/search_api_page":  {
                "PHP 8.1 strpos() error": "https://www.drupal.org/files/issues/2022-05-31/3274050-4-php-8-warning-pathprocessorsearchapipage.patch"
            }
        }
    }
}

```

If I run the following, it just doesn't update anything.

```
$ composer update "drupal/core-*" --with-all-dependencies

```

So I tried:

```
$ composer why-not drupal/core 9.4.12
drupal/core-recommended  9.4.3  requires  drupal/core (9.4.3)

```

And also:

```
$ composer prohibits drupal/core-recommended 9.4.5
drupal/core-recommended         9.4.5        requires          drupal/core (9.4.5)
drupal-composer/drupal-project  dev-develop  does not require  drupal/core (but 9.4.3 is installed)

```

And also with 9.5.7 which is the *recommended* version I see from the admin:

```
$ composer prohibits drupal/core-recommended 9.5.7
drupal/core-recommended         9.5.7        requires          drupal/core (9.5.7)
drupal-composer/drupal-project  dev-develop  does not require  drupal/core (but 9.4.3 is installed)
drupal/core-recommended         9.5.7        requires          laminas/laminas-diactoros (~2.14.0)
drupal-composer/drupal-project  dev-develop  does not require  laminas/laminas-diactoros (but 2.11.3 is installed)
drupal/core-recommended         9.5.7        requires          laminas/laminas-stdlib (~3.11.0)
drupal-composer/drupal-project  dev-develop  does not require  laminas/laminas-stdlib (but 3.7.1 is installed)
drupal/core-recommended         9.5.7        requires          symfony/polyfill-ctype (~v1.27.0)
drupal-composer/drupal-project  dev-develop  does not require  symfony/polyfill-ctype (but v1.25.0 is installed)
drupal/core-recommended         9.5.7        requires          symfony/polyfill-iconv (~v1.27.0)
drupal-composer/drupal-project  dev-develop  does not require  symfony/polyfill-iconv (but v1.25.0 is installed)
drupal/core-recommended         9.5.7        requires          symfony/polyfill-intl-idn (~v1.27.0)
drupal-composer/drupal-project  dev-develop  does not require  symfony/polyfill-intl-idn (but v1.25.0 is installed)
drupal/core-recommended         9.5.7        requires          symfony/polyfill-intl-normalizer (~v1.27.0)
drupal-composer/drupal-project  dev-develop  does not require  symfony/polyfill-intl-normalizer (but v1.25.0 is installed)
drupal/core-recommended         9.5.7        requires          symfony/polyfill-mbstring (~v1.27.0)
drupal-composer/drupal-project  dev-develop  does not require  symfony/polyfill-mbstring (but v1.25.0 is installed)
drupal/core-recommended         9.5.7        requires          symfony/polyfill-php80 (~v1.27.0)
drupal-composer/drupal-project  dev-develop  does not require  symfony/polyfill-php80 (but v1.25.0 is installed)
drupal/core-recommended         9.5.7        requires          twig/twig (~v2.15.4)
drupal-composer/drupal-project  dev-develop  requires          twig/twig (2.15.2)

```

Any idea what is going on? I am having a hard time figuring out what prevents the update.

# Answer
> 2 votes
As soon as you remove `twig/twig` from your *composer.json* it will work.

```
$ composer remove twig/twig --no-update
$ composer update "drupal/core-*" --with-all-dependencies

```

(You then need to update the patches, but that's a different issue. Also move `drupal/devel` and `kint-php/kint` to `require-dev`, this should never land on prod. Aaand you should also update your PHP and update the PHP requirement in the *composer.json* then.)

---
Tags: 9, updating, composer
---


# Question
Title: Does Drupal core include any comment spam prevention mechanism?
I have a Drupal 9 core website and I have allowed comments in it (approval needed).

The problem is that my website is flooded with spam boots posting links in pre-approved comments. This causes burden of filtering human-legitimate comments. I also desire not to install any modules and work only from the core.

Does Drupal core include any comment spam prevention mechanism?

I don't recall ever finding a core module which does it in the module's list. I went through this Google search query (`does drupal include any comment spam prevention mechanism?`) but I didn't find anything specific to comments and especially anything core-based.

# Answer
> 1 votes
No, it does not. Use contributed modules.

---
Tags: 9, comments
---


# Question
Title: Why is my template suggestion not called (the return)?
I have seen many questions like this ones and I have tried all the solutions...

Here is my setup:

1-I have a custom entity with the id defined as follow:

```
 * @ContentEntityType(
 *   id = "bank_operation",
 *   label = @Translation("Bank Operation"),

```

2- My `hook_theme()`:

```
function bank_operation_theme() {
  return [
    'bank_operation' => [
      'render element' => 'elements',
     ],
  ];
}

```

3- My `hook_theme_suggestions_HOOK()`

```
function bank_operation_theme_suggestions_bank_operation(array $variables) {
  $suggestions = [];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
  $suggestions[] = 'bank_operation__' . $sanitized_view_mode;
  return $suggestions;
}

```

4- My Route to display the entity with a "receipt" view mode

```
entity.bank_operation.receipt:
  path: '/bank_operation/receipt/{bank_operation}'
  defaults:
    _entity_view: bank_operation.receipt
    _title: 'Receipt'
  requirements:
    _entity_access: 'bank_operation.view'

```

5-My twig files:

```
modules/custom/bank_operation/templates/bank-operation.html.twig
modules/custom/bank_operation/templates/bank-operation--receipt.html.twig

```

**Result: Everything works fine :**

-`receipt` view mode is used

-suggestions seems to be correctly added:

```
<!-- THEME HOOK: 'bank_operation' -->
<!-- FILE NAME SUGGESTIONS:
   x bank-operation.html.twig
   * bank-operation--receipt.html.twig
   x bank-operation.html.twig
-->
<!-- BEGIN OUTPUT from 'modules/custom/bank_operation/templates/bank-operation.html.twig' -->

```

Except that `bank-operation--receipt.html.twig` is not called

I have tried all combinations of file name/suggestions/base hook name/... without any success

I have added all the possible twig file name in my `templates` folder (with or without dash)

I have the feeling that the problem is coming from the fact that the ID of my entity have an underscore in its name...

# Answer
> 0 votes
For anyone else landing here, a lesson I learned the hard way, and not obvious from the docs.

The beginning of the suggestion seemingly MUST match the 'THEME HOOK' plus two underscores. e.g. for the theme hook 'slick'

This should work

```
function THEMENAME_theme_suggestions_slick_alter(array &$suggestions, array $variables, $hook) {
$settings    = $element['#settings'];
if (!empty($settings['field_name'])) {
$suggestions[] = 'slick__' . strtr($settings['field_name'], '-', '_');
}

```

However

```
$suggestions[] = 'slicktheme__' . strtr($settings['field_name'], '-', '_');

```

Will not work because "slicktheme" is not the theme hook. Additionally be sure that underscores, not hyphens, are added to `$suggestions` array using the `strtr` function or it will probably not work.

---
Tags: theming
---


# Question
Title: When iterating over multi-value fields is it considered best practice to override the field template vs loop over the var in node or block template?
I have a link field that allows multiple (3 max) values on a custom block for which I'm using a twig template.

I want to iterate over the values using a for loop.

I got this to work the way I wanted in my block twig like so:

```
{% for key in content.field_footer_cta_buttons|keys %}
  {% if key matches '/^\\d+$/' %}
    <a href='{{content.field_footer_cta_buttons[key]['#url']}}'>{{ content.field_footer_cta_buttons[key]['#title'] }}</a>
  {% endif %}
{% endfor %}

```

However, according to: Iterate through multiple value content field in Twig template

You should override the field template, loop there, and then print it in a node template, or presumably, a block template depending on your situation. Is this correct? If so why?

Is it bad practice to iterate over render arrays even though they are part of the render API? I would think their structure would be constant.

Thank you.

# Answer
I solved this by adding this to my custom block twig:

```
{{ dump(content.field_footer_cta_buttons) }}

```

Which should've been like the first thing that I did.

I then saw that the structure actully looks like:

```
^ array:20 [▼
  "#theme" => "field"
  "#title" => "Footer CTA Buttons"
  "#label_display" => "hidden"
  "#view_mode" => "full"
  "#language" => "en"
  "#field_name" => "field_footer_cta_buttons"
  "#field_type" => "link"
  "#field_translatable" => false
  "#entity_type" => "block_content"
  "#bundle" => "footer_block"
  "#object" => Drupal\block_content\Entity\BlockContent {#2804 ▶}
  "#items" => Drupal\Core\Field\FieldItemList {#3601 ▶}
  "#formatter" => "link"
  "#is_multiple" => true
  "#third_party_settings" => []
  0 => array:4 [▼
    "#type" => "link"
    "#title" => "Some Link Text"
    "#options" => array:1 [▶]
    "#url" => Drupal\Core\Url {#3583 ▶}
  ]
  1 => array:4 [▼
    "#type" => "link"
    "#title" => "Some Other Link Text"
    "#options" => array:1 [▶]
    "#url" => Drupal\Core\Url {#3118 ▶}
  ]
  2 => array:4 [▼
    "#type" => "link"
    "#title" => "Yet More Link Text"
    "#options" => array:1 [▶]
    "#url" => Drupal\Core\Url {#3685 ▶}
  ]
  "#cache" => array:3 [▶]
  "#weight" => 2
]

```

To me this seems unintuitive and not conducive to iteration which I would think would be a common requirement. However, I'm betting someone way more knowledgeable than myself designed this so there's likely a very good reason for it and it's also likely that my approach is fundamentally flawed in some way.

My ultimate solution was:

```
{% for key in content.field_footer_cta_buttons|keys %}
  {% if key == 0 or key == 1 or key == 2 %}
    <a class='some-class' href='{{content.field_footer_cta_buttons[key]['#url']}}'>{{ content.field_footer_cta_buttons[key]['#title'] }}</a>
  {% endif %}
{% endfor %}

```

I do realize this assumes the link field will always have a maximum of 3 values. So, something like:

```
{% if key matches '/^\\d+$/' %}

```

might be better than:

```
{% if key == 0 or key == 1 or key == 2 %}

```

If someone posts a better answer, I'll happily accept it. Thank you.
> 0 votes

---
Tags: theming, blocks
---


# Question
Title: How to enforce a specific aspect ratio for all Media images?
With the core media **Image** type, I want to restrict uploaded images by aspect ratio.

I want to block images that have the wrong aspect ratio from being uploaded. My question is the same as this Drupal 7 question, but for Drupal 8+.

# Answer
This helpful blog post gave me an answer:

```
/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  switch ($form_id) {
    case 'media_image_add_form':
    case 'media_image_edit_form':
      $form['field_media_image']['widget'][0]['#upload_validators']['_validate_image_aspect_ratio'] = [];
      break;
  }
}

/**
 * Ensures that images have a square aspect ratio.
 *
 * @param \Drupal\file\FileInterface $file
 *   File object.
 *
 * @return array
 *   Errors array.
 */
function _validate_image_aspect_ratio(FileInterface $file) {
  $errors = [];
  $image = \Drupal::service('image.factory')->get($file->getFileUri());
  if ($image->isValid()) {
    // Add your condition validations here!
    $width = $image->getWidth();
    $height = $image->getHeight();
    $aspect_ratio = $width / $height;
    if ($aspect_ratio != 1 / 1) {
      $errors[] = t("The image should be a square!");
    }
  }
  return $errors;
}

```
> 0 votes

---
Tags: media, 10
---


# Question
Title: How to disable the first option of select form element
I created a custom module that contains a form with select element I tried to disable the first option like this, but failed to get it work

```
'#options' => $terms,
                '#empty_option' => '- Select category -',
                '#empty_value' => 'none',
                '#disabled_values' => array('none'),

```

How could this be achieved without removing the option?

# Answer
There is a contrib module that can help: Form Options Attributes

> This module adds the ability to specify attributes for individual options on Drupal Form API elements of the types: select, checkboxes, and radios.

Also Jaime Calvo's answer about writing:

```
['#options']['my disabled option'] = [];

```

Doesn't really create a disabled option, but an empty `optgroup`.
> 0 votes

# Answer
Drupal by default doesn't allow for this, so you'll need to do two things:

1. Override theme\_select()
2. Create your own callback the create the options
3. Change the way you define your select options in your form definition

Overriding theme\_select():

```
function THEME_select($variables) {
  $element = $variables['element'];
  element_set_attributes($element, array('id', 'name', 'size'));
  _form_set_class($element, array('form-select'));

  return '<select' . drupal_attributes($element['#attributes']) . '>' . custom_form_select_options($element) . '</select>';
}

```

Create your own callback the create the options:

```
function custom_form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = $value_valid && is_array($element['#value']);
  $options = '';
  foreach ($choices as $key => $choice) {
    if (is_array($choice) && !isset($choice['data'])) {
      $options .= '<optgroup label="' . check_plain($key) . '">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
    }
    elseif (is_object($choice)) {
      $options .= form_select_options($element, $choice->option);
    }
    else {
      $key = (string) $key;
      if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) {
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
      $options .= '<option value="' . check_plain($key) . '"' . $selected;
      if(is_array($choice) && isset($choice['disabled']) && $choice['disabled'])
      {
          $options .= ' disabled';
      }
      $choice = is_array($choice) && isset($choice['data']) ? $choice['data'] : $choice;
      $options .= '>' . check_plain($choice) . '</option>';
    }
  }
  return $options;
}

```

Change the way you define your select options in your form definition. Turn the option into an array with two keys:

1. data: The display value of the element
2. disabled: A boolean indicating whether to disable the element

Example:

```
$form['select'] = array(
  '#title' => t('Pick something'),
  '#type' => 'select',
  '#options' => array(
    'yes' => array('data' => t('Yes'), 'disabled' => TRUE),
    'no' => t('No'),
  ),
);

```
> 3 votes

# Answer
I prefer in this case use a single line of jQuery.

```
$("select option:first-child").attr("disabled", "true");

```

It adds the disabled attribute :

```
<option value="disabled_options" disabled="disabled">-- choice : --</option>

```

I would have preferred to do it with formApi but I think this is too much code for a basic thing.
> 0 votes

# Answer
I took Sébastien Gicquel answer and tweaked it a little, so you can define disabled options when building form.

It is for Drupal 10 but I think it is so simple that should work on other Drupal versions as well.

In JS file (I created small library `mymodule\forms` to customize forms) create code to disable certain options.

```
$(".select-options-disabled").each(function(){
  let selectNode = $(this);
  let optionsIdxs = selectNode.data('options-disabled').split(",");
  $.each(optionsIdxs, function(_i, idx){
    selectNode.find('option:nth-child(' + idx + ')').attr("disabled", "true");
  });
});

```

Then in any buildForm function when you want to create select with disabled options just add two attributes to it. Class `.select-options-disabled`, and `data-options-disabled` as comma seperated list of options to disable in given select. For example:

```
$form['my_options'] = array(
  '#type'          => 'select',
  '#options'       => $options,
  '#attributes' => [
    'class' => ['select-options-disabled'],
    'data-options-disabled' => '1,3',
  ],
);

```

Remember to add your JS file in my case it is my library

```
$form['#attached']['library'][] = 'mymodule/forms';

```

Also keep in mind that CSS nth-child selector starts from 1, not 0 therefore to disable first option in select add "1" not "0" :)

Still I wish there would be better option to do this, if there is please add comment here.
> 0 votes

# Answer
If you just set an empty array as value the option will be disabled.

```
['#options']['my disabled option'] = [];

```

this option will get the name, 'my disabled option' and will be disabled.
> -1 votes

---
Tags: 8, forms
---


# Question
Title: Loop through child elements in Twig like Element::children()
When dealing with a renderable array in PHP, I can use Element::children() to access the elements that are not `#` properties but subordinated renderable elements (form items inside a fieldset, items inside a field widget, etc). For instance, this snippet from file.module:

```
<?php
if ($element['#multiple']) {
  foreach (Element::children($element) as $name) {
    // ...
  }
}
?>

```

How can I do the same in a Twig template? If I do `{% for child in element %}`, it will include also `#type`, `#cache` etc.

# Answer
```
{% for key, child in element if key|first != '#' %}
  <div>{{ child }}</div>
{% endfor %}

```
> 25 votes

# Answer
Use the Twig Tweak module, which, among other wonderful features, has a "children" filter:

```
{% for item in content.field_name | children(true) %}
  {# loop.length, loop.revindex, loop.revindex0, and loop.last are now available #}
{% endfor %}

```
> 4 votes

# Answer
I have created a Twig filter that returns back with the children as an `ArrayIterator`.

`mymodule/mymodule.services.yml`

```
services:
  mymodule.twig_extension:
    arguments: ['@renderer']
    class: Drupal\mymodule\TwigExtension\Children
    tags:
      - { name: twig.extension }

```

`mymodule/src/TwigExtension/Children.php`

```
<?php

namespace Drupal\mymodule\TwigExtension;

class Children extends \Twig_Extension
{

  /**
   * Generates a list of all Twig filters that this extension defines.
   */
  public function getFilters()
  {
    return [
      new \Twig_SimpleFilter('children', array($this, 'children')),
    ];
  }

  /**
   * Gets a unique identifier for this Twig extension.
   */
  public function getName()
  {
    return 'mymodule.twig_extension';
  }

  /**
   * Get the children of a field (FieldItemList)
   */
  public static function Children($variable)
  {
    if (!empty($variable['#items'])
      && $variable['#items']->count() > 0
    ) {
      return $variable['#items']->getIterator();
    }

    return null;
  }

}

```

in the Twig template:

```
{% for headline in entity.field_headline|children %}
  {{ headline.get('value').getValue() }}
{% endfor %}

```
> 2 votes

# Answer
Here's a modification of https://drupal.stackexchange.com/a/236408/67965 that loops through the render children instead of the field `#items`.

The twig extension:

```
/**
 * Generates a list of all Twig filters that this extension defines.
 */
public function getFilters() {
  return [
    new \Twig_SimpleFilter('children', array($this, 'children')),
  ];
}

/**
 * Get the render children of a field
 */
public static function children($variable) {
  return array_filter(
    $variable,
    function($k) { return (is_numeric($k) || (strpos($k, '#') !== 0)); },
    ARRAY_FILTER_USE_KEY
  );
}

```

In twig, you can then pass through rendered children directly, which helps in atomic design patterns. Define an entity template, for instance:

```
{% include '@molecules/grid.html.twig' with {
   head : content.field_title,
   grid_columns: content.field_collection_items|children
} %}

```

where grid.html.twig is something like:

```
{% if head %}
<div class="slab__wrapper">
  {{ head }}
</div>
{% endif %}
<div class="grid">
  {% for col in grid_columns %}
  <div class="grid__column">
    {{ col }}
  </div>
  {% endfor %}
</div>

```

This is usually more useful than having to render a field template for `{{ content.field_collection_items }}` because the layout of the children may be controlled in the context of the parent design element.
> 1 votes

# Answer
Since Twig 2.10.0 using "if" in a "for" loop is deprecated, and you'll have to use filter.

```
{% for key, child in element if key|first != '#' %}
  <div>{{ child }}</div>
{% endfor %}

```

would become

```
{% for key, child in element|filter((child, key) => key|first != '#') %}
  <div>{{ child }}</div>
{% endfor %}

```
> 0 votes

---
Tags: 8, theming
---


# Question
Title: Replace MessageTemplate with token value and send the message
I'm having some trouble getting the tokens in my Drupal 9 message template to work properly. I have created and saved a message in my UI using the structure-\>message templates section, with a WYSIWYG text format field that contains the following:

> User \[user:account-name\] changed details. https://mysitename.com/user/\[user:uid\]

I'm concerned about the code snippet I've been using to send this message, which looks like this:

```
 $message = Message::create([
  'template' => $templateId,
  'user' => \Drupal::currentUser(),
]);

```

When I try to send the message, the email is successfully sent, but the tokens are not being replaced and the entire message text (with token placeholders untouched) is placed in the email subject.

```
public function submitForm(array &$form, FormStateInterface $form_state) {
$notifier = \Drupal::service('message_notify.sender');
$templateId = $form_state->getValue('templates');

// Load all template instances.
$templateInstances = $this->entityTypeManager->getStorage('message_template')
  ->load($templateId);

if (!$templateInstances instanceof MessageTemplateInterface) {
  $this->messenger()
    ->addError($this->t('No valid template has been selected.'));
  return;
}

$message = Message::create([
  'template' => $templateId,
  'user' => \Drupal::currentUser(),
]);

$message->save();

if ($notifier->send($message)) {
  $this->messenger()
    ->addStatus($this->t('The message has been successfully send.'));
}
else {
  // If none of the above if-conditions is met, show error notification.
  $this->messenger()
    ->addError($this->t('The message could not be send. Please contact the administrator.'));
}

```

I'm wondering how I can make this work with MessageTemplates and ensure that my code is in line with good Drupal practices.

Thank you for your help!

# Answer
> 0 votes
It turned out that tokens sounding like "current user" or "current date" worked just fine. The problem was that tokens starting from "user:...." needed an additional context of a user that wasn't provided by default.

To me the solution was to apply this patch: https://www.drupal.org/project/message/issues/2981259.

And then modify the code slightly so that it looks like this:

```
$message = Message::create([
  'template' => $templateId,
  'uid' => $entity->id(),
]);

$message->setOwner($entity);
$message->save();

```

Perhaps someone would find it useful. Cheers.

# Answer
> 0 votes
Message templates are configurable. You can add fields in them using the standsrd fields ui. Then you should be able to access the fields from code and use the tokens from the template.

---
Tags: 9, emails, tokens
---


# Question
Title: Why does loadMultiple load 1000 nodes 9.2 times faster than loading each node one by one?
I have always known that loading multiple nodes at the same time is faster than loading them one by one.

What I wasn't aware of, is the huge performance difference between the two ways of loading nodes.

I made an example to see the difference. Here's my result:

**Load 1000 nodes one by one:**

```
$start_time = microtime(TRUE);
foreach (range(1, 1000) as $nid) {
  $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
  if ($node) {
    print_r($node->id());
  }
}
$end_time = microtime(TRUE);
print_r($end_time - $start_time);

```

Execution time: **10.7266** seconds.

**Load 1000 nodes at the same time:**

```
$start_time = microtime(TRUE);
$nids = [];
foreach (range(1, 1000) as $nid) {
  $nids[] = $nid;
}

$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);

foreach ($nodes as $node) {
  if ($node) {
    print_r($node->id());
  }
}
$end_time = microtime(TRUE);
print_r($end_time - $start_time);

```

Execution time: **1.1664** second.

How can `loadMultiple` be 9.2 times faster than loading the nodes one by one?

Is it the connection between Drupal and the database? What other factors are slowing down the process when loading the nodes one by one?

I have cleared the cache after every request to avoid the loaded nodes being cached.

I used the `microtime` function to calculate the exact execution times.

# Answer
> 7 votes
It's likely that when you are loading individual nodes, you are loading the entity type manager, then for each node retrieve the storage, do a query to get the node data, and build the node. With the multiple process, some of the DB queries are likely combined, and the processing will be in a batch rather than having to run through the code multiple times.

# Answer
> 3 votes
This actually has nothing to do with the storage or entity type manager itself.

However, it is true that `loadMultiple` is faster in this case.

The slowness comes from `\Drupal\Core\Entity\Sql\SqlContentEntityStorage::getFromStorage` to be exact.

As a matter of fact, even if you do call `load`, it will call `loadMultiple`.

For the reason why loading entities individually is slower is because how the data is queried from database. In most usual scenario entity has base table, data table and possible revision tables. With node, you often have also extra fields, of which have separate table as well.

So as bare minimum on every load, you will be querying `node`, `node_field_data`, `node_revision` and `node_field_revision`.

When you load multiple entities, under the hood these tables are called only once including all entity IDs. Total of 4 queries.

When you load individually entities, under the hood these tables are also called for every entity. That's total of 4000 queries in your example.

If you have 20 extra fields for node, that would mean 44 queries vs 44000 queries.

Here's snippet you can utilize to see the difference in action.

```
<?php

$entityType = 'node';
$storage = \Drupal::entityTypeManager()->getStorage($entityType);
$ids = $storage->getQuery()->range(0, 1000)->execute();
$storage->resetCache();

$startTime = microtime(TRUE);
foreach ($ids as $id) {
  $entity = $storage->load($id);
  unset($entity);
}
$endTime = microtime(TRUE);
$runTime = $endTime - $startTime;
echo "Individual: {$runTime} seconds" . PHP_EOL;

$storage->resetCache();

$startTime = microtime(TRUE);
$entities = $storage->loadMultiple($ids);
unset($entities);
$endTime = microtime(TRUE);
$runTime = $endTime - $startTime;
echo "Multiple: {$runTime} seconds" . PHP_EOL;

```

---
Tags: entities, database, performance
---


title: create empty link

text:
# Question
Title: create empty link
I am using the following code.

```
$url = Url::fromUri('javascript:', array('external' => true));
$link = Link::fromTextAndUrl(t("#action"), $url)->toString();

```

It causes the following error:

> InvalidArgumentException: The URI 'javascript' is invalid. You must use a valid URI scheme. in Drupal\Core\Url::fromUri() (line 280 of C:\xampp\htdocs\drupal826\core\lib\Drupal\Core\Url.php).

Is this supported in Drupal 8?

I found How to create an EMPTY anchor-only link with l()? but that is for Drupal 7.

# Answer
> 4 votes
So we create url for link which should be empty.

For this we can use `::fromUserInput()` and send parameters starting with `#` or `/`.

Or create url from route `::fromRoute('<none>')` with specific token.

`$url = Url::fromUserInput('#');`

Way to create link is usual for D8:

`$link = Link::fromTextAndUrl($this->t('Some link'), $url);`

# Answer
> 0 votes
The best way I've found is this:

```
use Drupal\Core\Template\Attribute;

// A value that would inject HTML if you just used string interpolation.
$value_needing_sanitize = '"><h1>BAD</h1><a href="#" class="';

// Optionally add attributes with any HTML sanitized.
$attributes = new Attribute([
  'data-input' => $value_needing_sanitize,
]);

// Create the anchor tag.
$link = <<<HTML
  <a href="#" $attributes>
HTML . $this->t('Click here') . '</a></span>';

// Render array
$render_array = ['#markup' => $link];

```

If you don't need any attributes set to user input, then you can remove those parts and just create the anchor tag and render array.

---
Tags: 8, navigation
---


title: Hide block for specific content types?

text:
# Question
Title: Hide block for specific content types?
How can I hide a block on specific content types in Drupal 8? There's the setting for *displaying* blocks only on specified content types, but not for hiding them. Looks like the PHP filter is out in D8, so not sure how to go about this. I'm sure it's a simple fix.

# Answer
> 4 votes
You can use this module:

Block Visibility Groups

Here you can configure a visibility group which hides blocks for a specific content type.

The visibility groups not only provide more options for block visibility, they are also reusable, that means you can apply the same settings on multiple blocks.

Ctools also provides the functionality to hide blocks for content types, but there was an issue, I don't know if this is resolved.

# Answer
> 5 votes
Another way you can do this, just via code (in this example, in your theme's `.theme` file), is like so:

```
/**
 * Implements theme_preprocess_block().
 */
function themename_preprocess_block(&$variables) {
  // Remove page title block on 'article' content type.
  if ($variables['elements']['#id'] == 'themename_page_title') {
    $node = \Drupal::routeMatch()->getParameter('node');
    if ($node && $node->getType() == 'article') {
      $variables['content'] = [];
    }
  }
}

```

# Answer
> 1 votes
Using geerlingguy's code as is, you'll get a notice about `$block_id` variable being undefined. Place `$block_id = $variables['elements']['#id'];` before the `if` statement to get rid of the notice.

# Answer
> 1 votes
I tried multiple methods, including the one provided by CTools, Block Visibility Groups and Context. Context works, but it's tedious, since you have to recreate entire blocks layout for specific condition and it crashes when installed with Menu Position. So far the best solution seems to be hook\_block\_access()

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

/**
 * Implements hook_block_access().
 */
function MYMODULE_block_access(Block $block, $operation, AccountInterface $account) {

  $node = \Drupal::routeMatch()->getParameter('node');
    if ($node instanceof NodeInterface) { //checking if we're on node page
      if($node->type->entity->label() == NODE_TYPE && $block->getRegion() == REGION){
          if( $operation == 'view' && $block->getOriginalId() == BLOCK_ID){
              return AccessResult::forbiddenIf($block
              ->getRegion() == REGION)
              ->addCacheableDependency($block);
          }
      }
    }
    return AccessResult::neutral();
}

```

# Answer
> 0 votes
You can :

1. Create a custom plugin condition;
2. Place your block in a region;
3. And enable your custom condition.

Here is an example of a plugin condition :

```
<?php

namespace Drupal\my_module\Plugin\Condition;

use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a 'ContentType' condition.
 *
 * @Condition(
 *   id = "my_module_node_content_type",
 *   label = @Translation("Node content type"),
 *   context_definitions = {
 *     "node" = @ContextDefinition(
 *       "entity:node",
 *        label = @Translation("Node")
 *      )
 *   }
 * )
 *
 * @DCG prior to Drupal 8.7 the 'context_definitions' key was called 'context'.
 */
class NodeContentType extends ConditionPluginBase implements ContainerFactoryPluginInterface {

  /**
   * Creates a new ContentType instance.
   *
   * @param array $configuration
   *   The plugin configuration, i.e. an array with configuration values keyed
   *   by configuration option name. The special key 'context' may be used to
   *   initialize the defined contexts by setting it to an array of context
   *   values keyed by context names.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
   *   The date formatter.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition
    );
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return ['node_type' => NULL] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    // Add form checkbox.
    $form['node_type'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Dot not show on mini site node type'),
      '#default_value' => $this->configuration['node_type'],
    ];

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

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $this->configuration['node_type'] = $form_state->getValue('node_type');
    parent::submitConfigurationForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function summary() {
    return $this->t('Node not type: My node type');
  }

  /**
   * {@inheritdoc}
   */
  public function evaluate() {
    $node = \Drupal::routeMatch()->getParameter('node');
    if ($node && $node->getType() == 'my_node_type') {
      return FALSE;
    }

    return TRUE;
  }

}

```

---
Tags: 8, blocks
---


# Question
Title: How can I migrate URL fields to link fields?
I have run migration using ***drush migrate-upgrade*** by setting up Drupal 7 used module in Drupal 8 site and DB path etc. But one of my content type use field https://www.drupal.org/project/url but now link field is in Drupa 8 and version of this module is not available in Drupal 8.

In support I have gone thorugh https://www.drupal.org/project/url/issues/1778224 but didn't helped much.

How I can transfer data along with revisions data related to this field and create/migrate similar field in Drupal 8 content type (rest of fields got migrated in 8 from 7)?

# Answer
> 1 votes
Have to create solution based on https://www.drupal.org/docs/8/api/migrate-api/writing-migrations-for-contributed-and-custom-modules#field-migrations

```
<?php

namespace Drupal\custom_url_migrate\plugin\migrate\field;

use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;

/**
 * @MigrateField(
 * id = "url",
 * core = {7},
 * type_map = {
 * "url" = "link"
 * },
 * source_module = "url",
 * destination_module = "link"
 * )
 */
class url extends FieldPluginBase {

  /**
   * {@inheritdoc}
   */
  public function getFieldWidgetMap() {
    return [
      'url' => 'link',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldFormatterMap() {
    return [
     //'default' => 'link',
      //'url' => 'link',
      'url_external' => 'link',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function processFieldValues(MigrationInterface $migration, $field_name, $data) {
    $process = [
      'plugin' => 'sub_process',
      'source' => $field_name,
      'process' => [
        'uri' => 'value',
      ],
    ];
    $migration->setProcessOfProperty($field_name, $process);
  }

}

```

# Answer
> 0 votes
There is also now a contrib module for the URL to Link migration: https://www.drupal.org/project/migrate\_url2link

---
Tags: 8, migrations
---


# Question
Title: "No route found for GET" on custom REST resource when posting
I'm seeing really weird behavior. I've created a custom REST resource at the route `/user/login-special`.

This is a passwordless login resource, so I pass the `mail` address and then get a response that says `Login email was sent!`

However, when I POST to the `https://abc-xyz-123.us-3.platformsh.site/user/login-special?_format=json`, I get the error:

```
{
    "message": "No route found for \"GET https://www.abc-xyz-123.us-3.platformsh.site/user/login-special?_format=json\" (from \"https://abc-xyz-123.us-3.platformsh.site/user/login-special?_format=json\")"
}

```

The route definitely exists... what is going on? And why is it saying GET when I used POST?

# Answer
Read the error carefully.

You POSTed to `https://abc-xyz-123.us-3.platformsh.site/user/login-special?_format=json`, but it is returning https://**www**-- you're getting redirected.

So the solution is to POST to `https://www.abc-xyz-123.us-3.platformsh.site/user/login-special?_format=json` and everything works as expected.

This might be obvious to most people, but I wasted a lot of time debugging this today, so I'm posting it here in case anyone else makes the same mistake in the future.
> 1 votes

---
Tags: authentication-authorization, page-controller
---


# Question
Title: Custom field Widget multiple values as a Table
Hello I created a custom field type and field widget with multiple values, one entity reference and two checkboxes.

Everything's was working well, but to improve the usability of the fields, I would like to display the fields as a table, and that is when my problem begins.

I tried changing the field widget to be like a table, that visually worked to me, but now I will have problem because the keys of each field will be different than the defined in the field type, any suggestions in how to handle with this problem? If i roll back and remove the 'multiple\_route' key to create the table and set the $elements\[''\] like i orignally started the fields are saving correctly.

**FieldWidgetType.php**

```
class MultipleRouteWidgetType extends WidgetBase {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {

    $element['multiple_route'] = [
      '#type' => 'table',
      '#header' => [
        t(''),
        t('checkbox 1'),
        t('checkbox 2'),
      ],
    ];

    $route_value = NULL;
    if (isset($items[$delta]->route)) {
      $route_value = \Drupal::entityTypeManager()->getStorage('node')->load(intval($items[$delta]->route));
    }

    $element['multiple_route'][0]['route'] = $element + [
      '#title' => t('Page'),
      '#type' => 'entity_autocomplete',
      '#target_type' => 'node',
      '#required' => TRUE,
      '#empty_value' => NULL,
      '#default_value' => $route_value,
    ];

    $element['multiple_route'][0]['checkbox_1'] = [
      '#title' => t('checkbox 1'),
      '#type' => 'checkbox',
      '#empty_value' => 0,
      '#default_value' => isset($items[$delta]->checkbox_1) ? $items[$delta]->checkbox_1 : 0,
    ];

    $element['multiple_route'][0]['checkbox_2'] = [
      '#title' => t('checkbox_2.'),
      '#type' => 'checkbox',
      '#default_value' => isset($items[$delta]->checkbox_2) ? $items[$delta]->checkbox_2 : 0,
    ];

    return $element;
  }

}

```

Visually this is my goal and worked:

But now the fields will not save because the keys are different than the definied in the schema, resulting in this error:

> The website encountered an unexpected error. Please try again later. Error: Cannot create references to/from string offsets in Drupal\Component\Utility\NestedArray::setValue() (line 155 of core/lib/Drupal/Component/Utility/NestedArray.php). Drupal\Component\Utility\NestedArray::setValue(Array, Array, NULL) (Line: 1254)

Field Type.php property definitions and schema:

```
class RouteFieldType extends FieldItemBase {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['route'] = DataDefinition::create('integer')
      ->setLabel(new TranslatableMarkup('Page'))
      ->addConstraint('RouteConstraint', ['fieldName' => $field_definition])
      ->setRequired(TRUE);

    $properties['checkbox_1'] = DataDefinition::create('boolean')
      ->setLabel(new TranslatableMarkup('checkbox_1'));

    $properties['checkbox_2'] = DataDefinition::create('boolean')
      ->setLabel(new TranslatableMarkup('checkbox_2'));

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = [
      'columns' => [
        'route' => [
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ],
        'checkbox_1' => [
          'type' => 'int',
          'size' => 'tiny',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'default' => 0,
        ],
        'checkbox_2' => [
          'type' => 'int',
          'size' => 'tiny',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'default' => 0,
        ],
      ],
    ];

    return $schema;
  }

```

# Answer
> 1 votes
Your widget needs to massage the form values. Like this core widget which restructures the array by removing one level:

*/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php*

```
  /**
   * {@inheritdoc}
   */
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
    // Since file upload widget now supports uploads of more than one file at a
    // time it always returns an array of fids. We have to translate this to a
    // single fid, as field expects single value.
    $new_values = [];
    foreach ($values as &$value) {
      foreach ($value['fids'] as $fid) {
        $new_value = $value;
        $new_value['target_id'] = $fid;
        unset($new_value['fids']);
        $new_values[] = $new_value;
      }
    }

    return $new_values;
  }

```

# Answer
> 0 votes
Thank you.

Before that to quick solve the issue and because my task was late, i emulate the form table with a markup on the suffix and prefix on the widget fields and returned the keys to the root of structure of $element:

```
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $prefix = '<table class="responsive-enabled">
      <thead>
        <tr>
          <th></th>
          <th>' . t('Checbox 1') . '</th>
          <th>' . t('Checbox 2') . '</th>
        </tr>
      </thead>
      <tbody>
        <tr class="odd">
          <td>
    ';

    $route_value = NULL;
    if (isset($items[$delta]->route)) {
      $route_value = \Drupal::entityTypeManager()->getStorage('node')->load(intval($items[$delta]->route));
    }

    $element['route'] = $element + [
      '#prefix' => $prefix,
      '#suffix' => '</td>',
      '#title' => t('Page'),
      '#type' => 'entity_autocomplete',
      '#target_type' => 'node',
      '#required' => TRUE,
      '#empty_value' => NULL,
      '#default_value' => $route_value,
    ];

    $element['checkbox_1'] = [
      '#prefix' => '<td>',
      '#suffix' => '</td>',
      '#title' => t('c'),
      '#title_display' => 'invisible',
      '#type' => 'checkbox',
      '#empty_value' => 0,
      '#default_value' => isset($items[$delta]->checkbox_1) ? $items[$delta]->checkbox_1 : 0,
    ];

    $element['checkbox_2'] = [
      '#prefix' => '<td>',
      '#suffix' => '</td></tr></tbody></table>',
      '#title' => t('c.'),
      '#title_display' => 'invisible',
      '#type' => 'checkbox',
      '#empty_value' => 0,
      '#default_value' => isset($items[$delta]->checkbox_2) ? $items[$delta]->checkbox_2 : 0,
    ];

    return $element;
  }

```

---
Tags: entities, forms, 10
---


title: How to log incoming POST requests

text:
# Question
Title: How to log incoming POST requests
We are seeing a lot of POST requests by bots to /register pages or pages with forms. This is likely an attempt to make an SQL injection.

What's the best way to log the full POST request and see the payload data?

Cheers

# Answer
You may use HTTP Request Logger module.
> 2 votes

# Answer
If you are Nginx, below answer is the easiest way to log POST requests including data being carried.

https://stackoverflow.com/questions/4939382/logging-post-data-from-request-body
> 0 votes

---
Tags: 7
---


# Question
Title: How to make it so theme function isn't used when editing a node with layout builder
I have the following two functions in my .theme file that I'm using to gleam various bits of information from both the path alias and internal URL of the page being viewed. They are:

```
function rtv_preprocess_block(&$variables) {
  $internal_path = \Drupal::service('path.current')->getPath();
  $current_path = \Drupal::service('path_alias.manager')->getAliasByPath($internal_path);
  $variables['current_path'] = $current_path;
  $variables['internal_path'] = $internal_path;
  $int_exp = explode("/", $current_path);
  $alias = \Drupal::service('path_alias.manager')->getPathByAlias($int_exp[1]);
  $is_layout = false;
  if (isset($int_exp[3]) and $int_exp[3] == 'layout') {
    $is_layout = true;
  }
  if (isset($internal_path) && !$is_layout) {
    $params = Url::fromUri("internal:/" . $alias)->getRouteParameters();
    $entity_type = key($params);
    if (isset($entity_type)) {
      $node = \Drupal::entityTypeManager()->getStorage($entity_type)->load($params[$entity_type]);
      $variables['section_nid'] = $node->nid->value;
      $variables['is_site_section'] = false;
      if ($node->type->target_id == 'sections') {
        $variables['is_site_section'] = true;
        $variables['current_section'] = $node->field_section_machine_name->value;
        $variables['current_section_title'] = $node->title->value;
      }
    }
  }
}

function rtv_preprocess_node(&$variables) {
  $internal_path = \Drupal::service('path.current')->getPath();
  $current_path = \Drupal::service('path_alias.manager')->getAliasByPath($internal_path);
  $variables['current_path'] = $current_path;
  $variables['internal_path'] = $internal_path;
  $int_exp = explode("/", $current_path);
  $alias = \Drupal::service('path_alias.manager')->getPathByAlias($int_exp[1]);
  $is_layout = false;
  if (isset($int_exp[3]) and $int_exp[3] == 'layout') {
    $is_layout = true;
  }
  if (isset($internal_path) && !$is_layout) {
    $params = Url::fromUri("internal:/" . $alias)->getRouteParameters();
    $entity_type = key($params);

    if (isset($entity_type)) {
      $node = \Drupal::entityTypeManager()->getStorage($entity_type)->load($params[$entity_type]);
      $variables['section_nid'] = $node->nid->value;
      $variables['is_site_section'] = false;
      if ($node->type->target_id == 'sections') {
        $variables['is_site_section'] = true;
        $variables['current_section'] = $node->field_section_machine_name->value;
        $variables['current_section_title'] = $node->title->value;
      }
    }
  }
}

```

While they work where I need them to, I'm having issues with errors related to the pages where the function is being run on a path that doesn't have an internal path... generally giving me an error that says "External URLs do not have internal route parameters."

You'll see in my above functions that I've been trying to set up various conditionals to try to avoid this error. I thought I had done so, but then I discovered that I was getting ajax errors when trying to use the Layout Builder that broke its functionality. I thought checking if the path had a third segment with "layout" in it that I could tell it not to fire the functions on the layout edit page, but it still is. Any thoughts on where my mistake is?

In addition, I'm open to recommendations on making these functions more streamline or perhaps merging them into a single function that works with both blocks and nodes.

More information: The site is broken up into sections and each section is defined by a node from a content type called "sections". The path aliases are setup so pages are domain.com/sectionname/pagename. These functions basically are creating some variables to be used in my twig templates so I can theme these different sections differently.

Here's the full ajax error:

```
Object { message: "\nAn AJAX HTTP error occurred.\nHTTP Result Code: 500\nDebugging information follows.\nPath: /layout_builder/update/block/overrides/node.3205/2/second/ce22e4bb-e72c-406e-a430-5b4a36267126?destination=/node/3205/layout&_wrapper_format=drupal_dialog.off_canvas&ajax_form=1\nStatusText: 500 Service unavailable (with message)\nResponseText: The website encountered an unexpected error. Please try again later.UnexpectedValueException: External URLs do not have internal route parameters. in Drupal\\Core\\Url->getRouteParameters() (line 583 of core/lib/Drupal/Core/Url.php). rtv_preprocess_block(Array, 'block', Array)\ncall_user_func_array('rtv_preprocess_block', Array) (Line: 287)\nDrupal\\Core\\Theme\\ThemeManager->render('block', Array) (Line: 433)\nDrupal\\Core\\Render\\Renderer->doRender(Array) (Line: 446)\nDrupal\\Core\\Render\\Renderer->doRender(Array, ) (Line: 204)\nDrupal\\Core\\Render\\Renderer->render(Array) (Line: 479)\nDrupal\\Core\\Template\\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 52)\n__TwigTemplate_3657feff99df006eb4f124e3a699561a084dfd8bb48a625bef56f6fce9484a7e->doDisplay(Array, Array) (Line: 405)\nTwig\\Template->displayWithErrorHandling(Array, Array) (Line: 378)\nTwig\\Template->display(Array) (Line: 390)\nTwig\\Template->render(Array) (Line: 55)\ntwig_render_template('core/themes/stable9/layouts/layout_discovery/onecol/layout--onecol.html.twig', Array) (Line: 384)\nDrupal\\Core\\Theme\\ThemeManager->render('layout__onecol', Array) (Line: 433)\nDrupal\\Core\\Render\\Renderer->doRender(Array) (Line: 446)\nDrupal\\Core\\Render\\Renderer->doRender(Array) (Line: 446)\nDrupal\\Core\\Render\\Renderer->doRender(Array) (Line: 446)\nDrupal\\Core\\Render\\Renderer->doRender(Array, 1) (Line: 204)\nDrupal\\Core\\Render\\Renderer->render(Array, 1) (Line: 148)\nDrupal\\Core\\Render\\Renderer->Drupal\\Core\\Render\\{closure}() (Line: 580)\nDrupal\\Core\\Render\\Renderer->executeInRenderContext(Object, Object) (Line: 149)\nDrupal\\Core\\Render\\Renderer->renderRoot(Array) (Line: 36)\nDrupal\\Core\\Ajax\\InsertCommand->getRenderedContent() (Line: 31)\nDrupal\\Core\\Ajax\\ReplaceCommand->render() (Line: 43)\nDrupal\\Core\\Ajax\\AjaxResponse->addCommand(Object) (Line: 47)\nDrupal\\layout_builder\\Form\\ConfigureBlockFormBase->rebuildLayout(Object) (Line: 26)\nDrupal\\layout_builder\\Form\\ConfigureBlockFormBase->rebuildAndClose(Object) (Line: 242)\nDrupal\\layout_builder\\Form\\ConfigureBlockFormBase->successfulAjaxSubmit(Array, Object) (Line: 39)\nDrupal\\layout_builder\\Form\\ConfigureBlockFormBase->ajaxSubmit(Array, Object, Object)\ncall_user_func_array(Array, Array) (Line: 69)\nDrupal\\Core\\Form\\FormAjaxResponseBuilder->buildResponse(Object, Array, Object, Array) (Line: 109)\nDrupal\\Core\\Form\\EventSubscriber\\FormAjaxSubscriber->onException(Object, 'kernel.exception', Object)\ncall_user_func(Array, Object, 'kernel.exception', Object) (Line: 142)\nDrupal\\Component\\EventDispatcher\\ContainerAwarForeEventDispatcher->dispatch(Object, 'kernel.exception') (Line: 229)\nSymfony\\Component\\HttpKernel\\HttpKernel->handleThrowable(Object, Object, 1) (Line: 92)\nSymfony\\Component\\HttpKernel\\HttpKernel->handle(Object, 1, 1) (Line: 58)\nDrupal\\Core\\StackMiddleware\\Session->handle(Object, 1, 1) (Line: 48)\nDrupal\\Core\\StackMiddleware\\KernelPreHandle->handle(Object, 1, 1) (Line: 106)\nDrupal\\page_cache\\StackMiddleware\\PageCache->pass(Object, 1, 1) (Line: 85)\nDrupal\\page_cache\\StackMiddleware\\PageCache->handle(Object, 1, 1) (Line: 48)\nDrupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)\nDrupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)\nStack\\StackedHttpKernel->handle(Object, 1, 1) (Line: 718)\nDrupal\\Core\\DrupalKernel->handle(Object) (Line: 19)\n", name: "AjaxError", stack: "@http://XXXXX.docksal/core/misc/ajax.js?v=9.5.5:92:32\n@http://XXXXX.docksal/core/misc/ajax.js?v=9.5.5:642:3\n" }
ajax.js:92:32

```

Thank you.

# Answer
> 1 votes
More general for the block, if you want to make sure the theme function is only used when viewing the node and not in other node routes like editing, then check the route name first:

```
function mytheme_preprocess_block(&$variables) {
  $route_match = \Drupal::routeMatch();
  $variables['#cache']['contexts'][] = 'route';
  if ($route_match->getRouteName() == 'entity.node.canonical') {
    $node = $route_match->getParameter('node');
    ...

```

More specific for the layout builder in node preprocess:

```
function mytheme_preprocess_node(&$variables) {
  $route_name = \Drupal::routeMatch()->getRouteName();
  $variables['#cache']['contexts'][] = 'route.name.is_layout_builder_ui';
  if ($route_name && !str_starts_with($route_name, 'layout_builder.')) {
    $node = $variables['node'];
    ...

```

I've also added the necessary cache data.

---
Tags: theming, nodes, blocks, 9, path-aliases
---


title: SQL injection POST attacks

text:
# Question
Title: SQL injection POST attacks
Just found hundreds of POST requests to user registration pages on Drupal site. No users are being registered but these requests are still getting 200 status from Drupal.

Are these requests doing any damage? Any preventive measures I can undertake other than blocking them through Firewall.

Post Data/Request Body:

```
"{\x22name\x22: \x22oXfxOznTkE\x22, \x22mail\x22: \x22johnamico@aol.com\x22, \x22form_build_id\x22: \x22form-FiEoO2JUzWA2PQIq0ryxRHqddnXXtEn0xoV6k4xvhrk\x22, \x22form_id\x22: \x22user_register_form\x22, \x22email\x22: \x22johnamico@aol.com\x22}"

```

Complete Request:

```
     213.155.160.239 - - [14/Apr/2023:03:27:02 +0000] "POST /user/register HTTP/1.1" 200 7167 "-"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36"
     "-" "{\x22name\x22: \x22oXfxOznTkE\x22, \x22mail\x22: \x22johnamico@aol.com\x22, \x22form_build_id\x22: \x22form-FiEoO2JUzWA2PQIq0ryxRHqddnXXtEn0xoV6k4xvhrk\x22, \x22form_id\x22: \x22user_register_form\x22,
 \x22email\x22: \x22johnamico@aol.com\x22}"

```

# Answer
They do not do any damage because Drupal core sanitizes user input when querying the database.

How that is done from a programmer perspective is explained in Writing secure code for Drupal / Use the database abstraction layer to avoid SQL injection attacks, where it is explained to never concatenate data directly into SQL queries, but use placeholders, as in the example code shown in that page.

```
\Database::getConnection()->query('SELECT foo FROM {table} t WHERE t.name = :name', [':name' => $_GET['user']]);

```

Using code similar to the following, the site could be subject to SQL injection.

```
\Database::getConnection()->query('SELECT foo FROM {table} t WHERE t.name = '. $_GET['user']);

```

If `$_GET['user']` contains `"" or "1" = "1"`, the resulting query would return any row in that table; if then the data would be shown on a page, the user would see all the table content.
> 0 votes

---
Tags: 7, security
---


# Question
Title: How to get the label values for list fields in Twig?
I'm trying to fetch the label of the list and display it on the twig template.

Using `node.field_list.value` outputs the key value. I tried using `node.field_list.label` but it returns empty. I tried using `node.field_list.title`, `node.field_list.0.label` or `node.field_list[0]['#markup']` but it still returns empty on the twig template

# Answer
There are a few ways:

It calls List allowed values and it stores in the field configuration.

When field cardinality is 1

```
<div>
  {% set list_value = node.filter_list.value %}
  {{ node.filter_list.getSetting('allowed_values')[list_value] }}
</div>

```

When field cardinality is more than 1

```
<div>
  {% set allowed_values = node.filter_list.getSetting('allowed_values') %}
  {% set list_values = node.filter_list.getValue() %}
  {% for list_value in list_values %}
    <div>
      {{ allowed_values[list_value['value']] }}
    </div>
  {% endfor %}
</div>

```

In case when your template is node.html.twig you can render it based on field formatter in your view mode.

```
<div>
  {{ content.filter_list }}
</div>

```
> 6 votes

# Answer
Here is how I've been able to display the label of a Single Choice Text List inside a custom Entity referenced on a node with an EntityReference field :

```
{% for entity_ref_item in node.field_my_entity_ref_field %}
  {% set my_choice_key = entity_ref_item.entity.field_my_list_field.value %}
  {% set my_choice_label = entity_ref_item.entity.field_my_list_field.getSettings('allowed_values')['allowed_values'][my_choice_key] %}
  <div>{{ my_choice_label }}</div>
{% endfor %}

```

I believe that Alex Kuzava's code might have been missing one array \['allowed\_values'\]

```
{{ node.filter_list.getSetting('allowed_values')[list_value] }}

```

might need to be

```
{{ node.filter_list.getSetting('allowed_values')['allowed_values'][list_value] }}

```

instead. To be sure, you can use devel kint()

```
{{ kint(node.filter_list.getSetting('allowed_values'))}}

```

That's how I found out there was one more array level before getting to the label. But maybe my situation was different because I am accessing a custom entity...
> 4 votes

# Answer
You can use the module Twig Field Value to help with this task. Once installed the code:

```
{{ content.field_list|field_label }}

```

should display the label. The way the label is stored and accessed not affected by the field's cardinality.
> 2 votes

# Answer
I'm using Drupal9 and did the same as @alex\_kuzava. But got a problem with the key, seems that it was an array or something. So this is my answer:

```
{% set list_key = node.field_location_tag[0].value %}
{% set list_value = node.field_location_tag.getSetting('allowed_values')[list_key] %}
{{ list_value }}

```

Notice the \[0\] before field\_location\_tag. I don't really know whats happening here but my first clue was that list\_key wasn't a string. So I try to apply the |string filter and Drupal told me that I was trying to do an array to string transformation.

I guess is something to do that the content I'm getting is from a lists of nodes inside a paragraphs that are loaded using \Drupal\node\Entity\Node::loadMultiple($nids);

Or maybe that I'm using the language module. Somehow the value is converted to an array first.
> 0 votes

# Answer
Adding a PHP code solution since Search Engines are leading here

```
private function getListTypeValue($node, $fieldName) {
  $listField = $node->get($fieldName);
  return $listField->getSettings()['allowed_values'][$listField->value] ?? '';
}

```
> 0 votes

---
Tags: theming
---


# Question
Title: Why is this if statement not working correctly in Webform computed twig?
I'm using this in a computed\_twig element and it's fully working when I test it in this fiddle but only works correctly for value "0" while gives "high" for everything else (e.g. 0.1, 0.4, 0.5, etc) when in my webform!
q# are values of radio input type elements. I tried several variations all to no avail! Thanks

Edit: updated my question and added a minimal sample of my webform. I didn't post this at the twig stack because it's working on fiddle and only when in my Drupal Webform it's malfunctioning. I tried with and without the number\_format filter kindly suggested by @cilefen but still no change in the result. I'd exhausted my search online and read and saw examples on official Twig and other pages and still couldn't figure out what I'm doing wrong! I don't know much about twig apart from what I learn on the go to meet my special need at the time.
Note: I changed "TsC" in my previous post version to TCount to be more meaningful. Also, it's on a shared host with PHP 7.4 still.
Thanks to everyone who commented so far and I wish that whoever downvoted me straight away after posting explained why so I could learn for future posts.

```
qp1:
  '#type': wizard_page
  '#title': 'Part I'
  '#open': true
  ps1:
    '#type': details
    '#title': title...
    '#required': true
    '#attributes':
      class:
        - qp1_1
    q1:
      '#type': radios
      '#title': q1.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q2:
      '#type': radios
      '#title': q2....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q3:
      '#type': radios
      '#title': q3.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q4:
      '#type': radios
      '#title': q4.......
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q5:
      '#type': radios
      '#title': q5........
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q6:
      '#type': radios
      '#title': q6.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q7:
      '#type': radios
      '#title': q7....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q8:
      '#type': radios
      '#title': q8.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q9:
      '#type': radios
      '#title': q9.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q10:
      '#type': radios
      '#title': q10.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q11:
      '#type': radios
      '#title': q11....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q12:
      '#type': radios
      '#title': q12.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q13:
      '#type': radios
      '#title': q13.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q14:
      '#type': radios
      '#title': q14.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
      '#required': true
    q15:
      '#type': radios
      '#title': q15......
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    q16:
      '#type': radios
      '#title': q16.....
      '#options':
        - 'option 1 with value 0'
        - 'option 2 with value 1'
        - 'option 3 with value 2'
        - 'option 4 with value 3'
      '#required': true
    qs_count:
      '#type': computed_twig
      '#title': 'Total Count'
      '#wrapper_attributes':
        class:
          - score_qp_1
      '#template': 'Total Count: {% set TCount = (data.q1 + data.q2 +  data.q3 +  data.q4 +  data.q5 +  data.q6 +  data.q7 +  data.q8 +  data.q9 +  data.q10 +  data.q11 +  data.q12 +  data.q13 +  data.q14 +  data.q15 +  data.q16)|number_format(0) %} {{ TCount }} <br /> Ratio: {{ TCount / 16 }} <br /> {% if TCount == 0 %} Great {% elseif TCount > 0 and TCount < 0.5 %} Low {% elseif TCount >= 0.5 and TCount < 0.7 %} Moderate {% elseif TCount >= 0.7 %} High {% endif %}'
      '#ajax': true

```

# Answer
> 0 votes
I am going to answer myself and it seems the culprit was I was setting the conditionals based on TCount while it should be on the ratio (TCount / 16) instead. My bad! Sometimes the culprit would be too silly to miss (and ? notice) and right in front of us but we fail to see despite the many revisions and even different people. Thanks so much everyone. My template now is:

```
'Total Count: {% set TCount = (data.q1 + data.q2 +  data.q3 +  data.q4 +  data.q5 +  data.q6 +  data.q7 +  data.q8 +  data.q9 +  data.q10 +  data.q11 +  data.q12 +  data.q13 +  data.q14 +  data.q15 +  data.q16)|number_format %} {{ TCount }} <br /> {% set tRatio = TCount / 16 %} Ratio: {{ tRatio }} <br /> {% if tRatio >= 0.7 %} high {% elseif tRatio > 0 and tRatio <= 0.5 %} low {% elseif tRatio > 0.5 %} moderate {% else %} great {% endif %}'

```

---
Tags: webforms
---


title: Can services be autowired?

text:
# Question
Title: Can services be autowired?
In drupal 8, each time we used to declare a class as a service by adding it to \[module name\].services.yml and then injecting it in the constructor of a different class. However, according to this answer https://stackoverflow.com/questions/44809739/is-there-a-way-to-inject-entitymanager-into-a-service , in newer symfony versions, we can inject a service without defining the class in services.yml. does it apply to drupal 9 and 10 since they use newer versions of symfony?

# Answer
Having a bit more time today, so I'll elaborate on my comment to your question:

Autowiring would've been possible with the Symfony version used by Drupal 8 already. However, Drupal is using a plethora of services and autowiring wasn't highest priority when moving the architecture from Drupal 7 to 8.

Therefore, support for Symfony autowiring wasn't added to Drupal before version 9.3. See this change record for more details:

https://www.drupal.org/node/3218156

Still, Drupal Core services couldn't be autowired by that time, due to services in Drupal are using string identifiers in their service declarations rather than fully qualified class/interface names.

Implementing a class referencing interfaces or classes of such services as method parameter type wouldn't allow Symfony to identify the intended Core service to inject when autowring.

So you still had to define the Core service parameters, but could use autowiring for your own services, if you defined them with class or interface alias.

This has been changed as of Drupal 10.1, where the interfaces and class namespaces have been added to the core services definitions. See this change record as reference:

https://www.drupal.org/project/drupal/issues/3049525

For 3rd party module services and even some modules provided by Drupal Core, this may or may not have been done yet. Before using them in an autowiring scenario, you'll have to check those manually.

To sum it up: Symfony autowiring **is possible** in Drupal since version **9.3** when using services as arguments that already define their PHP class or interface aliases in their service definitions. Core service parameters had to be defined manually, because they didn't define their aliases. As of version **10.1** you can autowire including Core service parameters, but still should check for 3rd party module services.

Side note:

I have to admit, that I never was nor am a huge fan of autowiring and still prefer defining the arguments of all my classes and services manually.

It may be a little bit more work, but it allows me to find dependencies at a glance just by opening my modules' `*.services.yml` file (or in very rare cases the modules' service provider class).
> 8 votes

---
Tags: 9
---


# Question
Title: How can I render comment pager on the top and the bottom of the comments portion of the page
When comments are numerous on a node and you have a high comments per page setting, you have to scroll a lot to get to the pager. My users have requested pager at top as well as bottom of **the comments list** (not the top of the page) on a default node page.

To clarify

**Current page layout**

* Title
* Content
* Comments
* Pager
* New comment form

**The desired result is**

* Title
* Content
* Pager
* Comments
* Pager
* New comment form

I've done this in the past in Drupal 7, but I can't figure out what template to use in Drupal 9.

I tried this code in field--comment.html.twig, without success.

```
{% if pager %}
  {{ pager }}
{% endif %}

```

None of the other templates in my theme seem to render the pager either, but has to be somewhere. I have Twig debugging on which provided some clues. I have checked page.html.twig, node.html.twig, and comment.html.twig as well as the field template. None of them seem to render the pager.

I feel like I'm missing something obvious, but obvious or not, I'm stumped.

# Answer
The pager can be duplicated in the field--comment.html.twig if you correctly identify the pager (which I did not in the question).

```
{% if comments.pager %}
 {{ comments.pager }}
{% endif %}

```

Tested in my custom theme and also in Olivero.
> 1 votes

---
Tags: theming, 9, paging
---


# Question
Title: Unable to update node entity fields using custom form
There is a certain content type, in which there is a field to enter a node or taxonomy. I want to create a new node for that content type using a custom form and register the node or taxonomy in the entity field.

```
$form['client_company'] = [
  '#type' => 'entity_autocomplete',
  '#target_type' => 'taxonomy_term',
  '#tags' => TRUE,
  '#selection_settings' => array(
    'target_bundles' => array('vid', 'client'),
  ),
  '#weight' => '0',
];

$form['client_human'] = [
  '#type' => 'entity_autocomplete',
  '#target_type' => 'node',
  '#tags' => TRUE,
  '#selection_settings' => array(
    'target_bundles' => array('budle', 'client_human'),
  ),
  '#weight' => '0',
];
$form['url'] = [
  '#type' => 'url',
];

$node->field_project_refercompany->value = $form_state->getValue('client_company');
$node->field_project_referclienthuman->value = $form_state->getValue('client_human');
$node->field_project_url->value = $form_state->getValue('url');
$node->save();

```

The $node, field\_refercompany, field\_project\_referclienthuman, etc. are nodes and their field names, no error there. However, even though I can create a new node, the above fields are blank and nothing is entered.

# Answer
Self Answer

```
$node->field_project_refercompany->target_id = $form_state->getValue('client_company')[0]['target_id'];
$node->field_project_referclienthuman->target_id = $form_state->getValue('client_human')[0]['target_id'];
$node->field_project_url->uri = Url::fromUri($form_state->getValue('url'))->toString();

```
> 0 votes

---
Tags: nodes, forms, taxonomy-terms, 9
---


# Question
Title: /search-results page returns 404 if no query parameter is present
I have inherited a Drupal site with a node that uses /search-results as path alias.

The page works fine if I use https://example.com/search-results?keys=test or https://example.com/search-results?keys, but it returns a 404 error if I use https://example.com/search-results.

Any thoughts on where to begin?

# Answer
> 0 votes
How do you process the parameter? Is there a module to look at it? or you do it in your `.theme` hooks?

In any case you wanna try to set a default respose for the function you are using to process that `key` parameter.

---
Tags: search
---


# Question
Title: Is it possible to send an argument to a title callback?
I am trying to figure out how to set page titles on various pages. I have seen a lot of posts suggesting preprocess functions. These are not correctly setting the route's page title but only setting the value for the html template.

Another solution is closer in that it suggests using an EventSubscriber triggering off the KernelEvents::REQUEST event. This is the correct approach. Some routes have a default title and can easily be set using:

```
$route->setDefault('_title', $title);

```

while others use a title callback. This callback can be overridden using:

```
$route->setDefault('_title_callback', 'set_page_title_callback');

```

but I can't figure out how to pass an argument ($title) to this callback.

# Answer
Controller and title methods are often in the same controller class, but they don't have to. They can be in different classes, which also can be a service class, which is preferable, if you have a lot of dependencies and the class is shared by multiple routes.

In a RouteSubscriber:

```
$route->setDefault('_title_callback', 'my_service:my_title_callback');

```

See Override specific routing from a core module

When you set a title callback it always has priority, so this works in both cases, no matter if the title was set before statically or dynamically.

The title callback receives the same arguments as the main controller. The title callback has then to calculate and return the dynamic title from these arguments and the context, like the request, which you can pull from the callback's arguments as well. See How do I pass parameters from a form redirect to a controller?
> 1 votes

---
Tags: 9, event-subscribers
---


# Question
Title: States on input type button broken since core update
I've updated my Drupal project from 9.5.3 to 9.5.7, but the submission button's `#states` property stopped working.

```
$form['actions']['submit'] = [
  '#type' => 'submit',
  '#value' => 'Accéder au paiement',
  '#enabled' => FALSE,
];

$form['actions']['submit']['#states'] = [
  'enabled' => [
    [
      ':input[name="name_1"]' => ['empty' => FALSE],
      ':input[name="name_2"]' => ['empty' => FALSE],
      ':input[name="name_3"]' => ['empty' => FALSE],
      [
        ':input[name="name_4"]' => ['!value' => ''],
      ],
    ],
  ],
];

```

I rolled back to 9.5.3 and upgrade to each release to find out when it stopped working.

Turns out it's from the 9.5.6 release. On the 9.5.7 release, the button is always enabled, but changing my other inputs toggle the class `form-disabled` and the disable attribute is not triggered.

How can I fix it?

# Answer
> 1 votes
Find a small workaround, you need to change the HTML structure of your button/input submit (done on 9.5.7)

Remove the .js-form-submit (example here with twig)

```
<input{{ attributes.removeClass('js-form-submit') }} />{{ children }}

```

Add .js-form-item around your input

```
$form['actions']['submit'] = [
  '#type' => 'submit',
  '#value' => t('Validate'),
  '#enabled' => FALSE,
  '#prefix' => '<div class="js-form-item">',
  '#suffix' => '</div>',
];

$form['actions']['submit']['#states'] = [
  'enabled' => [
    [
      ':input[name="other_input"]' => ['empty' => FALSE],
    ],
  ],
];

```

Problably not the cleanest, but for now it's working

---
Tags: forms, 9, updating
---


# Question
Title: How do I set a plain text field value to be empty?
```
<?php
function MY_MODULE_node_update(NodeInterface $node)
{
    if ($node instanceof \Drupal\node\NodeInterface) {

        $nid = $node->id();
        $nodeType = $node->getType();

        $vid = \Drupal::entityTypeManager()
                ->getStorage('node')
                ->getLatestRevisionId($nid);

        $comments = $node->get('field_workflow_comment')->getValue();

        foreach($comments as $comment){
            $revision_log = ($comment['value']);
        }

        // $empty = array();
        // $commentEmpty = $node->set('field_workflow_comment', $empty);

        $node->field_workflow_comment = [];

        $timestamp = REQUEST_TIME;

        $connection = Database::getConnection();

        $add_revision_log = $connection->update('node_revision')
                            ->fields([
                                'revision_log' => $revision_log
                            ])
                            ->condition('nid', $nid)
                            ->condition('vid', $vid)
                            ->condition('revision_timestamp', $timestamp)
                            ->execute();
        $node->save();

    }
}

```

This does not work for me on *hook\_node\_update()*

Thanks in advance

# Answer
You can simply set the field to an empty array:

```
$node->field_name = [];
$node->save();

```
> 0 votes

---
Tags: hooks
---


# Question
Title: How do I use \Drupal methods in a class?
I was out of Drupal for a few years, and PHP a couple years, due to having several years of jobs that didn't use Drupal between two that did. And I'm noticing a lot of functions that were reliable are now gone.

The particular case I am currently looking at is `variable_get()` occurrences replaced by `\Drupal::state()->get()`.

However, I tried using it, and it's far from a drop-in replacement. In a class in a module I was working on, I tried changing `$lm = variable_get('user_import_line_max');` to `$line_max = \Drupal::state()->get('user_import_line_max');` but this complained that it can't be used inside a class (despite the fact the function could).

How do I use `\Drupal` methods in a class?

# Answer
> 8 votes
`\Drupal::state()` is a static helper function intended for use *in procedural code*. It returns the singleton "State" object (called a "service") from the service container. `\Drupal::state()->get()` is a chained method call that then calls the `get` method on the state service.

In object-oriented code the advice is to use dependency injection patterns, the most common type of which is constructor injection, to obtain service container objects. A strong argument for using dependency injection is that it facilitates unit testing your classes. The Drupal API documentation I linked to in the above paragraph explains using the service container in some detail.

As a sample you could look at core's *DbUpdateController.php*. There you can see how the State service is first injected:

And how it later is used inside the controller:

# Answer
> 4 votes
To add to Cliefen's very good answer, you can view the documentation for the `\Drupal` class here: https://api.drupal.org/api/drupal/core%21lib%21Drupal.php/class/Drupal/10

It provides a list of all functions that can be called statically off the class.

---
Tags: 9
---


# Question
Title: Image from field not properly displayed in teaser
On Drupal 10 with Olivero theme, I set image in teaser to be displayed with original size. I also made sure the image field display is enabled in teaser view.

Still, the image is shown on the left of teaser, as minuscule round image. It seems teaser display doesn't respect what is set in admin.

How to have image displayed in teaser with specified size and location?

# Answer
> 0 votes
When you set `Original size` on display mode, it's refers to the image URL. The CSS of the theme still change the image. You can try create another display or change the CSS, to achive what you want.

---
Tags: views, theming
---


title: Google API callback doesn't work

text:
# Question
Title: Google API callback doesn't work
I've created this javascript (delivery.js) that is supposed to take a pre-existing div on a webform page and append a Google Map to it, along with a few circles of differing radius.

Here is my libraries.yml, I reference the GoogleMaps global in the script url to be used as the callback.

```
delivery:
  version: 1.x
  js:
    js/delivery.js: {}
  dependencies:
  - core/jquery
  - core/drupalSettings
maps:
  js:
    'https://maps.googleapis.com/maps/api/js?key=AIzaSyCP9_LHyWKv821WTcLWZd8GcLLHIHvPPU0&callback=GoogleMaps.initMap&v=weekly':  { type: external, minified: true }

```

.module file attaching the libraries:

```
function order_form_webform_handler_form_alter(&$form, &$form_state, $form_id) {
    if($form_id == "webform_submission_order_submission_node_5_add_form" || $form_id == "webform_submission_order_submission_test_form" ){
        $form['#attached']['library'][] = 'order_form_webform_handler/delivery';
        $form['#attached']['library'][] = 'order_form_webform_handler/maps';
    }
}

```

Finally, my delivery.js file that does all the work:

```
var GoogleMaps = GoogleMaps || {};
(function ($, Drupal, GoogleMaps) {

    Drupal.behaviors.attachMap = {
      attach: function () {

        GoogleMaps.initMap = function(){
          // Create the map.
          const map = new google.maps.Map(document.getElementById("edit-map"), {
            zoom: 4,
            center: { lat: 44.24, lng: -76.57 },
            mapTypeId: "terrain",
          });

          let radii = [10000, 15000, 20000, 30000];

          for(let i = 0; i < radii.length; i++){
            const cityCircle = new google.maps.Circle({
              strokeColor: "#FF0000",
              strokeOpacity: 0.8,
              strokeWeight: 2,
              fillColor: "#FF0000",
              fillOpacity: 0.35,
              map: map,
              center: { lat: 44.24, lng: -76.57 },
              radius: radii[i],
            });
          }
        }
      }
    };

  })(jQuery, Drupal, GoogleMaps);

```

However, when I try to run this, I get this error:

> .te {message: 'GoogleMaps.initMap is not a function', stack: 'Error\n at \_.te.captureStackTrace (https://maps.PPU0&callback=GoogleMaps.initMap&v=weekly:212:276', name: 'InvalidValueError'}

Is there a better method of invoking the callback that I don't know about?

# Answer
> 0 votes
You will need to ensure your library is loaded first, by creating it as a dependency of the first library:

```
delivery:
  version: 1.x
  js:
    js/delivery.js: {}
  dependencies:
  - core/jquery
  - core/drupalSettings

maps:
  js:
    'https://maps.googleapis.com/maps/api/js?key=AIzaSyCP9_LHyWKv821WTcLWZd8GcLLHIHvPPU0&callback=GoogleMaps.initMap&v=weekly':  { type: external, minified: true }
  dependencies:
  - order_form_webform_handler/delivery

```

# Answer
> 0 votes
I've finally managed to hobble this together in a working manner for me:

**my\_module.libraries.yml:**

```
my_module:
  version: 1.x
  js:
    js/my-module.js: {}
  dependencies:
    - core/jquery
    - my_module/maps
maps:
  js:
    //maps.googleapis.com/maps/api/js?key=[APIKEY]&callback=Function.prototype&libraries=places&v=weekly: { type: external, minified: true }

```

In the **my\_module.module** file, I'm attaching the library in a hook\_form\_alter, but you may attach it in whatever function suits you...

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

```

**my-module.js:**

```
// Initialize GoogleMaps object
let GoogleMaps = google.maps || {};

(function ($, Drupal, GoogleMaps) {

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

      GoogleMaps.fillInAddress = function () {
        console.log('fillInAddress');
        /* do something */
      };

      GoogleMaps.initMap = function () {
        console.log('initMap');
        GoogleMaps.fillInAddress();
        /* do something */
      };

      GoogleMaps.initMap();
    }
  };

})(jQuery, Drupal, GoogleMaps);

```

In the libraries file, I used the Function.prototype callback as described here: https://stackoverflow.com/a/75212692/1541757

In the first line of the js file, I had to change:

```
let GoogleMaps = GoogleMaps || {};

```

To ...

```
let GoogleMaps = google.maps || {};

```

I'm unsure if this is a Google API change or what, but this is working for me so far.

# Answer
> -1 votes
Found the fix, the attach: function(....) wrapper line was affecting the visibility of the initMap function. Removing that cleared the error. Dynamically loading the script with JS would work as well, but did not get to a point where I tried testing that.

---
Tags: javascript, webforms, google-maps
---


# Question
Title: Append new paragraph item to existing node
I want to append a new paragraph item to a existing node, which has previously a paragraph item. I just want to add another paragraph item with the previous one. But my script only updates the previous added paragraph item with the latest data, not appending with the previous one.

```
$nid = \Drupal::entityQuery('node')->condition('title', $product_name)->execute();
        $nid = array_values($nid)[0];
        if(empty($nid)) {
            // this node doesn't exit

            $paragraph = Paragraph::create([
                'type' => 'product_details',
                'field_title' => $product_details_title,
                'field_description_details' => $product_details_description,
            ]);
            $paragraph->save();

            $node = Node::create([
                'type'        => 'products',
                'title'       => $product_name,
                'field_composition_api' => $product_api,
                'field_product_geography_area'  => [
                    ['target_id' => 26]
                ],
                'field_product_details'  => array(
                  array(
                    'target_id' => $paragraph->id(),
                    'target_revision_id' => $paragraph->getRevisionId(),
                  ),

                ),
                'created' => time(),
            ]);
            $node->save();
        }
        else {
            // this node already exits

            $paragraph = Paragraph::create([
                'type' => 'product_details',
                'field_title' => $product_details_title,
                'field_description_details' => $product_details_description,
            ]);
            $paragraph->save();

            $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

            $node->field_product_details = array(
                array(
                    'target_id' => $paragraph->id(),
                    'target_revision_id' => $paragraph->getRevisionId(),
                )
            );
            $node->save();
        }

```

I have already checked this . But no luck!

Please help me out of this. I just want to append new paragraph item to a existing node without affecting previously added paragraph item to the same node.

# Answer
After spending hours on this, I came to this solution. Currently the script retrieves existing paragraph items and adding them again together with the new paragraph item.

```
            $product_details = array();
            $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

            foreach ($node->field_product_details as $item) {
                $existing_product_details = $item->entity;
                $product_details[] = [
                    'target_id' => $existing_product_details->id(),
                    'target_revision_id' => $existing_product_details->getRevisionId(),
                ];
            }

            $paragraph = Paragraph::create([
                'type' => 'product_details',
                'field_title' => $product_details_title,
                'field_description_details' => $product_details_description,
            ]);
            $paragraph->save();

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

            $node->set('field_product_details',$product_details);
            $node->save();

```
> 1 votes

# Answer
You don't need to retrieve the existing paragraphs. You can treat the field itself as array and add items to it:

```
        $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

        $paragraph = Paragraph::create([
            'type' => 'product_details',
            'field_title' => $product_details_title,
            'field_description_details' => $product_details_description,
        ]);

        $node->field_product_details[] = $paragraph;
        $node->save();

```

Also, you can add unsaved paragraphs to the field. Then you don't need to save the paragraph and add the target/revision IDs.
> 0 votes

# Answer
You can try the code below

```
 paragraph = Paragraph::create([
                        'type' => 'paragraph_type',
                        'field_name' => $name,
                    ]);

    $node->field_paragraph->appendItem($paragraph);
    $paragraph->save();
    $node->save();

```

Ref to: https://www.drupal.org/project/paragraphs/issues/2707017#comment-14917158
> 0 votes

---
Tags: nodes, paragraphs
---