Programatically add recaptcha to Drupal Commerce checkout form

Components

If you are using the UI you can add a Recaptcha only to the entire checkout form, and that usually is not what you want. Fortunately, adding a Recaptcha to a certain step is easy:

use Drupal\Core\Form\FormStateInterface;

function MY_MODULE_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form_id == 'commerce_checkout_flow_multistep_default' && $form['#step_id'] == 'order_information') {
    $form['captcha'] = [
      '#type' => 'captcha',
      '#captcha_type' => 'recaptcha/reCAPTCHA',
    ];
  }
}

Add a role on user create

Components

In some cases, you may want to add a role when a new user is created regardless of how the user is created (UI, GraphQL, Rest API, or something else). The hook_ENTITY_TYPE_presave() hook is perfect for this.

use Drupal\user\UserInterface;

function MY_MODULE_user_presave(UserInterface $user) {
  if ($user->isNew() && !$user->hasRole('frontend_app')) {
    $user->addRole('frontend_app');
  }
}

Add a menu link to existing menu

Components

Adding a menu link to some existing menu programmatically (for example in an update hook) is something I have to do fairly often. Here's how you can add a new menu link to the main menu:

use Drupal\menu_link_content\Entity\MenuLinkContent;

function MY_MODULE_update_8001() {
  MenuLinkContent::create([
    'title' => 'About',
    'link' => ['uri' => 'internal:/about'],
    'menu_name' => 'main',
    'weight' => -50,
  ])->save();
}

And if you want to add a submenu link you first have to load the parent link and then create a submenu link:

use Drupal\menu_link_content\Entity\MenuLinkContent;

function MY_MODULE_update_8001() {
  $menu_link_parents = \Drupal::entityTypeManager()
    ->getStorage('menu_link_content')
    ->loadByProperties([
      'title' => 'Dashboard',
      'menu_name' => 'main',
    ]);

  $menu_link_parent = reset($menu_link_parents);

  if ($menu_link_parent) {
    MenuLinkContent::create([
      'title' => 'Add tasks',
      'link' => ['uri' => 'internal:/add-task'],
      'menu_name' => 'main',
      'weight' => -50,
      'parent' => $menu_link_parent->getPluginId(),
    ])->save();
  }
}

Render a menu in a form

Components

Rendering a menu in a form requires a little bit more lines of code than rendering a View, but it is also very easy thanks to the menu.link_tree service. In the following example, we are rendering the main menu.

use Drupal\Core\Menu\MenuTreeParameters;

$menu_parameters = new MenuTreeParameters();
$manipulators = [
  ['callable' => 'menu.default_tree_manipulators:checkNodeAccess'],
  ['callable' => 'menu.default_tree_manipulators:checkAccess'],
  ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
];
$tree = \Drupal::menuTree()->load('main', $menu_parameters);
$tree = \Drupal::menuTree()->transform($tree, $manipulators);

$form['content'] = \Drupal::menuTree()->build($tree);

Render a View in a form

Components

Rendering Views in a form is super easy thanks to the buildRenderable() method, which builds the render array for the given display. To render a View, you just need to know the machine name of the View and display ID (usually page_1 or block_1). To render the Content View do this:

use Drupal\views\Views;
$view = Views::getView('content');
$form['content'] = $view->buildRenderable('page_1');

Get the list of fields for an entity type

Components

To get the field definitions of any entity type use the following code. We first get all bundles, and then for each bundle we are getting the field definitions.

$bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('node');

foreach ($bundles as $bundle => $data) {
  $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', $bundle);
}

Add CSS and JS to Admin pages

Components

Adding CSS or JS to admin pages is easy. Just create a new library, create CSS and JS files, and then attach the library in the hook_page_attachments() hook.

MY_MODULE.libraries.yml

admin_styling:
  version: VERSION
  css:
    theme:
      css/admin-styling.css: {}
  js:
    js/admin-script.js: {}

MY_MODULE.module

/**
 * Implements hook_page_attachments().
 */
function MY_MODULE_page_attachments(array &$attachments) {
  if (\Drupal::service('router.admin_context')->isAdminRoute()) {
    $attachments['#attached']['library'][] = 'MY_MODULE/admin_styling';
  }
}

Hide generator metatag

Components

If you want to hide the generator metatag that exposes information about your Drupal version then you can use the hook_page_attachments_alter() hook. This applies only for versions 8 and 9!

/**
 * Implements hook_page_attachments_alter().
 */
function MY_MODULE_page_attachments_alter(array &$attachments) {
  foreach ($attachments['#attached']['html_head'] as $key => $attachment) {
    if (isset($attachment[1]) && $attachment[1] == 'system_meta_generator') {
      unset($attachments['#attached']['html_head'][$key]);
    }
  }
}

Ajax form element callback

Components

To save the API token field without submitting the form, you can add a simple ajax callback function. In this example, we are saving the field value on the keyup event, but you can also use the change event or if you have an autocomplete field then you probably want to use the autocompleteclose event.

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Form\FormStateInterface;

public function buildForm(array $form, FormStateInterface $form_state) {
  $form['api_token'] = [
    '#type' => 'textfield',
    '#title' => $this->t('API token'),
    '#ajax' => [
      'callback' => '::updateApiToken',
      'event' => 'keyup',
    ],
  ];

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

public function updateApiToken(array $form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  $this->config('MY_MODULE.settings')
    ->set('api_token', $form_state->getValue('api_token'))
    ->save();
  return $response;
}

Remove an item from entity reference field

Components

Let's say that you have a multivalue reference field named field_tags in a node type. To remove a specific item from that field you can do something like this:

$node_id = 152;
$tag_to_remove = 56;
$entity_storage = \Drupal::entityTypeManager()->getStorage('node');
$entity = $entity_storage->load($node_id);

foreach ($entity->get('field_tags') as $delta => $field_item) {
  if ($field_item->target_id == $tag_to_remove) {
    $entity->get('field_tags')->removeItem($delta);
    $entity->save();
    break;
  }
}