# Question
Title: How to execute code that uses the dependency injection container, at regular intervals?
Using Drupal 10 - I tried installing Ultimate Cron for D10, but it is in Alpha state and has deprecated methods that will not let it work.

This Cron needs to be executed only on a specific day and time. But the question is, how?

I have created a service that will be used to collect some data and send an email.

```
services:
  rmdrs.send_reminder_service:
    class: Drupal\rmdrs\Service\SendRMDRSService
    arguments: ['@database', '@plugin.manager.mail', '@language_manager']

```

The SendRMDRSService class:

```
    <?php

namespace Drupal\rmdrs\Service;

use \Drupal\Core\Language\LanguageManagerInterface;
use \Drupal\Core\Mail\MailManagerInterface;
use \Drupal\Core\Database\Connection;
use Symfony\Component\DependencyInjection\ContainerInterface;

class SendRMDRSService {
  private $db;
  private $today_timestamp;

  /**
   * The mail manager.
   *
   * @var \Drupal\Core\Mail\MailManagerInterface
   */
  protected $mailManager;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  public function __construct(Connection $connection, MailManagerInterface $mail_manager, LanguageManagerInterface $language_manager) {
    // Create and set a database connection
    $this->db = $connection;
    $this->today_timestamp = strtotime(date("m/d/Y"));
    $this->mailManager = $mail_manager;
    $this->languageManager = $language_manager;
  }

  public static function create(ContainerInterface $container): static {
    return new self(
      $container->get('database'),
      $container->get('plugin.manager.mail'),
      $container->get('language_manager')
    );
  }

  public function collectAndSend() {
      $due_reminders_data = $this->getDueReminderData();
      $reminders_forecast = $this->getRemindersForecast();

      $reminder_data = array(
        'total_reminders_due' => $due_reminders_data['total_reminders_due'],
        'reminders_due' => $due_reminders_data['reminders_due'],
        'reminder_forecast' => $reminders_forecast,
        'host' => $_SERVER['HTTP_HOST'],
      );

      // All system mails need to specify the module and template key (mirrored
      // from hook_mail()) that the message they want to send comes from.
      $module = 'rmdrs';
      $key = 'rmdrs_cron_message';

      // Specify 'to' and 'from' addresses.
      $to = 'cyberjobesx@gmail.com';
      $from = 'cyberjobes@gmail.com';

      $build = [
        '#theme' => 'rmdrs_cron_message',
        '#cron_var' => $reminder_data,
      ];

      $params['cron_email_body'] = \Drupal::service('renderer')->render($build);
      $language_code = $this->languageManager->getDefaultLanguage()->getId();
      $send_now = TRUE;
      $result = $this->mailManager->mail($module, $key, $to, $language_code, $params, $from, $send_now);
  }

  private function getDueReminderData() {
    $reminder_due_data = null;
    $reminders_due_query = $this->db->select('rmdrs_schedules', 'sched');
    $reminders_due_query->join('rmdrs_reminders', 'rem', 'rem.id = sched.reminder_id');
    $reminders_due_query->join('rmdrs_categories', 'cat', 'cat.id = rem.category_id');
    $reminders_due_query->join('rmdrs_frequencies', 'freq', 'freq.frequency_code = rem.frequency');
    $reminders_due_query->addField('sched', 'id', 'schedule_id');
    $reminders_due_query->addField('rem', 'id', 'reminder_id');
    $reminders_due_query->fields('cat', ['category'])->fields('rem', ['title',])
      ->fields('sched', ['next_reminder_date', 'next_reminder_timestamp', 'reminder_id'])
      ->fields('freq', ['frequency_text'])
      ->orderBy("sched.next_reminder_timestamp", "ASC")
      ->condition('sched.active', '1')
      ->condition('sched.completed', '1', '<>')
      ->condition('sched.next_reminder_timestamp', $this->today_timestamp, '<=');

    $reminder_due_data['total_reminders_due'] = count($reminders_due_query->execute()->fetchAll());

    if ($reminder_due_data['total_reminders_due'] != 0) {
      $reminder_due_data['reminders_due'] = $reminders_due_query->range(0, 10)->execute()->fetchAll();
    } else {
      $reminder_due_data['reminders_due'] = "No Reminders due at this time";
    }

    return $reminder_due_data;
  }

  private function getRemindersForecast() {
    $reminders_forecast_query = $this->db->select('rmdrs_schedules', 'sched');
    $reminders_forecast_query->join('rmdrs_reminders', 'rem', 'rem.id = sched.reminder_id');
    $reminders_forecast_query->join('rmdrs_categories', 'cat', 'cat.id = rem.category_id');
    $reminders_forecast_query->join('rmdrs_frequencies', 'freq', 'freq.frequency_code = rem.frequency');
    $reminders_forecast_query->addField('sched', 'id', 'schedule_id');
    $reminders_forecast_query->addField('rem', 'id', 'reminder_id');
    $reminders_forecast_query->fields('cat', ['category'])->fields('rem', ['title',])
      ->fields('sched', ['next_reminder_date', 'next_reminder_timestamp', 'reminder_id'])
      ->fields('freq', ['frequency_text'])
      ->orderBy("sched.next_reminder_timestamp", "ASC")
      ->condition('sched.active', '1')
      ->condition('sched.completed', '1', '<>')
      ->condition('sched.next_reminder_timestamp', $this->today_timestamp, '>');
    return $reminders_forecast_query->range(0, 10)->execute()->fetchAll();
  }
}

```

Since I cannot use Ultimate Cron I want to use the Cron in my linux server. How do I fire off this service via Cron?

I've been reading the doc but I'm not getting what they want me to do here. I tried this

```
rmdrs.servicetest:
  path: '/service-test'
  defaults:
    _controller: 'Drupal\rmdrs\Service\SendRMDRSService::collectAndSend'
  arguments: ['@database', '@plugin.manager.mail', '@language_manager']
  requirements:
    _permission: 'TRUE'

```

But I continue to get an error. Here is what I am getting:

ArgumentCountError: Too few arguments to function Drupal\rmdrs\Service\SendRMDRSService::\__construct(), 0 passed in /Users/jobes/Websites/int-apps/web/core/lib/Drupal/Core/DependencyInjection/ClassResolver.php on line 31 and exactly 3 expected in Drupal\rmdrs\Service\SendRMDRSService-\>\__construct() (line 28 of /Users/jobes/Websites/int-apps/web/modules/custom/rmdrs/src/Service/SendRMDRSService.php).

Can I get some guidance in this?

# Answer
To use *rmdrs.send\_reminder\_service* as controller, the route should use the following line.

```
  _controller: 'rmdrs.send_reminder_service:collectAndSend'

```

The first part of the string is the service ID as used in the .services.yml file, not the class name; that is followed by `:` (not `::`) and by the method name.
The definition of a route does not use any `arguments` line, even when the controller is implemented as service.

A service class does not need to implement `create()`, which is never called from Drupal core when the service instance is created. Drupal core reads the arguments to pass to the class constructor from the .services.yml file.

If I needed to implement a route that is called from the server's cron, I would rather do like the System module does: I would implement a controller (not a service) that calls the necessary service (*rmdrs.send\_reminder\_service*, in your case). That is similar to what the controller class associated to the *system.cron* route (`CronController`) does.

```
system.cron:
  path: '/cron/{key}'
  defaults:
    _controller: '\Drupal\system\CronController::run'
  options:
    no_cache: TRUE
  requirements:
    _access_system_cron: 'TRUE'

```

Notice that the *system.cron* route uses a custom access handler and expects a `key` parameter (which is used from that custom access handler, and should be considered a private key) to avoid the route is used in DOS/DDOS attacks. Only when `key` is equal to the value returned from `\Drupal::state()->get('system.cron_key')`, the controller will invoke the cron service (implemented from the `Cron` class).

I would follow this path only if implementing `hook_cron()` would not work for your case, for example because you want your code to be executed every 10 minutes, but you still want `hook_cron()` implementations to be invoked once every 24 hours. In most of the cases, `hook_cron()` is the way to go.

As alternative to implement a controller as you do, you could simply implement `hook_cron()` with code that calls *rmdrs.send\_reminder\_service* methods. Since you want to execute your code on every Monday, the code should first check that condition is verified (as cron tasks are suggested to run at least once per day).
> 0 votes

---
Tags: cron
---


# Question
Title: Rules Action 'Set a data value' of a DateTime with CurrentDateContext is not using UTC date
I am having a hard time finding documentation on this. I have a Date and Time field (field\_start\_time) in a Drupal 10 ECK Entity (wo\_time\_clock). I found that it is saving the date and time in ISO format without the timezone (2024-01-16T20:58:00). This field is a standard Date field with the 'Date and Time' option picked.

I have a Rule that is activated once a different Entity is flagged, that I am trying to get to create the wo\_time\_clock Entity and set this wo\_time\_clock field to the current time. The Rule works fine other that the time is 7 hours off. For the life of me, I can't figure out how to adjust it.

So far to get it to work to this point I have done the following:

I had to set up a special Date Format in Configuration \> Region and language \> Date and time formats

I set the Name to 'Rules Format' and the Format String to 'Y-m-d\TH:m:s'

In the Rules Actions, 'Add action' of 'Set a Data Value'

I used the Data selector to pick your Entity and add a period '.' to get the field to show. Example: wo\_time\_clock\_created.field\_start\_time

In the Value field I had to take the 'data selection' option and add twig brackets to it. Then within that I added the " | format\_date('rules\_format')" Like this:

```
{{@rules.current_date_context:current_date | format_date('rules_format')}}

```

This works and doesn't throw an error when the Entity is flagged. BUT, it saves the time in GMT and not Mountain Time.

If I try to format the date within it, it throws an error that the result is to long.

Example:

```
{{@rules.current_date_context:current_date | format_date('custom', 'Y-m-d\TH:m:s-0700')}}

```

or

```
{{@rules.current_date_context:current_date | format_date('custom', 'Y-m-d\TH:m:sO')}}

```

Gives a WSOD with this:

```
The website encountered an unexpected error. Try again later.

Drupal\Core\Entity\EntityStorageException: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'field_start_time_value' at row 1: INSERT INTO "wo_time_clock__field_start_time" ("entity_id", "revision_id", "bundle", "delta", "langcode", "field_start_time_value") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5); Array ( [:db_insert_placeholder_0] => 36 [:db_insert_placeholder_1] => 36 [:db_insert_placeholder_2] => entry [:db_insert_placeholder_3] => 0 [:db_insert_placeholder_4] => en [:db_insert_placeholder_5] => 2024-01-20T13:01:10-0700 ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 817 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).

Drupal\Core\Database\StatementWrapperIterator->execute() (Line: 44)
Drupal\mysql\Driver\Database\mysql\Insert->execute() (Line: 1400)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->saveToDedicatedTables() (Line: 971)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->doSaveFieldItems() (Line: 718)
Drupal\Core\Entity\ContentEntityStorageBase->doSave() (Line: 486)
Drupal\Core\Entity\EntityStorageBase->save() (Line: 806)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (Line: 352)
Drupal\Core\Entity\EntityBase->save() (Line: 201)
Drupal\rules\Context\ExecutionState->autoSave() (Line: 168)
Drupal\rules\EventSubscriber\GenericEventSubscriber->onRulesEvent()
call_user_func() (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch() (Line: 32)
rules_flag_flagging_insert()
call_user_func_array() (Line: 409)
Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}() (Line: 388)
Drupal\Core\Extension\ModuleHandler->invokeAllWith() (Line: 416)
Drupal\Core\Extension\ModuleHandler->invokeAll() (Line: 215)
Drupal\Core\Entity\EntityStorageBase->invokeHook() (Line: 900)
Drupal\Core\Entity\ContentEntityStorageBase->invokeHook() (Line: 564)
Drupal\Core\Entity\EntityStorageBase->doPostSave() (Line: 781)
Drupal\Core\Entity\ContentEntityStorageBase->doPostSave() (Line: 144)
Drupal\flag\Entity\Storage\FlaggingStorage->doPostSave() (Line: 489)
Drupal\Core\Entity\EntityStorageBase->save() (Line: 806)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (Line: 352)
Drupal\Core\Entity\EntityBase->save() (Line: 313)
Drupal\flag\FlagService->flag() (Line: 84)
Drupal\flag\Controller\ActionLinkNoJsController->flag()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 627)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 181)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 58)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 32)
Drupal\big_pipe\StackMiddleware\ContentLength->handle() (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 50)
Drupal\ban\BanMiddleware->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 36)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 51)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 704)
Drupal\Core\DrupalKernel->handle() (Line: 19)

```

Am I missing something?

My site is all set to work off of Mountain Time. Even the PHP of the hosting is set to Mountain Time. This was so much easier in Drupal 7.

# Answer
> 0 votes
Ok, I think I may have finally figured it out. The Rules CurrentDateContext.php is saving the DateTime in your current timezone and not in a UTC without applying the Mountain Time (or any other specific timezone) adjustment.

I ended up hacking the code. That code can be seen...

https://www.drupal.org/project/rules/issues/3403193#comment-15407229

I am not up on how to creating patches and/or knowing if this is the proper way to get it done. If someone sees this and wants to improve on it and create a patch for it I would be grateful!

---
Tags: rules, datetime, 10
---


# Question
Title: The username used for a failed login attempt is not reported anymore
Just completed a migration of a site from Drupal 7 to 10, and it's gone pretty smoothly after quite a bit of preparation. One thing seems different in a frustrating way: In the prior site on Drupal7, failed login attempts would be logged as eg "Login attempt failed for admin." or "Login attempt failed for \[mis-spelled name\]".

Once upgraded, it appears that failed attempts only list a username if that username exists (and there is no user called admin), otherwise these just log an ip address. Am I correct in interpreting this, and is there a simple way to replicate the old behaviour? To be clear, I find it useful to be able to display the username: my clients use this day-to-day supporting users who are getting usernames wrong.

I'll probably find a relevant hook if there's no existing solution.

# Answer
> 3 votes
Yes, it is correct to say that the username is logged only when there is an account matching that username. That is what `UserLoginForm::validateFinal()` does.

```
// Use $form_state->getUserInput() in the error message to guarantee
// that we send exactly what the user typed in. The value from
// $form_state->getValue() may have been modified by validation
// handlers that ran earlier than this one.
$user_input = $form_state->getUserInput();
$query = isset($user_input['name']) ? ['name' => $user_input['name']] : [];
$form_state->setErrorByName('name', $this->t('Unrecognized username or password. <a href=":password">Forgot your password?</a>', [':password' => Url::fromRoute('user.pass', [], ['query' => $query])->toString()]));
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
if (!empty($accounts)) {
  $this->logger('user')
    ->notice('Login attempt failed for %user.', ['%user' => $form_state->getValue('name')]);
}
else {
  // If the username entered is not a valid user,
  // only store the IP address.
  $this->logger('user')
    ->notice('Login attempt failed from %ip.', ['%ip' => $this->getRequest()->getClientIp()]);
}

```

To log what has been entered in the login form as username, you can add a validation handler that contains code similar to the following one.

```
if (!$form_state->get('uid')) {
  // The login attempt failed.
  $user_input = $form_state->getUserInput();
  $ip = Drupal::request()->getClientIp();
  \Drupal::logger('mymodule')->notice('Login attempt failed (user: "%user", IP: %ip).', ['%user' => $user_input['name'] ?? '', '%ip' => $ip]);
}

```

I used `$form_state->getUserInput()` for the same reason given in the `UserLoginForm::validateFinal()` comment: `$form_state->getUserInput()` returns exactly what has been entered in the form, while the value returned by `$form_state->getValue()` could have been modified by any validation handler.

---
Tags: authentication-authorization, 10, log-messages
---


title: Setting a cookie

text:
# Question
Title: Setting a cookie
I have a drupal 10 website. I have to set a cookie for the first time the user visits the site, a cookie with a lifetime of 1 year. When the user first visits the website, the cookie is not set, a welcome message block appears, the cookie is then set, then the next time the user visits the site, the cookie is already set so the message block doesn't appear. I am calling my block in a region using `{{elements.blockId}}` So I know that I have to check if the cookie exists in the `preprocess_region` and based on its value, i check whether i should print the block or not.

However, I don't know where do I have to set the cookie? In some `page_attachment` function?

I have read this How do I set a cookie? And I thought I should use

```
use Symfony\Component\HttpFoundation\Cookie;

$cookie = new use Cookie('cookie_name', TRUE);
$response->headers->setCookie($cookie);
return $response;

```

But I don't have a controller? Where Can i add this code to return a response?

# Answer
Since you want to show a message when the cookie has not been set, you could implement `hook_page_top()`.

```
/**
 * Implements hook_page_top().
 */
function mymodule_page_top(array &$page_top) {
  $messenger = \Drupal::messenger();
  $request = \Drupal::request();

  if (!$request->cookies->get('cookie_name', FALSE) {
    // Show a message using $messenger.
    $messenger->addStatus('Your message');

    // Add the cookie using JavaScript.
    $page_top['#attached']['html_head'][] = [
      ['#tag' => 'script',
        '#value' => 'document.cookie = "cookie_name=1; path=/; expires=' . date(DATE_COOKIE, strtotime("+1 year")) . '"',
      ],
      'mymodule_add_cookie_js',
    ];
  }
}

```

`system_page_top()` is an example of `hook_page_top()` implementation that uses the messenger to show messages.
`big_pipe_page_attachments()` is an example of hook implementation that sets a cookie using JavaScript (using `#attached`).
> 0 votes

---
Tags: cookies
---


# Question
Title: How do I translate the default meta tags?
I dont see any option to translate default meta tags. Any tips?

Admin url is at admin/config/search/metatag/taxonomy\_term

I am using metatag version 2.x

# Answer
> 2 votes
Method 1:

Use the "Translate" option in the list of Metatag defaults:

Method 2:

1. Go to Admin -\> Configuration -\> Region and language -\> Configuration translation
2. Click 'List' next to 'Metatag defaults"
3. Choose the category and translate from there:

If neither option is available, you probably don't have the Configuration translation module installed.

---
Tags: i18n-l10n, 10, meta-tags
---


title: How can I disable the core migrations?

text:
# Question
Title: How can I disable the core migrations?
After I enabled the Drupal Migrate module in Drupal 10, every time I do a `drush ms` I get a page of errors about Drupal 7 migrations like the following one.

> \[error\] Could not retrieve source count from d6\_filter\_format: SQLSTATE\[42S02\]: Base table or view not found: 1146 Table 'PRODUCTION\_content\_shared.filter\_formats' doesn't exist: SELECT COUNT(\*) AS "expression"

Is there a way to disable migrations by migration\_tags (all the Drupal 6 migrations are tagged "Drupal 6") or if necessary by ID?

# Answer
> 1 votes
Use `hook_migrations_plugins_alter`.

For example:

```
/**
 * Implements hook_migration_plugins_alter().
 */
function my_module_migration_plugins_alter(array &$migrations) {
  $migrations = array_filter($migrations, fn(array $m) => $m['provider'] == 'my_module');
}

```

---
Tags: migrations, 10
---


# Question
Title: How to add a prefix to all taxonomy terms of a given vocabulary?
I wish that any `term` will have a prefix like `articles in the topic of` so to get:

**`Articles in the topic of term`**.

How to do that?

# Answer
> 0 votes
If you are using the default taxonomy term view \[admin/structure/views/view/taxonomy\_term\], you can add the prefix either in title of the view with token or in the override title field as shown in the below screenshots.

OR

---
Tags: taxonomy-terms, 10
---


# Question
Title: How can I change the order of vertical tabs?
More specifically, I am using Location in Drupal 7 to allow people to add location to nodes, but I want it to appear at the top of the vertical tabs fieldgroup.

# Answer
> 2 votes
An alternative to user842's answer (for those who are interested in a more code-based solution) is to set the weights on all the form items whose group is "additional\_settings". The idea here is, higher the weight, lower the tab will appear, where the default is the one with the lowest weight (or one whose weight is not set).

To do this without modifying core code, create a module with a very large weight (see weight module for a module-based, or this post for a DB-based approach). Specifying the weight is important so that the module can be executed later than other modules which may change the order of the tabs. In this module, implement `hook_form_alter`, or `hook_node_form_alter`, which exposes the `$form` array.

At this point, you can look at the `$form` array, and pick out the indices that correspond to the vertical tabs whose order you wish to edit. One way to look at the `$form` array is by printing it out (via `drupal_set_message($form)` in the `hook_form_alter` or `hook_node_form_alter`). This array is huge, so you may want to run some kind of browser search to find the items that you want.

The indices that correspond to items on the vertical tabs are items with `#group` set to `additional_settings` (so you can search for the string "additional\_settings"). For example, you may see the following when you print out the `$form` array:

```
Array
(
   ...
   [path] => Array
        (
           ...
           [#group] => additional_settings
           [#weight] => 314159265359
           ...
        )
)

```

and this is showing that `path` which corresponds to the location tab in the vertical tabs has a large weight 314159265359, which might be the reason that it is showing last in the vertical tabs.

With this info, you can now change the order of this tab in your hook by decreasing the value of the weight:

```
// set a small weight for the path tab
$form['path']['#weight'] = -1;

```

in your hook. Notice that weights can also be negative. You can do this for some/every tab (by going through and changing their weights).

This unfortunately doesn't work for all situations. If the `#weight` of some vertical tab is unset, somehow, that tab will bubble to the top. You can fix that by giving the tab. The source-code might look something like

```
/**
   Implements hook_form_FORMID_alter, hook_node_form_alter.
   Changes the $form array to reorder vertical tabs.
*/
function reorder_vertical_tabs_node_form_alter(&$form, &$form_state, $form_id)
{
   // move 'location' to the top (as the default)
   $form['path']['#weight'] = -1;

   // move 'themekey' under 'locations'
   $form['themekey_ui_themes']['#weight'] = 0;

   // move 'published/unpublished' down
   $form['options']['#weight'] = 100;
}

```

From experience, I do not think you can change the order of the vertical tabs by setting `$form['#after_build']`. At that point, the order of the vertical tabs are already set.

# Answer
> 1 votes
The Renderable elements module enables you to will make (additional) elements (such as anything in the vertical tabs) available of existing entities on the manage forms/display screens so you can rearrange the fields through Field UI.

# Answer
> 1 votes
You can simply use the Vertical Tabs Config module.

> This module allows you to:
>
> * Hide vertical tabs on add/edit node pages depending on content type and role.
> * Decide vertical tabs order.

**Warning**

You may need to clear drupal's and/or browsers cache if you don't see any changes after modifying vertical tabs order.

# Answer
> 0 votes
Drupal 10 change order for form Configure block

```
/**
 * Implements hook_form_alter().
 * Set\change VerticalTabs order (Configure block /admin/structure/block/manage/...)
 */
function wt_plus_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
    switch($form_id){
    /*Set\change VerticalTabs order*/
        case 'block_form':
            $order = ['request_path', 'user_role', 'php'];
            foreach($order as $key => $value) if( isset($form['visibility'][$value]) ) $form['visibility'][$value]['#weight'] = -(100 - $key);
            break;
    }
}

```

---
Tags: 7, forms
---


# Question
Title: How can I get image style URL based on the file stored in the module?
Given `mymodule` with image `img/1.png`.

I'm trying to get the image style of that image programmatically.

```
$module_path = \Drupal::service('module_handler')->getModule('mymodule')->getpath();
$url = ImageStyle::load('image_style_id')->buildUrl($module_path . '/img/1.png');

```

In that case `$url` contains a URL but the image is not generated and not available. How to build an image style URL in that case properly?

# Answer
Something like this could work:

1. Load the image file as an ImageInterface instance using `image.factory` service.
2. Load the image style entity.
3. Traverse the effects of the image style and apply each effect to the ImageInterface instance.
4. Save the ImageInterface instance to an available file system scheme

Example:

```
$module_path = \Drupal::service('module_handler')->getModule('mymodule')->getpath();
/** @var \Drupal\Core\Image\ImageInterface $image */
$image = \Drupal::service('image.factory')->get($module_path . '/img/1.jpg');
$image_style = ImageStyle::load('image_style_id');
/** @var \Drupal\image\ImageEffectInterface $effect */
foreach ($image_style->getEffects() as $effect) {
   $effect->apply($image);
}
// This should be a valid stream wrapper destination rather than the module directory.
// That could cause it to get deleted later automatically.
$success = $image->save('public://my_module/images/1.image_style_id.jpg');

```
> 0 votes

---
Tags: 10
---


# Question
Title: Undefined index: php in system_requirements()
Whenever updating modules on an existing Drupal 7 site, I got lots of these error messages

> Undefined index: php in system\_requirements() (line 471 of xxxx/modules/system/system.install)

The code at that place reads

```
$files = system_rebuild_module_data();

...

foreach ($files as $module => $file) {
      // Ignore disabled modules and installation profiles.
      ...
      $php = $file->info['php'];
      if (version_compare($php, PHP_VERSION, '>')) {
        $requirements['php']['description'] .= $t('@name requires at least PHP @version.', array('@name' => $name, '@version' => $php));
        $requirements['php']['severity'] = REQUIREMENT_ERROR;
      }

```

It looks as if `system_rebuild_module_data` reads module .info files from the database (and stores it in `drupal_static`).

The 'php' entry in module .info files is optional, and indeed missing in most of my contrib modules.

Is this a bug in system.install, or am I missing something ?

The odd thing is, lots of people should be having this problem, and I can't find much online. Perhaps there is something wrong with my installation ?

# Answer
When a module does not set a value for `php` in its .info file, Drupal uses `DRUPAL_MINIMUM_PHP` as default value. That default value is set by `_system_rebuild_module_data()` (which is called by `system_rebuild_module_data()`, which is called by `system_requirements()`).

```
// Set defaults for module info.
$defaults = array(
  'dependencies' => array(),
  'description' => '',
  'package' => 'Other',
  'version' => NULL,
  'php' => DRUPAL_MINIMUM_PHP,
  'files' => array(),
  'bootstrap' => 0,
);

// omissis

// Merge in defaults and save.
$modules[$key]->info = $module->info + $defaults;

```

Since the data returned from `system_rebuild_module_data()` is only cached in memory, I suppose there must be a module implementing `hook_system_info_alter()` that removes the `php` index from that data.

`_system_rebuild_module_data()` invokes those hooks with the following code.

```
// Invoke hook_system_info_alter() to give installed modules a chance to
// modify the data in the .info files if necessary.
$type = 'module';
drupal_alter('system_info', $modules[$key]->info, $modules[$key], $type);

```
> 1 votes

---
Tags: 7, updating
---


# Question
Title: How can I invalidate the entire cache when saving menu item?
I use the Simplify Menu module. Since changes in the menu are not applied, I would like to clear the entire cache. Unfortunately the following code does not work

```
function MODULE_menu_link_content_update(Drupal\Core\Entity\EntityInterface $entity)
{
        if ($entity->menu_name->value === 'main') {
            \Drupal::cache()->invalidateAll();
        }
}

```

does anyone have an idea?

# Answer
The Simplify Menu module has the problem that it doesn't transport cache metadata and a code like `\Drupal::cache()->invalidateAll()` doesn't exist.

You can add the most important cache tag to the Twig template yourself because you know the menu name:

```
{{ {'#cache': {'tags': ['config:system.menu.main']}} }}

```

Then changes in the `main` menu configuration will trigger a cache invalidation of the template automatically.

After you've added the cache tag to the Twig template it's possible to use it for menu content as well. The hook mentioned in the question can target it like this:

```
\Drupal::service('cache_tags.invalidator')->invalidateTags(['config:system.menu.main']);

```

This cache tag is the most important one and you will see a much better cache handling. However, there is a lot more cache metadata in menus, for example cache contexts for access handling, so this module should be used only for really simple menus.

## Background info

For arrays Twig `{{ }}` is the same as PHP `render()`, which bubbles up metadata in contrast to renderPlain(). You use render() in the theming level (templates and preprocess hooks) when there is a page render stack (Drupal term *render context* not to be confused with *cache context*). Bubbleable metadata is `#cache` and `#attached` in a render array.

For example the core Twig function

```
{{ attach_library('core/drupal.dialog.ajax') }}

```

runs PHP:

```
  public function attachLibrary($library) {
    assert(is_string($library), 'Argument must be a string.');

    // Use Renderer::render() on a temporary render array to get additional
    // bubbleable metadata on the render stack.
    $template_attached = ['#attached' => ['library' => [$library]]];
    $this->renderer->render($template_attached);
  }

```

and could be replaced in Twig with the same result:

```
{{ {'#attached': {'library': ['core/drupal.dialog.ajax']}} }}

```

For #cache metadata you have to use the second method as there is no specific Twig function in core. Or the contrib module could bubble up the cache tag already when preparing the menu tree because Twig functions are always in a render stack.
> 4 votes

# Answer
`\Drupal::cache()``->invalidateAll()` does not invalidate the content of all the cache bins; it just invalidates the content of the *cache.default* bin.

The Workspaces module uses `workspaces_menu_link_content_update()` to invalidate the menu cache. I would use similar code.

```
function mymodule_menu_link_content_update(EntityInterface $entity) {
  if ($entity->getMenuName() === 'main') {
    $cache_tags = Cache::buildTags('config:system.menu', ['main'], '.');
    \Drupal::service('cache_tags.invalidator')->invalidateTags($cache_tags);
  }
}

```
> 1 votes

---
Tags: hooks, navigation, caching, 10
---


# Question
Title: How to prevent installing module in already installed module?
I have few modules that provide the same functionality in a different way and they would collide if they would be installed simultaneously.

So I want to prevent installation of multiple modules of this "type". I do not want to add hook\_requirements into each one of these modules, I'd prefer to have a central logic(in a "central" module, or profile even).

What would be a good approach to this?

# Answer
> 0 votes
How about this?

1. Create a central module that implements hook\_module\_preinstall
2. Follow some naming convention for the modules which should not coexist, or simply gather their names in a list
3. Use your naming convention or the list of module names in your hook to detect installation of one of these modules. Also, implement the logic for counting the number of such modules that are already enabled in the system.
4. Conditional on one such module arriving as an argument to hook\_module\_preinstall and the count of existing enabled modules of this kind being greater than 0, throw a custom Exception in order to block the installation of the said module.
5. Most importantly, add your central module as a dependency to all of your modules that you wanna control in that way.

Very theoretical answer. :-) May it be of help to you!

# Answer
> 0 votes
One solution is to use hook\_requirements(). It has to be in an .install file. It can return an array that prevents module installation.

An example. This is for the module t1. The code is in t1.install.

```
/**
 * Implements hook_requirements().
 */
function t1_requirements($phase) {
  if ($phase == 'install') {
    $requirements['bob_having_a_fit'] = [
      'title' => t('T1'),
      'description' => t('Bob does not like it.'),
      'severity' => REQUIREMENT_ERROR,
    ];
    return $requirements;
  }
}

```

To test whether a module is installed, you could do something like:

```
if (\Drupal::service('module_handler')->moduleExists('doom_module')) {

```

# Answer
> 0 votes
Looking into this I agree there is not an easy way to do this. I suspect you need to do three things:

1. Form alter the module installation form and add validation there.
2. Then for Drush I would implement a hook to do the same thing - which I have not done, but it appears to be something that would work.
3. For config import add a ConfigImportSubscriber.php and I suspect you can throw an exception.

---
Tags: 8
---


# Question
Title: Drupal\Component\Plugin\Exception\PluginNotFoundException: The "The filter image lazy load" plugin does not exist
How can I resolve this issue? This log issue occurs in Drupal 10.0.10, Drush 12.2.0 PHP 8.1 Everything seemed fine when I had upgraded to D10 all the sites were working fine, All of sudden its starting to throw this error when I had cleared cache.

I tried resolving it deleting the vendor and composer lock and then running a composer update it did not work.

I am assuming the issue is caused by Blazy module. I hope I am correct. I tried to remove the module and but its dependency modules are failing to delete.

```
0-php-errors.log
Uncaught PHP Exception Error: "Class "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList" not found" at /mnt/www/html/Development/docroot/core/lib/Drupal/Core/TypedData/TypedDataManager.php line 91 request_id="v-82976780-5dd8-11ee-8de8-b7f4d9badddd"
 Uncaught PHP Exception Drupal\Component\Plugin\Exception\PluginNotFoundException: "The "filter_image_lazy_load" plugin does not exist. Valid plugin IDs for Drupal\filter\FilterPluginManager are: blazy_filter, editor_file_reference, entity_embed, filter_align, filter_autop, filter_caption, filter_html, filter_htmlcorrector, filter_html_escape, filter_html_image_secure, filter_null, filter_url, media_embed, filter_responsive_tables_filter, slick_filter, video_embed_wysiwyg" at /mnt/www/html/Development/docroot/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php line 53 request_id="v-2aa6af7a-5dda-11ee-a334-e7cbdc62d936"

```

# Answer
I had the same issue rolling back from Drupal 10.1.4 to 9.5.11. You can get the conflicting config with this cmd  `drush cget filter.format.basic_html filters.filter_image_lazy_load` then you will be able to remove from DB with  `drush cdel filter.format.basic_html filters.filter_image_lazy_load`
> 2 votes

---
Tags: drush, 10
---


# Question
Title: PhpUnit 9.6.16 with Drupal 10.2 unable to write to output directory: How to fix that error?
I have multiple sites running Drupal 10.1.7, all are using custom modules with appropriate functional tests. The tests are running fine.

Now, I wanted to update the sites to Drupal 10.2.2. This update also applied smoothly without any issues. But PhpUnit started to complain with the following error:

HTML output directory web/sites/simpletest/browser\_output is not a writable directory.

Tests are still running ok \[OK (9 tests, 85 assertions)\], but no output is written to the configured output directory. The directory existed before, wasn't changed and still contains output from the last test using Drupal 10.1.7.

When reverting the site to Drupal 10.1.7, the error disappears again. So, I assume, it must have something to do with Drupal 10.2, but I couldn't find anything related in the changelogs.

Tests are running as user www-data and the directories "web/sites/simpletest" and "web/sites/simpletest/browser\_output" belong to that user. Folder permissions have been set to 755 and 777 without fixing the error.

Directory is set with: `<env name="BROWSERTEST_OUTPUT_DIRECTORY" value="web/sites/simpletest/browser_output"/>`, but I also tried with `./web/sites/simpletest/browser_output` (alternative relative notation) and `/var/www/html/web/sites/simpletest/browser_output` (absolute path) to no avail.

Example output from one of the sites:

```
$ vendor/bin/phpunit web/modules/custom
HTML output directory web/sites/simpletest/browser_output is not a writable directory.
PHPUnit 9.6.16 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.1.22
Configuration: /var/www/html/phpunit.xml

Testing /var/www/html/web/modules/custom
.........                                                           9 / 9 (100%)

Time: 01:09.421, Memory: 14.00 MB

OK (9 tests, 85 assertions)

```

It's identical in Drupal 10.1.7 but without the error in line 2.

## Update #1

I tried an incremental update. Tests are running fine and output is written to defined directory with 10.1.7 and 10.1.8, writing problems start with 10.2.0.

# Answer
I am not sure why this was changed, but starting with Drupal 10.2.0, the `BROWSERTEST_OUTPUT_DIRECTORY` variable is no longer relative to the DOCROOT at `/var/www/html` but relative to the WEBROOT at `/var/www/html/web`.

Thus, the new line in my phpunit.xml file reads as follows and the error is gone.

`<env name="BROWSERTEST_OUTPUT_DIRECTORY" value="sites/simpletest/browser_output"/>`
> 3 votes

---
Tags: testing, 10
---


# Question
Title: I cannot update packages because https://www.drupal.org/9/packages.json cannot be downloaded
Trying to do a simple composer update of my core Drupal packages to the latest recommended version, and I'm being hit with a 404 and it won't update.

The error Composer spits out when running `composer update drupal/core "drupal/core-*" --with-all-dependencies` is:

```
[Composer\Downloader\TransportException]
  The "https://www.drupal.org/9/packages.json" file could not be downloaded (HTTP/2 404 )

```

I have tried manually visiting both https://www.drupal.org/9/packages.json AND https://packages.drupal.org/9/packages.json from a web-browser and am similarly met with a 404. Is the file legitimately missing right now, or did my Composer somehow bork itself?

The repo/packages section in my composer.json reads

```
"repositories": {
        "drupal": {
            "type": "composer",
            "url": "https://packages.drupal.org/9"
        },
        "0": {
            "type": "composer",
            "url": "https://packages.drupal.org/8"
        }
    }

```

I have not edited this at all since the update process was working properly--so I don't know why it wouldn't be working. This was also generated automatically during a Composer Drupal install and I wasn't the one who wrote it.

Any help would be appreciated--thank you! Let me know if there is more information I can provide.

# Answer
> 2 votes
There is no https://packages.drupal.org/9; the API stopped at 8. The repo at https://packages.drupal.org/8 has packages for Drupal 8+ (including 9):

```
"repositories": {
    "drupal": {
        "type": "composer",
        "url": "https://packages.drupal.org/8"
    }
}

```

It's not really possible to guess how you might have ended up in this situation. https://packages.drupal.org/9/package.json has never existed, so logically you should have received the same error previously if your composer.json was the same. Perhaps it was changed by someone else without your knowledge at some point.

# Answer
> 0 votes
Some modules added this wrong url by mistake to their composer.json in some releases.

```
"repositories": {
    "packages.drupal": {
      "type": "composer",
      "url": "https://packages.drupal.org/9"
    }
  }

```

you need to either patch it, clear the `modules/contrib` folder before the next update or fix it manually (search and replace).

In my case it was `drupal/views_search_snippet` but there might be others as well.

---
Tags: composer
---


title: Inject code just after Body tag

text:
# Question
Title: Inject code just after Body tag
I need to inject some code inside of **head** and **body** tags.

I managed to add code inside of **head** using **hook\_page\_attachments\_alter** but I can't do same for Body.

Can somebody point me into right direction?

I am trying to inject a **iframe** just after **body** tag so it is like

```
<body><iframe>....</body>

```

Thanks.

# Answer
> 0 votes
The body tag is rendered using the theme's Twig template, usually `html.html.twig`. So the natural option would be do add it there.

See e.g. https://git.drupalcode.org/project/drupal/-/blob/11.x/core/themes/olivero/templates/layout/html.html.twig?ref\_type=heads

Since the `<body>` tag (and whatever is following immediatly after the body tag) is hard-coded in the theme's Twig file, you most likely will no be able to inject this this programmatically with a hook.

---
Tags: hooks, 10
---


title: Unable to upgrade from 10.1 to 10.2

text:
# Question
Title: Unable to upgrade from 10.1 to 10.2
I've been using Composer from the start and have had no problem upgrading Drupal as it went, but somehow it refuses to upgrade from 10.1.8 to 10.2.

In my composer.json I have `"drupal/core-recommended": "^10",`

If I run `composer update "drupal/core-*" --with-all-dependencies`, it tells me there's nothing to update.

So in changed composer.json to `"drupal/core-recommended": "^10.2.2",`

And now when I run composer it tells me *"drupal/core 10.2.2 conflicts with drupal/core 10.2.2"*

Any idea what's going on?

Edit - here are more details:

**with "drupal/core-recommended": "^10"**

command:

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

```

output:

```
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating optimized autoload files

```

command:

```
composer why-not drupal/core-recommended 10.2.2

```

output:

```
drupal/core-recommended     10.2.2      requires          drupal/core (10.2.2)
drupal/recommended-project  dev-master  does not require  drupal/core (but 10.1.8 is installed)
drupal/core-recommended     10.2.2      requires          asm89/stack-cors (~v2.2.0)
drupal/recommended-project  dev-master  does not require  asm89/stack-cors (but v2.1.1 is installed)
drupal/core-recommended     10.2.2      requires          composer/semver (~3.4.0)
drupal/recommended-project  dev-master  does not require  composer/semver (but 3.3.2 is installed)
drupal/core-recommended     10.2.2      requires          guzzlehttp/guzzle (~7.8.1)
drupal/recommended-project  dev-master  does not require  guzzlehttp/guzzle (but 7.7.1 is installed)
drupal/core-recommended     10.2.2      requires          guzzlehttp/psr7 (~2.6.2)
drupal/recommended-project  dev-master  does not require  guzzlehttp/psr7 (but 2.5.1 is installed)
drupal/core-recommended     10.2.2      requires          symfony/console (~v6.4.1)
drupal/recommended-project  dev-master  does not require  symfony/console (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/dependency-injection (~v6.4.1)
drupal/recommended-project  dev-master  does not require  symfony/dependency-injection (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/deprecation-contracts (~v3.4.0)
drupal/recommended-project  dev-master  does not require  symfony/deprecation-contracts (but v3.3.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/error-handler (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/error-handler (but v6.3.5 is installed)
drupal/core-recommended     10.2.2      requires          symfony/event-dispatcher (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/event-dispatcher (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/event-dispatcher-contracts (~v3.4.0)
drupal/recommended-project  dev-master  does not require  symfony/event-dispatcher-contracts (but v3.3.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/http-foundation (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/http-foundation (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/http-kernel (~v6.4.1)
drupal/recommended-project  dev-master  does not require  symfony/http-kernel (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/mime (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/mime (but v6.3.5 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-ctype (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-ctype (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-iconv (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-iconv (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-intl-grapheme (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-intl-grapheme (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-intl-idn (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-intl-idn (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-intl-normalizer (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-intl-normalizer (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-mbstring (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-mbstring (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/polyfill-php83 (~v1.28.0)
drupal/recommended-project  dev-master  does not require  symfony/polyfill-php83 (but v1.27.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/process (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/process (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/psr-http-message-bridge (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/psr-http-message-bridge (but v2.2.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/routing (~v6.4.1)
drupal/recommended-project  dev-master  does not require  symfony/routing (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/serializer (~v6.4.1)
drupal/recommended-project  dev-master  does not require  symfony/serializer (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/service-contracts (~v3.4.0)
drupal/recommended-project  dev-master  does not require  symfony/service-contracts (but v3.3.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/string (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/string (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/translation-contracts (~v3.4.0)
drupal/recommended-project  dev-master  does not require  symfony/translation-contracts (but v3.3.0 is installed)
drupal/core-recommended     10.2.2      requires          symfony/validator (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/validator (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/var-dumper (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/var-dumper (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/var-exporter (~v6.4.1)
drupal/recommended-project  dev-master  does not require  symfony/var-exporter (but v6.3.11 is installed)
drupal/core-recommended     10.2.2      requires          symfony/yaml (~v6.4.0)
drupal/recommended-project  dev-master  does not require  symfony/yaml (but v6.3.8 is installed)
drupal/core-recommended     10.2.2      requires          twig/twig (~v3.8.0)
drupal/recommended-project  dev-master  does not require  twig/twig (but v3.6.1 is installed)

```

**with "drupal/core-recommended": "10.2.2"**

command:

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

```

output:

```
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
  Problem 1
    - Root composer.json requires drupal/core-recommended 10.2.2 -> satisfiable by drupal/core-recommended[10.2.2].
    - drupal/core 10.2.2 conflicts with drupal/core 10.2.2.
    - drupal/core-recommended 10.2.2 requires drupal/core 10.2.2 -> satisfiable by drupal/core[10.2.2].
    - drush/drush is locked to version 11.6.0 and an update of this package was not requested.

```

# Answer
> 2 votes
```
Your requirements could not be resolved to an installable set of packages.
  ...
  - drush/drush is locked to version 11.6.0 and an update of this package was not requested.

```

See the release notes of Drupal 10.2.x:

> ## Drush versions below 12.4.3 are incompatible with Drupal 10.2.x
>
> * Users of Drush will need to upgrade to version 12.4.3 or greater.

https://www.drupal.org/project/drupal/releases/10.2.0

---
Tags: composer
---


# Question
Title: Relationship between taxonomy field and other fields within a content type (recipe example)
I'm looking to implement a recipe content type, in which the ingredients are chosen from a taxonomy list using the reference field; and the amount of each ingredient varies and added manually per recipe.

How do I create a realtionship between the taxonomy field and the amount field? Does drupal implicitly assume a relationship according to the order of input? Or do I need to use a different module for that? (Paragraphs module?)

To clarify my need: imagine a view that queries and displays the number of eggs used in each recipe.

I'm using drupal 10 (and have preference for relying on drupal core functionality only, if at all humanly possible in this case. )

Thanks in advance for any ideas how to design this!

# Answer
> How do I create a realtionship between the taxonomy field and the amount field? Does drupal implicitly assume a relationship according to the order of input?

The order of the items in separate fields does provide a (weak) relationship, which you *could* exploit manually in preprocess functions/templates - but it would mean you have to maintain the two lists separately, which will get more awkward as a recipe gets more complex. Imagine having to manage those lists for a Beef Wellington!

You also may want to add another dimension in the future (e.g. "Prep details" for each ingredient), and then you'll have 3 lists to manage.

You've already mentioned the easiest solution, which is Paragraphs. It was built for pretty much exactly the kind of use case you've described, is very mature, and has plenty of complementary modules to get additional functionality quickly (like Paragraphs table). Plus the UI for managing fields is identical to core, so very little learning curve. For basic to mid-level requirements it's an out-of-the box, no-code solution, and works great.

Another option is to create your own custom field type, field widget(s), formatter(s), etc. Unless you need the (probably only very small) performance improvement this method provides, or need something specific that Paragraphs doesn't offer, it's probably overkill for what you're describing.
> 2 votes

---
Tags: taxonomy-terms
---


# Question
Title: Programmatically updating a Webform submission with a file element doesn't remove the attached files
I am using Drupal 9.3 and Webform 6.1.7.

I have a Webform that contains a file element and am building a custom form to update the submission data.

I have found that when I submit my custom form I can load the Webform submission and set the file IDs and it will save the data fine with no issues.

However when it comes to removing a file after saving, the file I remove just remains attached.

I know that using the Webform interface the file upload / remove is working fine. However it seems to be a problem with removing files when updating the submission programmatically.

Is there any extra step I am missing to remove the files?

This is really simple version of the form I am building:

```
<?php

namespace Drupal\example\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides an example form.
 */
class WebformFileUpdateForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'example_webform_file_update_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $em = \Drupal::entityTypeManager();

    // Load all webform submissions for the node.
    $nid = 86552;
    $query = $em->getStorage('webform_submission')
      ->getQuery()
      ->condition('webform_id', 'tutorial', '=')
      ->condition('entity_id', $nid)
      ->pager(10);

    $submission_ids = $query->execute();
    $webform_submissions = $em->getStorage('webform_submission')
      ->loadMultiple($submission_ids);

    $form['#tree'] = TRUE;

    $form['files'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Files'),
    ];

    foreach ($webform_submissions as $submission) {
      $form['files'][$submission->id()]['file'] = [
        '#type' => 'managed_file',
        '#title' => $this->t('File'),
        '#default_value' => $submission->getElementData('attached_files'),
        '#upload_location' => 'private://tutorials/' . $submission->id() . '/',
        '#multiple' => TRUE,
      ];
    }

    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Update'),
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $em = \Drupal::entityTypeManager();
    $values = $form_state->getValues();
    foreach ($values['files'] as $sid => $data) {
      $em->getStorage('webform_submission')
        ->load($sid)
        ->setElementData('attached_files', $data['file'])
        ->save();
    }
  }

}

```

# Answer
This issue replicable for anonymous users only, kind of caching issue so if you add following cache API function code in custom form , then latest submission data 'll be load that 'll be added/removed by user while interacting with custom form.

```
 $form['#cache'] = [
   'contexts' => ['session']
 ];

```
> 1 votes

---
Tags: forms, webforms
---


title: Arrange images and text side-by-side

text:
# Question
Title: Arrange images and text side-by-side
I've created a view for selected articles to be featured on the homepage. Each article includes the following fields: image, title, body. Currently, these elements are arranged vertically, but I aim to modify their layout. I want to position the image to the left with the title and body text to the right of the image.

Initially, I considered using a CSS style such as 'float:left' for this purpose. However, it appears that the ability to add custom CSS classes to images has been removed in Drupal 10. Do you have any suggestions for achieving this layout?

# Answer
**Steps to Add class to img field wrapper:**

1. click on image field, click on style settings
2. Check this option "Customize field and label wrapper HTML"
3. Select from select box (Wrapper HTML element) dropdown "div" element
4. Checked the "Add HTML class" option & add your required class

Note: clear cache after adding class

There are multiple approaches to achieve the desired layout that you specified:

1. Create view mode & theming of this newly created view mode as per desired HTML structure , select the view mode in view -- i.e more flexible way as that layout can be reused anywhere where that view mode selected , layout 'll not restricted to view page/block only
2. Add custom text field in view after adding all the fields , then hide the all fields except custom field, Add required html structure as per need in the "custom text" field along with token of field that are hide in above for eg:

Note: These token can be get from by click on "Replacement patterns" tab of custom text field.
> 0 votes

---
Tags: views, 10
---


# Question
Title: Block config not saved apparently due to an enforced 303 status
After upgrading from 10.0 to 10.1.7, changes to block configuration were no longer saved.

I cannot reproduce this in an out-of-the-box instance. I've turned off all custom modules and as many of the contrib modules in use that I can without utterly breaking the site due to interdependencies between modules and content structures, etc. Nothing changed with this behavior.

I've since moved on to attempting to locate where the behavior is explained in xdebug.

When I click the submit button in the block config form, stepping through in xdebug, I reach ::submitConfigurationForm() in BlockPluginTrait.

At this point $form\_state contains the changed block title in $form\_state-\>decoratedFormState-\>values\[settings\]\[label\].

::blockSubmit() is then called.

It progresses to FormBuilder::buildForm() where ::processForm() is called and returns an EnforcedResponseException code of 303 (Other).

No error message is given on the screen, nothing logged, but the result is a return to the block layout screen with the config change not having been saved.

What does the 393 enforced response exception signify and what can cause it in the context of saving a block config?

# Answer
> What does the 30x enforced response exception signify and what can cause it in the context of saving a block config?

You are debugging low-level code on how Form API handles redirects. Drupal forms use this exception to transport redirect responses to the Symfony stack because they didn't find a way to return it from the form controller. This is an internal issue for core developers. You need to debug Form API level code returning a redirect response from a form class or setting the redirect in `$form_state` in places where you don't want to do that.

In Form API the default for any form is set to redirect to itself after a successful submit to display a new empty form. Even a very simple form would show this exception in xdebug. Additonally, a destination query parameter can override any redirect URL. Where the user is sent after a form is submitted is not necessarily determined by the form.
> 1 votes

---
Tags: blocks, configuration, 10
---


title: Programmatically create media file

text:
# Question
Title: Programmatically create media file
I'm trying to programmatically create a media file. Here's what I got so far:

```
$file_data = file_get_contents('http://www.africau.edu/images/default/sample.pdf');

$file = file_save_data($file_data, 'public://3010.pdf', FILE_EXISTS_REPLACE);

$media = Media::create([
  'bundle' => 'file',
  'uid' => '0',
  'field_media_file' => [
    'target_id' => $file->id(),
  ],
]);

$media->setName('Hello')
  ->setPublished(TRUE)
  ->save();

```

Unfortunately this returns me the following error and the media file doesn't get saved:

> Error: Call to a member function getSource() on null in Drupal\media\Entity\Media-\>getSource() (line 138 of core/modules/media/src/Entity/Media.php).

What am I doing wrong? How can I fix this error?

# Answer
> 10 votes
`uid` shouldn't be 0, just change `'uid' => '0'` by `'uid'=> \Drupal::currentUser()->id()`:

```
$file_data = file_get_contents('http://www.africau.edu/images/default/sample.pdf');
$file = file_save_data($file_data, 'public://3010.pdf', FILE_EXISTS_REPLACE);
$media = Media::create([
  'bundle'           => 'file',
  'uid'              => \Drupal::currentUser()->id(),
  'field_media_file' => [
    'target_id' => $file->id(),
  ],
]);
$media->setName('Hello')->setPublished(TRUE)->save();

```

# Answer
> 6 votes
Things have changed a bit since the advent of Drupal 9 and the Media module in core. Notice that the bundle is now `'document'`. You can find the media types at /admin/structure/media: Hover your mouse over the edit button and you'll see the media type in the URL.

```
$file_data = file_get_contents('http://www.africau.edu/images/default/sample.pdf');
$file = file_save_data($file_data, 'public://3010.pdf', FileSystemInterface::EXISTS_REPLACE);
$media = Media::create([
  'bundle'=> 'document',
  'uid' => \Drupal::currentUser()->id(),
  'field_media_document' => [
    'target_id' => $file->id(),
  ],
]);

$media->setName('3010 Sample PDF')
  ->setPublished(TRUE)
  ->save();

```

# Answer
> 0 votes
**TL;DR** Use `\Drupal::service('file.repository')->writeData()` instead of `file_save_data` in Drupal 10

I got `Call to undefined function Drupal\custom_module\Commands\file_save_data()`

Here's the change record: https://www.drupal.org/node/3223520

---
Tags: 8, media
---


title: How do I add a "Add more" button?

text:
# Question
Title: How do I add a "Add more" button?
I want to show a *Add more* button that adds an extra text field and the corresponding *Remove* button.

I followed `ajax_example_add_more()` to achieve this in Drupal 7. For Drupal 8, I tried with the following code.

```
class XmlfeedsSettingsForm extends FormBase {
  public function getFormId() {
    return 'formid';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
   // Because we have many fields with the same values, we have to set
  // #tree to be able to access them.
  $form['#tree'] = TRUE;
  $form['names_fieldset'] = array(
    '#type' => 'fieldset',
    '#title' => t('People coming to the picnic'),
    // Set up the wrapper so that AJAX will be able to replace the fieldset.
    '#prefix' => '<div id="names-fieldset-wrapper">',
    '#suffix' => '</div>',
  );

  // Build the fieldset with the proper number of names. We'll use
  // $form_state['num_names'] to determine the number of textfields to build.
  $form_state->getValue['num_names_parent'] = '';
  if (empty($form_state->getValue['num_names_parent'])) {
    $form_state->getValue['num_names_parent'] = 1;
  }
  for ($i = 0; $i < $form_state->getValue['num_names_parent']; $i++) {
    $form['names_fieldset']['name'][$i] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
    );
  }
  $form['names_fieldset']['add_name'] = array(
    '#type' => 'submit',
    '#value' => t('Add one more'),
    '#submit' => array('::ajax_example_add_more_add_one'),
    // See the examples in ajax_example.module for more details on the
    // properties of #ajax.
    '#ajax' => array(
      'callback' => '::ajax_example_add_more_callback',
      'wrapper' => 'names-fieldset-wrapper',
    ),
  );
  if ($form_state->getValue['num_names_parent'] > 1) {
    $form['names_fieldset']['remove_name'] = array(
      '#type' => 'submit',
      '#value' => t('Remove one'),
      '#submit' => array('::ajax_example_add_more_remove_one'),
      '#ajax' => array(
        'callback' => '::ajax_example_add_more_callback',
        'wrapper' => 'names-fieldset-wrapper',
      ),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  // This simply allows us to demonstrate no-javascript use without
  // actually turning off javascript in the browser. Removing the #ajax
  // element turns off AJAX behaviors on that element and as a result
  // ajax.js doesn't get loaded.
  // For demonstration only! You don't need this.
 // if ($no_js_use) {
    // Remove the #ajax from the above, so ajax.js won't be loaded.
//    if (!empty($form['names_fieldset']['remove_name']['#ajax'])) {
//      unset($form['names_fieldset']['remove_name']['#ajax']);
//    }
//    unset($form['names_fieldset']['add_name']['#ajax']);
 // }

  return $form;
  }
  public function ajax_example_add_more_add_one(array &$form, FormStateInterface $form_state) {
      print_R($form_state);die;
  //$form_state->getValue['num_names_parent']++;
  //$form_state->setRebuild(TRUE);

}
public function ajax_example_add_more_callback(array &$form, FormStateInterface $form_state) {
  return $form['names_fieldset'];

}
public function ajax_example_add_more_remove_one(array &$form, FormStateInterface $form_state) {
  if ($form_state->getValue['num_names_parent'] > 1) {
    $form_state->getValue['num_names_parent']--;
  }
  $form_state['rebuild'] = TRUE;
}

```

`print_r($form_state)` in `buildForm()` outputs the following.

```
[getValue] => Array
        (
            [num_names_parent] => 1
        )

```

In `ajax_example_add_more_add_one()`, the *num\_names\_parent* index is missing.

How can I correctly add the *Add more* button?

# Answer
> 13 votes
It works !! Try this

```
<?php

namespace Drupal\fapi_example\Form;

use Drupal\Core\Form\FormStateInterface;

class AjaxAddMore extends FormBase {
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['description'] = array(
      '#markup' => '<div>'. $this->t('This example shows an add-more and a remove-last button.').'</div>',
    );

    $i = 0;
    $name_field = $form_state->get('num_names');
    $form['#tree'] = TRUE;
    $form['names_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('People coming to picnic'),
      '#prefix' => '<div id="names-fieldset-wrapper">',
      '#suffix' => '</div>',
    ];
    if (empty($name_field)) {
      $name_field = $form_state->set('num_names', 1);
    }
    for ($i = 0; $i < $name_field; $i++) {
      $form['names_fieldset']['name'][$i] = [
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      ];
    }
    $form['actions'] = [
      '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
      '#type' => 'submit',
      '#value' => $this->t('Add one more'),
      '#submit' => array('::addOne'),
      '#ajax' => [
      'callback' => '::addmoreCallback',
      'wrapper' => 'names-fieldset-wrapper',
      ],
    ];
    if ($name_field > 1) {
      $form['names_fieldset']['actions']['remove_name'] = [
      '#type' => 'submit',
      '#value' => $this->t('Remove one'),
      '#submit' => array('::removeCallback'),
       '#ajax' => [
        'callback' => '::addmoreCallback',
        'wrapper' => 'names-fieldset-wrapper',
      ]
      ];
    }
    $form_state->setCached(FALSE);
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];

    return $form;
  }

  public function getFormId() {
    return 'fapi_example_ajax_addmore';
  }

  public function addOne(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    $add_button = $name_field + 1;
    $form_state->set('num_names', $add_button);
    $form_state->setRebuild();
  }

  public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    return $form['names_fieldset'];
  }

  public function removeCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    if ($name_field > 1) {
      $remove_button = $name_field - 1;
      $form_state->set('num_names', $remove_button);
    }
    $form_state->setRebuild();
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue(array('names_fieldset', 'name'));

    $output = t('These people are coming to the picnic: @names', array(
      '@names' => implode(', ', $values),
      )
    );
    drupal_set_message($output);
  }
}

```

# Answer
> 5 votes
If reporting error is turned on then the code Shreya Shetty returns error - might be related to changes in Drupal 8 ... but it is related to objects not being able to be used as ints... the amended version would be:

```
namespace Drupal\dynamic_forms\Form;

 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;

  class test3 extends FormBase {

public function buildForm(array $form, FormStateInterface $form_state) {
    $form['description'] = array(
        '#markup' => '<div>'. t('This example shows an add-more and a remove-last button.').'</div>',
    );
    $i = 0;
    $name_field = $form_state->get('num_names');
    $form['#tree'] = TRUE;
    $form['names_fieldset'] = [
        '#type' => 'fieldset',
        '#title' => $this->t('People coming to picnic'),
        '#prefix' => '<div id="names-fieldset-wrapper">',
        '#suffix' => '</div>',
    ];
    if (empty($name_field)) {
        $name_field = $form_state->set('num_names', 1);
    }

    if ($form_state->get('num_names')>0) {
        $value = $form_state->get('num_names');
    }
    else {
        $value=1;
    }
        for ($i = 0; $i < $value; $i++) {
            $form['names_fieldset']['name'][$i] = [
                '#type' => 'textfield',
                '#title' => t('Name'),
            ];
        }

    $form['actions'] = [
        '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
        '#type' => 'submit',
        '#value' => t('Add one more'),
        '#submit' => array('::addOne'),
        '#ajax' => [
            'callback' => '::addmoreCallback',
            'wrapper' => 'names-fieldset-wrapper',
        ],
    ];
    //        if ($name_field > 1) {
    if ($value > 1) {
        $form['names_fieldset']['actions']['remove_name'] = [
            '#type' => 'submit',
            '#value' => t('Remove one'),
            '#submit' => array('::removeCallback'),
            '#ajax' => [
                'callback' => '::addmoreCallback',
                'wrapper' => 'names-fieldset-wrapper',
            ]
        ];
    }
    $form_state->setCached(FALSE);
    $form['actions']['submit'] = [
        '#type' => 'submit',
        '#value' => $this->t('Submit'),
    ];

    return $form;
}

public function getFormId() {
    return 'fapi_example_ajax_addmore';
}

public function addOne(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    $add_button = $name_field + 1;
    $form_state->set('num_names', $add_button);
    $form_state->setRebuild();
}

public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    return $form['names_fieldset'];
}

public function removeCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    if ($name_field > 1) {
        $remove_button = $name_field - 1;
        $form_state->set('num_names', $remove_button);
    }
    $form_state->setRebuild();
}

public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue(array('names_fieldset', 'name'));

    $output = t('These people are coming to the picnic: @names', array(
            '@names' => implode(', ', $values),
        )
    );
    drupal_set_message($output);
}

}

```

Thought the amended version might help someone, especially newcomers... don't forget to say thanks to Shreya for original solution.

# Answer
> 1 votes
Another solution (use the get method instead of relying on the return value of the set method for $form\_state):

```
if (empty($name_field)) {
  $form_state->set('num_names', 1);
}
$name_field = $form_state->get('num_names');
for ($i = 0; $i < $name_field; $i++) {
  $form['names_fieldset']['name'][$i] = [
    '#type' => 'textfield',
    '#title' => t('Name'),
  ];
}

```

# Answer
> 1 votes
For Drupal 8:

```
namespace Drupal\form_api_example\Form;

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements the ajax demo form controller.
 *
 * This example demonstrates using ajax callbacks to add people's names to a
 * list of picnic attendees.
 *
 * @see \Drupal\Core\Form\FormBase
 * @see \Drupal\Core\Form\ConfigFormBase
 */
class AjaxAddMore {

  /**
   * Form with 'add more' and 'remove' buttons.
   *
   * This example shows a button to "add more" - add another textfield, and
   * the corresponding "remove" button.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['description'] = [
      '#type' => 'item',
      '#markup' => $this->t('This example shows an add-more and a remove-last button.'),
    ];

    // Gather the number of names in the form already.
    $num_names = $form_state->get('num_names');
    // We have to ensure that there is at least one name field.
    if ($num_names === NULL) {
      $name_field = $form_state->set('num_names', 1);
      $num_names = 1;
    }

    $form['#tree'] = TRUE;
    $form['names_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('People coming to picnic'),
      '#prefix' => '<div id="names-fieldset-wrapper">',
      '#suffix' => '</div>',
    ];

    for ($i = 0; $i < $num_names; $i++) {
      $form['names_fieldset']['name'][$i] = [
        '#type' => 'textfield',
        '#title' => $this->t('Name'),
      ];
    }

    $form['names_fieldset']['actions'] = [
      '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
      '#type' => 'submit',
      '#value' => $this->t('Add one more'),
      '#submit' => ['::addOne'],
      '#ajax' => [
        'callback' => '::addmoreCallback',
        'wrapper' => 'names-fieldset-wrapper',
      ],
    ];
    // If there is more than one name, add the remove button.
    if ($num_names > 1) {
      $form['names_fieldset']['actions']['remove_name'] = [
        '#type' => 'submit',
        '#value' => $this->t('Remove one'),
        '#submit' => ['::removeCallback'],
        '#ajax' => [
          'callback' => '::addmoreCallback',
          'wrapper' => 'names-fieldset-wrapper',
        ],
      ];
    }
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'form_api_example_ajax_addmore';
  }

  /**
   * Callback for both ajax-enabled buttons.
   *
   * Selects and returns the fieldset with the names in it.
   */
  public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    return $form['names_fieldset'];
  }

  /**
   * Submit handler for the "add-one-more" button.
   *
   * Increments the max counter and causes a rebuild.
   */
  public function addOne(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    $add_button = $name_field + 1;
    $form_state->set('num_names', $add_button);
    // Since our buildForm() method relies on the value of 'num_names' to
    // generate 'name' form elements, we have to tell the form to rebuild. If we
    // don't do this, the form builder will not call buildForm().
    $form_state->setRebuild();
  }

  /**
   * Submit handler for the "remove one" button.
   *
   * Decrements the max counter and causes a form rebuild.
   */
  public function removeCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    if ($name_field > 1) {
      $remove_button = $name_field - 1;
      $form_state->set('num_names', $remove_button);
    }
    // Since our buildForm() method relies on the value of 'num_names' to
    // generate 'name' form elements, we have to tell the form to rebuild. If we
    // don't do this, the form builder will not call buildForm().
    $form_state->setRebuild();
  }

  /**
   * Final submit handler.
   *
   * Reports what values were finally set.
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue(['names_fieldset', 'name']);

    $output = $this->t('These people are coming to the picnic: @names', [
      '@names' => implode(', ', $values),
    ]
    );
    $this->messenger()->addMessage($output);
  }
}

```

Source: https://api.drupal.org/api/examples/form\_api\_example%21src%21Form%21AjaxAddMore.php/class/AjaxAddMore/8.x-1.x

# Answer
> 0 votes
Tri this add more field with remove button in custom form, I have updated. and work for me.

```
  use Drupal\Core\Form\FormBase;
  use Drupal\Core\Form\FormStateInterface;

  public function buildForm(array $form, FormStateInterface $form_state) {
    // Build your form here.
    // Add a wrapper for dynamic fields.
    $form['field_wrapper'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'field-wrapper'],
      '#tree' => TRUE, // Separate values for each field.
    ];

    // Add an "Add Field" button.
    $form['add_field'] = [
      '#type' => 'submit',
      '#value' => t('Add Field'),
      '#submit' => ['::addField'],
      '#ajax' => [
        'callback' => '::addFieldsAjaxCallback',
        'wrapper' => 'field-wrapper',
      ],
    ];

    // Check if there are fields in the form_state and add them.
    $fields = $form_state->get('num_fields') ?: 1;
    for ($i = 0; $i < $fields; $i++) {
      $form['field_wrapper'][$i] = [
        '#type' => 'container',
        '#attributes' => ['class' => ['field-container']],
      ];
      $form['field_wrapper'][$i]['field'] = [
        '#type' => 'textfield',
        '#title' => t('Field @index', ['@index' => $i + 1]),
        '#default_value' => $form_state->getValue(['field_wrapper', $i, 'field']),
      ];

      // Add a "Remove" button for each field.
      $form['field_wrapper'][$i]['remove_field'] = [
        '#type' => 'submit',
        '#value' => t('Remove Field'),
        '#submit' => ['::removeField'],
        '#name' => 'remove_field_' . $i,
        '#ajax' => [
          'callback' => '::addFieldsAjaxCallback',
          'wrapper' => 'field-wrapper',
        ],
        '#attributes' => ['class' => ['remove-field-button']],
        '#field_index' => $i, // Pass field index to submit handler.
        '#limit_validation_errors' => [
          ['field_wrapper', $i, 'field'],
        ],
      ];

      // Use #prefix and #suffix to hide or show the field based on removal.
      if ($form_state->get(['field_wrapper', $i, 'field_removed'])) {
        $form['field_wrapper'][$i]['#access'] = FALSE;
      }
    }

    return $form;
  }

  public function addField(array &$form, FormStateInterface $form_state) {
    $fields = $form_state->get('num_fields') ?: 1;
    $form_state->set('num_fields', $fields + 1);
    $form_state->setRebuild();
  }

  public function removeField(array &$form, FormStateInterface $form_state) {
    $triggering_element = $form_state->getTriggeringElement();
    $field_index = $triggering_element['#field_index'];

    // Debugging: Check if removeField is called and gets the correct field index.
    \Drupal::logger('my_custom_module')->debug('Remove field called for index: ' . $field_index);

    // Mark the field as removed in the form state.
    $form_state->set(['field_wrapper', $field_index, 'field_removed'], TRUE);

    // Rebuild the form.
    $form_state->setRebuild();
  }

  public function addFieldsAjaxCallback(array &$form, FormStateInterface $form_state) {
    return $form['field_wrapper'];
  }

```

---
Tags: forms, 8, ajax
---


# Question
Title: How to override a service provided by a contrib module?
I need to override Validator class from Telephone Validations contrib module. I tried the both static and dynamic methods described here: https://www.drupal.org/docs/8/api/services-and-dependency-injection/altering-existing-services-providing-dynamic-services

```
namespace Drupal\mymodule;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
// @note: You only need Reference, if you want to change service arguments.
use Symfony\Component\DependencyInjection\Reference;

/**
 * Modifies the language manager service.
 */

class MyServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    // Overrides language_manager class to test domain language negotiation.
    // Adds entity_type.manager service as an additional argument.
    $definition = $container->getDefinition('telephone_validation.validator');
    $definition->setClass('Drupal\mymodule\MyValidator');

    $definition->addArgument(new Reference('country_manager'));
  }

}

```

and also static:

```
services:
  telephone_validation.validator:
    class: Drupal\mymodule\MyValidator
    arguments: ['@country_manager']

```

but it the both methods do not work, Drupal still uses Validator class form oroginal module

Also I tried modified method described here: https://github.com/RikdeBoer/service\_overrider

```
class MyServiceProvider implements ServiceModifierInterface {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    // Overrides language_manager class to test domain language negotiation.
    // Adds entity_type.manager service as an additional argument.
    $definition = $container->getDefinition('telephone_validation.validator');
    $definition->setClass('Drupal\mymodule\MyValidator');

    $definition->addArgument(new Reference('country_manager'));
  }

}

```

The result is the same: MyValidator class is not used instead of default Validator class. So what I am doing wrong?

# Answer
The name of your Class must be **MyModuleServiceProvider**. You can the documentation here: Altering existing services, providing dynamic services

> Note that if you want this service alteration to be recognized automatically, ***the name of this class is required to be a PascalCase version of your module's machine name followed by ServiceProvider,*** it is required to be in your module's top-level namespace Drupal\your\_module, and it must implement \Drupal\Core\DependencyInjection\ServiceModifierInterface (which ServiceProviderBase does).

This must solve your issue.
> 0 votes

---
Tags: 8, symfony-services
---


# Question
Title: HTML style attributes have a prefix in their values after changing directory and settings
I am a sysadmin with some experience in PHP and databases, but a complete Newbie to Drupal.

I have a Drupal site I am hosting but it needed to be moved away from the web root to host a new website altogether. So I moved it from public\_html to public\_html/archived/. While doing this I updated the htaccess file and drupal's settings.php to make sure references to the root directory / are replaced with /archived/ (including example.com to example.com/archived/. The website loaded but several images and styles were missing. Inspecting the rendered HTML showed that all the style attributes in different tags got prepended by /archived/. So something like:

`<div style="width:100%">` became `<div style="/archived/width:100%">`

I have never seen this kind of behavior. And I am not sure if this was because of the changes in htaccess or settings.php, or if there is some theme level code doing this.

Just to try my luck, I moved the website from /public\_html/archived to /archived/ and pointed a subdomain archived.example.com to it. I updated htaccess and settings.php again. The same issue happened with the style attribute

`<div style="/width:100%">`

Here are the changes I am doing in .htaccess

```
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://archived.example.com/$1 [L,R=301]

RewriteBase /

```

And here is what I am updating in /sites/default/settings.php

```
$settings['trusted_host_patterns'] = [
   '^archived\.example\.com$'
];

```

There is also this in settings.php

```
$settings['file_public_base_url'] = 'https://archived.example.com/';

```

But I have commented it out right now.

# Answer
> While doing this I updated the htaccess file and drupal's settings.php to make sure references to the root directory

There is nothing to do in these files or others to change the directory from where Drupal runs. Just move the files, adjust the web server's vHost config, and clear the cache. The recommended composer project contains a web subfolder, for security reasons you should only publish this subfolder and not the composer root directory.

If the style attributes get overridden, this is not normal Drupal behavior, as @Clive mentioned. This is probably a previous attempt to manipulate the directory from where Drupal runs.
> 0 votes

---
Tags: 8, theming, migrations
---


# Question
Title: Twig LoaderError: Template is not defined
When I try to edit a page to upload 2 documents, I get an error.

Procedure: I edit the node, upload the first document. Then I insert a new field to upload the second document. However, the second field does not appear and I get the following error message in dblog:

> "Twig\Error\LoaderError: Template "themes/bootstrap\_sbv/layout/sbv-personenseite-layout.html.twig" is not defined. in Twig\Loader\ChainLoader-\>getCacheKey() (line 10 of webpath/docroot/modules/ds/templates/ds-entity-view.html.twig).

I have tried to include the layout in the "ds-entity-view.html.twig" file.

```
{% include "/themes/bootstrap_sbv/layouts/sbv-personenseite-layout.html.twig" %}

```

But it doesn't work.

Curiously, everything works on the local environment. Only on the test and production environment I get the error. I have a custom theme on my Drupalsite.

# Answer
Moving the templates to the Templates folder `themes/THEMENAME/templates` has solved the problem. I also had to change the path in the `THEMENAME.layouts.yml`
> 4 votes

# Answer
Pay attention to `@my_theme` syntax it refers to `themes/my_theme/templates` folder while the desired template is located into `themes/my_theme/layouts` folder.

So what I suggest is to use a relative path to include the template, something like that:

`{% include '../../../themes/bootstrap_sbv/layouts/sbv-personenseite-layout.html.twig' %}`
> 1 votes

# Answer
I came across this error when I wanted to use a database state, which was created with Version 10.2.2 and my Drupal instance was on 10.1.2.

Updating fixed the issue.
> 0 votes

---
Tags: theming
---


# Question
Title: How to disable ampersand encoding in {{ attributes }}
Is it possible to disable the ampersand encoding when rendering `<div {{ attributes }}>` in a hook (preferred) or Twig?

Background:
Drupal by default escapes ampersands `&` into `&`. This usually is a good thing, but I've the edge case where I need to render one-off TailwindCSS class names like `[&_.child-of-this-div]:text-red`. Looks weird, but is actually a completely valid CSS class name.

But due the ampersand encoding, the rendered output now looks like `<div class="[&_.child-of-this-div]:text-red">`, which by CSS rules is now a completely different class name, and Tailwind's parser therefore no longer "sees" the real class name `[&_.child-of-this-div]:text-red`.

Already tried, did not work:

* Backslash escaping `[\&_.child-of-this-div]:text-red` (still incorrect output)
* using CSS symbol encoding `[\000026_.child-of-this-div]:text-red` (does not work with Tailwind's parser).
* using the raw filter: `{{ attributes|raw }}` (still incorrect output)

# Answer
> 0 votes
The combination of both, `|replace` **and** `|raw`, does remove the encoding.

```
<div {{ attributes|replace({'&': '&'})|raw }}>

```

I needed data-attributes to remain untouched, the closest I got so far is

```
<div class="{{ attributes.class|replace({'&': '&'})|raw }}" {{ attributes|without('class') }}>

```

but this is tedious and unsafe.

I'm still looking for something that does not use `|raw`, so I leave the question open.

---
Tags: theming
---


# Question
Title: Prevent ajax submit function being called when it fails validation
I have a form, that when it's submitted, should be validated, and if valid, the results of that form should then appear below the form.

This works fine without AJAX:

```
function _form_validate($form, &$form_state) {
  if (!is_numeric($form_state['vsalues']['zip'])) {
    form_set_error('zip', 'Zip must be number');
  }
}

function _form_submit($form, &$form_state) {
    $values = $form_state['values'];
    $html = "<p>zip: ". $values['zip'] . "</p>";
    $form_state['storage'] = $html;
    $form_state['rebuild'] = TRUE;
 }

```

and then this gets printed out my main form in

```
  $form['output'] = array(
    '#markup' => (isset($form_state['storage']))?$form_state['storage']:'',
  );

```

If I make it an AJAX form, and change the submit function to:

```
function _form_submit($form, &$form_state) {
    $values = $form_state['values'];
    $html = "<p>zip: ". $values['zip'] . "</p>";
    $form_state['storage'] = $html;
    $form_state['rebuild'] = FALSE;
    return array("#markup" => $html);
}

```

It works fine apart from the fact the submit callback is triggered even when validation fails (e.g. passing a non-numeric value shows both a validation error yet also prints "zip: ").

What am I doing wrong? How can I stop the submit function being called?

# Answer
> 1 votes
Only thing you should have to put in your AJAX submit callback is this

```
  if (!form_get_errors()) {
    // Do your processing
  }
  else {
    // Return your form with errors.
   return $form;
  }

```

# Answer
> 0 votes
I admitedly couldn't find an elegant solution for this but I found a decent workaround that solved my problem.

In my validation callback:

```
// Track errors and submissions ourselves.
$form_state['form_has_errors'] = FALSE;
$form_state['form_was_submitted'] = FALSE;

if ($condition) {
  form_set_error($form['fieldname'], t('Error message'));
  $form_state['form_has_errors'] = TRUE;
}

```

In the submit callback:

```
// Check for our custom error flag.
// For some reason form_get_errors() is empty, even when validation errors ocurred.
if (!$form_state['form_has_errors']) {
  // Put your submit callback code here.

  $form_state['form_was_submitted'] = TRUE;
}

```

In the AJAX callback:

```
if ($form_state['form_was_submitted']) {
  // Do something different here.
}
else {
  // For has errors and wasn't submitted.
}

```

Again, this is admittedly NOT an elegant solution but I couldn't find any other way to make Drupal not call the submit callback if errors were found.

# Answer
> 0 votes
Try to put it in `hook_node_validate`. if your form is built using content type then it will definitely help you.

Another way might help you like, check it in `hook_node_presave()` and make exit() call if your validation got fails.

# Answer
> 0 votes
Its an old issue, but i just worked on the same topic:

```
  // prevent default submit if form does not validate
  $('form').find('input[type="submit"]').on('click', function (e) {
    var $form = $(this).closest('form');
    if ( !($form[0].checkValidity && $form[0].checkValidity())) {
      e.preventDefault();
    }
  });

```

This is using the browser form validation which i used in a Drupal.behaviors function.

---
Tags: 7, ajax, forms
---


# Question
Title: How to prevent an Ajax form from submitting
The below code blocks any non-ajax form from submitting.

```
$(this.form)
  .on('submit', function(event) {
    if (/* Some condition */) {
      event.preventDefault();
    }
  });

```

In Drupal 8, how can we block an Ajax form from being submitted?

Ajax forms do not trigger a form submit handler and I have not been able to block the click event on an Ajaxified form's submit buttons.

For reference, I am trying to fix Issue #3010084: Form submission finishes before file upload is complete

# Answer
I found the solution here.

```
// Add submit handler to form.beforeSend.
// Update Drupal.Ajax.prototype.beforeSend only once.
if (typeof Drupal.Ajax !== 'undefined' && typeof Drupal.Ajax.prototype.beforeSubmitOriginal === 'undefined') {
  Drupal.Ajax.prototype.beforeSubmitOriginal = Drupal.Ajax.prototype.beforeSubmit;
  Drupal.Ajax.prototype.beforeSubmit = function (form_values, element_settings, options) {
    if (/* Custom condition */) {
      this.ajaxing = false;
      return false;
    }
    return this.beforeSubmitOriginal();
  };
}

```
> 16 votes

# Answer
Preventing a form submit while the form ajax handler is ongoing

```
   $(document).ajaxComplete(function(){
    $('#edit-next').removeAttr("disabled");
  });

  /**
   * prevent submit in case of ongoing ajax update
   */
  $(document).ajaxStart(function(event){
    event.preventDefault();
    $("#edit-next").attr("disabled", "disabled");
  } );

```
> 0 votes

---
Tags: 8, forms, ajax, webforms
---


# Question
Title: Search API Solr: Correct Field Data Type?
I'm running Drupal core 10.2.1, Search API Solr 4.3.1, and Solr server 9.4.0. I've noticed that some searches aren't finding indexed content and I'm wondering if the field type definition is correct. The data types can be set at /admin/config/search/search-api/index/myindex/fields.

The field in question is a string that includes hyphens, such as "2B257-Kit-1". A search for "2B257" will find content, but searches for "2B257-Kit" and "2B257-Kit-1" return no results. I've tried multiple data types without success. How can I match these values to return appropriate search results?

# Answer
> 1 votes
This seems relevant and could be the cause of your issue: Products containing a hyphen or numbers not shown or wrong relevance

In my experience we have needed to use the type "Fulltext ngram" for partial matching. Make sure you are reindexing your content after each field type change as well.

# Answer
> 0 votes
I found a working combination of settings: after setting the Search API Solr field type to "fulltext ngramstring" and the views fulltext search parse mode in the views filter to "multiple words" I'm able to return results for a substring-matching query as described above.

---
Tags: search
---


# Question
Title: Can't find "starts with" for Filter criteria In Views (indexed field for taxonomy)
I created a view using indexed fields (search api) to list a taxonomy. I used the rest export checkbox so I have an api endpoint for my view. I am not able to find a filter criteria "starts with" for a field (indexed) that is a string. I don't understand why, the field is behaving like an integer and showing greater than, less than etc..

I am trying to use the hook\_views\_query\_alter but I am getting an error from the line $query-\>where :

```
Error: Cannot access protected property Drupal\search_api\Plugin\views\query\SearchApiQuery::$where

```

In my view , I can see in the "Preview" section the query :

```
Index: books
Keys: NULL
Options: array (
    'search_api_view' => 'object (Drupal\\views\\ViewExecutable)',
    'search_api_base_path' => 'api/books',
  )

```

How do I add a filter criteria "starts with" for an indexed field which is a string? Thanks for your help

# Answer
See the full thread linked by @misterdidi here: https://www.drupal.org/project/search\_api/issues/3114833#comment-13528391 This is the gist of it:

> If it’s “starts with” you are interested in, you could conceivably emulate this using “greater than”: field STARTS WITH 'abc' is pretty much the same as field \>= 'abc' AND field \< 'abd' (unless the field is multi-valued – then you’d have to use IS BETWEEN

If you're using Solr index fields, this field has to be of type string (not fulltext). The letter to be filtered on can be set on the url parameters: `url.tld?initial=a` for example.

You can implement this filter using `hook_views_query_alter`. Here "surname" is the machine name of the field as set in solr index fields.

```
function my_module_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if ($view->id() === 'my_view') {
    // Get filter value from URL.
    $initial = strtoupper(\Drupal::request()->query->get('initial'));
    if (!empty($initial)) {
      $query->addWhere(0, 'surname', $initial, '>=');
      $query->addWhere(0, 'surname', ++$initial, '<');
    }
  }
}

```
> 0 votes

---
Tags: views, 9
---