diff --git a/knowledge.module b/knowledge.module
index 7638164..aef9c76 100644
--- a/knowledge.module
+++ b/knowledge.module
@@ -10,11 +10,10 @@
  * collaborative book page, user etc.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge\Hook\KnowledgeHooks;
 use Drupal\Component\Utility\DeprecationHelper;
-use Drupal\Component\Utility\Html;
-use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\Core\Entity\Entity\EntityViewMode;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -26,15 +25,11 @@ use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
-use Drupal\Core\Utility\Error;
 use Drupal\field\FieldConfigInterface;
 use Drupal\field\FieldStorageConfigInterface;
-use Drupal\knowledge\Entity\KnowledgeType;
 use Drupal\knowledge\KnowledgeInterface;
 use Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItemInterface;
-use Drupal\knowledge\Service\SequenceService;
 use Drupal\node\NodeInterface;
-use Drupal\user\RoleInterface;
 use Drupal\user\UserInterface;
 use Drupal\views\ViewExecutable;
 
@@ -51,37 +46,9 @@ use Drupal\views\ViewExecutable;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function knowledge_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.knowledge':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Knowledge module allows users to knowledge on site content, set Knowledge defaults and permissions, and moderate Knowledge. For more information, see the <a href=":knowledge">online documentation for the Knowledge module</a>.', [':knowledge' => 'https://www.drupal.org/documentation/modules/knowledge']) . '</p>';
-      $output .= '<h3>' . t('Uses') . '</h3>';
-      $output .= '<dl>';
-      $output .= '<dt>' . t('Enabling Knowledge') . '</dt>';
-      $output .= '<dd>' . t('Knowledge functionality can be enabled for any entity sub-type (for example, a <a href=":content-type">content type</a>) by adding a <em>Knowledge</em> field on its <em>Manage fields page</em>. Adding or removing knowledge links for an entity through the user interface requires the <a href=":field_ui">Field UI</a> module to be enabled, even though the knowledge functionality works without it. For more information on fields and entities, see the <a href=":field">Field module help page</a>.', [
-        ':content-type' => (\Drupal::moduleHandler()->moduleExists('node')) ? Url::fromRoute('entity.node_type.collection')->toString() : '#',
-        ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString(),
-        ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#',
-      ]) . '</dd>';
-      $output .= '<dt>' . t('Configuring Knowledge settings') . '</dt>';
-      $output .= '<dd>' . t('Knowledge settings can be configured by editing the <em>Knowledge</em> field on the <em>Manage fields page</em> of an entity type if the <em>Field UI module</em> is enabled. Configuration includes the label of the knowledge field, the amount of knowledge to be displayed, and whether they are shown in threaded list. Knowledge can be configured as: <em>Open</em> to allow new knowledge, <em>Closed</em> to view existing knowledge, but prevent new knowledge, or <em>Hidden</em> to hide existing knowledge and prevent new knowledge. Changing this configuration for an entity type will not change existing entity items.') . '</dd>';
-      $output .= '<dt>' . t('Overriding default settings') . '</dt>';
-      $output .= '<dd>' . t('Users with the appropriate permissions can override the default Knowledge settings of an entity type when they create an item of that type.') . '</dd>';
-      $output .= '<dt>' . t('Adding knowledge types') . '</dt>';
-      $output .= '<dd>' . t('Additional <em>knowledge types</em> can be created per entity sub-type and added on the <a href=":field">Knowledge types page</a>. If there are multiple knowledge types available you can select the appropriate one after adding a <em>Knowledge field</em>.', [':field' => Url::fromRoute('entity.knowledge_type.collection')->toString()]) . '</dd>';
-      $output .= '<dt>' . t('Approving and managing knowledge') . '</dt>';
-      $output .= '<dd>' . t('Knowledge from users who have the <em>Skip knowledge approval</em> permission are published immediately. All other Knowledge are placed in the <a href=":knowledge-approval">Unlinked knowledge</a> queue, until a user who has permission to <em>Administer knowledge and knowledge settings</em> publishes or deletes them. Published knowledge can be bulk managed on the <a href=":admin-knowledge">Published knowledge</a> administration page. When a knowledge has no replies, it remains editable by its author, as long as the author has <em>Edit own knowledge</em> permission.', [
-        ':knowledge-approval' => Url::fromRoute('knowledge.admin_approval')->toString(),
-        ':admin-knowledge' => Url::fromRoute('knowledge.admin')->toString(),
-      ]) . '</dd>';
-      $output .= '</dl>';
-      return $output;
-
-    case 'entity.knowledge_type.collection':
-      $output = '<p>' . t('This page provides a list of all knowledge types on the site and allows you to manage the fields, form and display settings for each.') . '</p>';
-      return $output;
-  }
+  return \Drupal::service(KnowledgeHooks::class)->help($route_name, $route_match);
 }
 
 /**
@@ -100,253 +67,97 @@ function knowledge_uri(KnowledgeInterface $knowledge) {
 /**
  * Implements hook_entity_extra_field_info().
  */
+#[LegacyHook]
 function knowledge_entity_extra_field_info() {
-  $return = [];
-  foreach (KnowledgeType::loadMultiple() as $knowledge_type) {
-    $return['knowledge'][$knowledge_type->id()] = [
-      'form' => [
-        'author' => [
-          'label' => t('Author'),
-          'description' => t('Author textfield'),
-          'weight' => -2,
-        ],
-      ],
-    ];
-    $return['knowledge'][$knowledge_type->id()]['display']['links'] = [
-      'label' => t('Links'),
-      'description' => t('Knowledge operation links'),
-      'weight' => 100,
-      'visible' => TRUE,
-    ];
-  }
-
-  return $return;
+  return \Drupal::service(KnowledgeHooks::class)->entityExtraFieldInfo();
 }
 
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function knowledge_theme() {
-  return [
-    'knowledge' => [
-      'render element' => 'elements',
-    ],
-    'field__knowledge' => [
-      'base hook' => 'field',
-    ],
-    'knowledge_adherence_form' => [
-      'render element' => 'form',
-    ],
-    'knowledge_quality' => [
-      'render element' => 'elements',
-    ],
-    'block__social__plugin_id__knowledge_suffa' => [
-      'render element' => 'elements',
-      'base hook' => 'block',
-    ],
-    'knowledge_wave' => ['render element' => 'elements'],
-  ];
+  return \Drupal::service(KnowledgeHooks::class)->theme();
 }
 
 /**
  * Implements hook_ENTITY_TYPE_create() for 'field_config'.
  */
+#[LegacyHook]
 function knowledge_field_config_create(FieldConfigInterface $field) {
-  if ($field->getType() == 'knowledge' && !$field->isSyncing()) {
-    // Assign default values for the field.
-    $default_value = $field->getDefaultValueLiteral();
-    $default_value += [[]];
-    $default_value[0] += [
-      'status' => KnowledgeItemInterface::OPEN,
-      'first_knowledge' => 0,
-      'first_knowledge_timestamp' => 0,
-      'first_knowledge_uid' => 0,
-      'last_knowledge' => 0,
-      'last_knowledge_timestamp' => 0,
-      'last_knowledge_uid' => 0,
-      'total_count' => 0,
-      'long_count' => 0,
-      'medium_count' => 0,
-      'short_count' => 0,
-      'total_citation' => 0,
-      'long_citation' => 0,
-      'medium_citation' => 0,
-      'short_citation' => 0,
-    ];
-    $field->setDefaultValue($default_value);
-  }
+  \Drupal::service(KnowledgeHooks::class)->fieldConfigCreate($field);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_update() for 'field_config'.
  */
+#[LegacyHook]
 function knowledge_field_config_update(FieldConfigInterface $field) {
-  if ($field->getType() == 'knowledge') {
-    // Knowledge field settings also affects the rendering of *knowledge*
-    // entities, not only the *linked* entities.
-    \Drupal::entityTypeManager()->getViewBuilder('knowledge')->resetCache();
-  }
+  \Drupal::service(KnowledgeHooks::class)->fieldConfigUpdate($field);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_insert() for 'field_storage_config'.
  */
+#[LegacyHook]
 function knowledge_field_storage_config_insert(FieldStorageConfigInterface $field_storage) {
-  if ($field_storage->getType() == 'knowledge') {
-    // Check that the target entity type uses an integer ID.
-    $entity_type_id = $field_storage->getTargetEntityTypeId();
-    if (!_knowledge_entity_uses_integer_id($entity_type_id)) {
-      throw new \UnexpectedValueException('You cannot attach a knowledge field to an entity with a non-integer ID field');
-    }
-  }
+  \Drupal::service(KnowledgeHooks::class)->fieldStorageConfigInsert($field_storage);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
  */
+#[LegacyHook]
 function knowledge_field_config_delete(FieldConfigInterface $field) {
-  if ($field->getType() == 'knowledge') {
-    // Delete all knowledge that used by the entity bundle.
-    $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
-    $entity_query->condition('entity_type', $field->getEntityTypeId());
-    $entity_query->condition('field_name', $field->getName());
-    $kids = $entity_query->execute();
-    $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
-    $knowledge_links = $knowledge_storage->loadMultiple($kids);
-    $knowledge_storage->delete($knowledge_links);
-  }
+  \Drupal::service(KnowledgeHooks::class)->fieldConfigDelete($field);
 }
 
 /**
  * Implements hook_node_links_alter().
  */
+#[LegacyHook]
 function knowledge_node_links_alter(array &$links, NodeInterface $node, array &$context) {
-  // Knowledge links are only added to node entity type for backwards
-  // compatibility. Should you require knowledge links for other entity types
-  // you can do so by implementing a new field formatter.
-  // @todo Make this configurable from the formatter. See
-  //   https://www.drupal.org/node/1901110.
-  $knowledge_links = \Drupal::service('knowledge.link_builder')->buildKnowledgeedEntityLinks($node, $context);
-  $links += $knowledge_links;
+  \Drupal::service(KnowledgeHooks::class)->nodeLinksAlter($links, $node, $context);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_view_alter() for node entities.
  */
+#[LegacyHook]
 function knowledge_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
-  if (\Drupal::moduleHandler()->moduleExists('history')) {
-    $build['#attributes']['data-history-node-id'] = $node->id();
-  }
+  \Drupal::service(KnowledgeHooks::class)->nodeViewAlter($build, $node, $display);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter() for field_ui_field_storage_add_form.
  */
+#[LegacyHook]
 function knowledge_form_field_ui_field_storage_add_form_alter(&$form, FormStateInterface $form_state) {
-  $route_match = \Drupal::routeMatch();
-  if ($form_state->get('entity_type_id') == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
-    $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
-  }
-  if (!_knowledge_entity_uses_integer_id($form_state->get('entity_type_id'))) {
-    $optgroup = (string) t('General');
-    // You cannot use knowledge fields on entity types with non-integer IDs.
-    unset($form['add']['new_storage_type']['#options'][$optgroup]['knowledge']);
-  }
+  \Drupal::service(KnowledgeHooks::class)->formFieldUiFieldStorageAddFormAlter($form, $form_state);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function knowledge_form_field_ui_form_display_overview_form_alter(&$form, FormStateInterface $form_state) {
-  $route_match = \Drupal::routeMatch();
-  if ($form['#entity_type'] == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
-    $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
-  }
+  \Drupal::service(KnowledgeHooks::class)->formFieldUiFormDisplayOverviewFormAlter($form, $form_state);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function knowledge_form_field_ui_display_overview_form_alter(&$form, FormStateInterface $form_state) {
-  $route_match = \Drupal::routeMatch();
-  if ($form['#entity_type'] == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
-    $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
-  }
+  \Drupal::service(KnowledgeHooks::class)->formFieldUiDisplayOverviewFormAlter($form, $form_state);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function knowledge_form_node_form_alter(&$form, $form_state) {
-  if (array_key_exists('knowledge_audience', $form)) {
-    $form['knowledge_audience']['#group'] = 'meta';
-    $form['knowledge_audience']['widget']['#wrapper_attributes'] = [
-      'class' => ['container-inline'],
-    ];
-  }
-  if (array_key_exists('status', $form)) {
-    $form['status']['#group'] = 'meta';
-  }
-  if (array_key_exists('moderation_state', $form)) {
-    $form['moderation_state']['#group'] = 'meta';
-  }
-
-  if (array_key_exists('knowledge_governance', $form) && \Drupal::currentUser()->hasPermission('edit knowledge governance')) {
-    $form['#validate'][] = '_knowledge_governance_form_validate';
-    $entity = $form_state->getFormObject()->getEntity();
-
-    $knowledge_governance = (bool) $entity->get('knowledge_governance')->value;
-    $form['governance'] = [
-      '#type' => 'details',
-      '#title' => t('Governance'),
-      // Open the details when the selected value is different to the stored
-      // default values for the field.
-      '#open' => ($knowledge_governance),
-      '#group' => 'advanced',
-      '#attributes' => [
-        'class' => ['knowledge-governance-' . Html::getClass($entity->getEntityTypeId()) . '-settings-form'],
-      ],
-      '#attached' => [
-        // 'library' => ['knowledge/drupal.knowledge'], create library.
-      ],
-    ];
-    $form['knowledge_governance']['#group'] = 'governance';
-    if (array_key_exists('knowledge_governance_user', $form)) {
-      $form['knowledge_governance_user']['#group'] = 'governance';
-      $form['knowledge_governance_user']['#states']['visible'] = [
-        ':input[name="knowledge_governance"]' => ['value' => 1],
-      ];
-
-    }
-    if (array_key_exists('knowledge_governance_user_role', $form)) {
-      $governance_settings = \Drupal::config('knowledge.governance.settings');
-      $roles = $governance_settings->get('roles');
-      $roles = array_filter($roles);
-
-      $form['knowledge_governance_user_role']['#group'] = 'governance';
-      $form['knowledge_governance_user_role']['#states']['visible'] = [
-        ':input[name="knowledge_governance"]' => ['value' => 1],
-      ];
-
-      $roles_remove = [];
-      $role_options = $form['knowledge_governance_user_role']['widget']['#options'] ?? [];
-      foreach ($role_options as $role => $role_label) {
-
-        $remove = !empty($roles) && !in_array($role, $roles);
-        if ($role == '_none') {
-          $remove = TRUE;
-        }
-        if ($remove) {
-          $roles_remove[] = $role;
-        }
-      }
-
-      foreach ($roles_remove as $role) {
-        unset($form['knowledge_governance_user_role']['widget']['#options'][$role]);
-      }
-    }
-
-  }
+  \Drupal::service(KnowledgeHooks::class)->formNodeFormAlter($form, $form_state);
 }
 
 /**
@@ -374,75 +185,26 @@ function _knowledge_governance_form_validate(&$form, $form_state) {
  *
  * @see \Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItem::propertyDefinitions()
  */
+#[LegacyHook]
 function knowledge_entity_storage_load($entities, $entity_type) {
-  // Knowledge can only be attached to content entities, so skip others.
-  if (!\Drupal::entityTypeManager()->getDefinition($entity_type)->entityClassImplements(FieldableEntityInterface::class)) {
-    return;
-  }
-  if (!\Drupal::service('knowledge.manager')->getFields($entity_type)) {
-    // Do not query database when entity has no knowledge fields.
-    return;
-  }
-  // Load knowledge information from the database and update the entity's
-  // knowledge statistics properties, which are defined on each KnowledgeItem
-  // field.
-  $result = \Drupal::service('knowledge.statistics')->read($entities, $entity_type);
-  foreach ($result as $record) {
-    // Skip fields that entity does not have.
-    if (!$entities[$record->entity_id]->hasField($record->field_name)) {
-      continue;
-    }
-    $knowledge_statistics = $entities[$record->entity_id]->get($record->field_name);
-    $knowledge_statistics->first_knowledge = $record->first_knowledge;
-    $knowledge_statistics->first_knowledge_timestamp = $record->first_knowledge_timestamp;
-    $knowledge_statistics->first_knowledge_uid = $record->first_knowledge_uid;
-    $knowledge_statistics->last_knowledge = $record->last_knowledge;
-    $knowledge_statistics->last_knowledge_timestamp = $record->last_knowledge_timestamp;
-    $knowledge_statistics->last_knowledge_uid = $record->last_knowledge_uid;
-    $knowledge_statistics->total_count = $record->total_count;
-    $knowledge_statistics->long_count = $record->long_count;
-    $knowledge_statistics->medium_count = $record->medium_count;
-    $knowledge_statistics->short_count = $record->short_count;
-    $knowledge_statistics->total_citation = $record->total_citation;
-    $knowledge_statistics->long_citation = $record->long_citation;
-    $knowledge_statistics->medium_citation = $record->medium_citation;
-    $knowledge_statistics->short_citation = $record->short_citation;
-  }
+  \Drupal::service(KnowledgeHooks::class)->entityStorageLoad($entities, $entity_type);
 }
 
 /**
  * Implements hook_entity_insert().
  */
+#[LegacyHook]
 function knowledge_entity_insert(EntityInterface $entity) {
-  // Allow bulk updates and inserts to temporarily disable the
-  // maintenance of the {knowledge_entity_statistics} table.
-  if (\Drupal::state()->get('knowledge.maintain_entity_statistics') &&
-    $fields = \Drupal::service('knowledge.manager')->getFields($entity->getEntityTypeId())) {
-    \Drupal::service('knowledge.statistics')->create($entity, $fields);
-  }
+  \Drupal::service(KnowledgeHooks::class)->entityInsert($entity);
 
 }
 
 /**
  * Implements hook_entity_predelete().
  */
+#[LegacyHook]
 function knowledge_entity_predelete(EntityInterface $entity) {
-  // Entities can have non-numeric IDs, but {knowledge} and
-  // {knowledge_entity_statistics} tables have integer columns for entity ID,
-  // and PostgreSQL throws exceptions if you attempt query conditions with
-  // mismatched types. So, we need to verify that the ID is numeric (even for an
-  // entity type that has an integer ID, $entity->id() might be a string
-  // containing a number), and then cast it to an integer when querying.
-  if ($entity instanceof FieldableEntityInterface && is_numeric($entity->id())) {
-    $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
-    $entity_query->condition('entity_id', (int) $entity->id());
-    $entity_query->condition('entity_type', $entity->getEntityTypeId());
-    $kids = $entity_query->execute();
-    $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
-    $knowledge_links = $knowledge_storage->loadMultiple($kids);
-    $knowledge_storage->delete($knowledge_links);
-    \Drupal::service('knowledge.statistics')->delete($entity);
-  }
+  \Drupal::service(KnowledgeHooks::class)->entityPredelete($entity);
 }
 
 /**
@@ -469,85 +231,17 @@ function _knowledge_entity_uses_integer_id($entity_type_id) {
 /**
  * Implements hook_node_update_index().
  */
+#[LegacyHook]
 function knowledge_node_update_index(EntityInterface $node) {
-  $index_knowledge = &drupal_static(__FUNCTION__);
-
-  if ($index_knowledge === NULL) {
-    // Do not index in the following three cases:
-    // 1. 'Authenticated user' can search content but can't access knowledge.
-    // 2. 'Anonymous user' can search content but can't access knowledge.
-    // 3. Any role can search content but can't access knowledge and access
-    // knowledge is not granted by the 'authenticated user' role. In this case
-    // all users might have both permissions from various roles but it is also
-    // possible to set up a user to have only search content and so a user
-    // edit could change the security situation so it is not safe to index the
-    // knowledge.
-    $index_knowledge = TRUE;
-    $roles = \Drupal::entityTypeManager()->getStorage('user_role')->loadMultiple();
-    $authenticated_can_access = $roles[RoleInterface::AUTHENTICATED_ID]->hasPermission('access knowledge');
-    foreach ($roles as $rid => $role) {
-      if ($role->hasPermission('search content') && !$role->hasPermission('access knowledge')) {
-        if ($rid == RoleInterface::AUTHENTICATED_ID || $rid == RoleInterface::ANONYMOUS_ID || !$authenticated_can_access) {
-          $index_knowledge = FALSE;
-          break;
-        }
-      }
-    }
-  }
-
-  $build = [];
-
-  if ($index_knowledge) {
-    foreach (\Drupal::service('knowledge.manager')->getFields('node') as $field_name => $info) {
-      // Skip fields that entity does not have.
-      if (!$node->hasField($field_name)) {
-        continue;
-      }
-      $field_definition = $node->getFieldDefinition($field_name);
-      $mode = $field_definition->getSetting('default_mode');
-      $knowledge_per_page = $field_definition->getSetting('per_page');
-      if ($node->get($field_name)->status) {
-        /** @var \Drupal\knowledge\KnowledgeStorageInterface $knowledge_storage */
-        $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
-        /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
-        $knowledge_links = $knowledge_storage
-          ->loadThread($node, $field_name, $mode, $knowledge_per_page);
-        if ($knowledge_links) {
-          $build[] = \Drupal::entityTypeManager()->getViewBuilder('knowledge')->viewMultiple($knowledge_links);
-        }
-      }
-    }
-  }
-  return \Drupal::service('renderer')->renderInIsolation($build);
+  return \Drupal::service(KnowledgeHooks::class)->nodeUpdateIndex($node);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function knowledge_cron() {
-  // Store the maximum possible knowledge per thread (used for node search
-  // ranking by reply count).
-  \Drupal::state()->set('knowledge.node_knowledge_statistics_scale', 1.0 / max(1, \Drupal::service('knowledge.statistics')->getMaximumCount('node')));
-  $today = strtotime('today');
-  $statistics_calculated = \Drupal::state()->get('knowledge.knowledge_statistics_calculated', 0);
-  if ($today > $statistics_calculated) {
-    $database = \Drupal::database();
-    $query = $database->query('SELECT last_knowledge
-    FROM knowledge_entity_statistics
-    WHERE last_knowledge <> 0');
-    $results = $query->fetchAll();
-    $ids = [];
-    foreach ($results as $result) {
-      $ids[] = $result->last_knowledge;
-    }
-    $knowledge_links = \Drupal::service('entity_type.manager')
-      ->getStorage('knowledge')
-      ->loadMultiple($ids);
-    foreach ($knowledge_links as $link) {
-      \Drupal::service('knowledge.statistics')->update($link);
-    }
-    \Drupal::state()->set('knowledge.knowledge_statistics_calculated', \Drupal::time()->getRequestTime());
-  }
+  \Drupal::service(KnowledgeHooks::class)->cron();
 }
 
 /**
@@ -556,68 +250,25 @@ function knowledge_cron() {
  * Formats a knowledge count string and returns it, for display with search
  * results.
  */
+#[LegacyHook]
 function knowledge_node_search_result(EntityInterface $node) {
-  /** @var \Drupal\node\NodeInterface $node */
-  $knowledge_fields = \Drupal::service('knowledge.manager')->getFields('node');
-  $knowledge_links = 0;
-  $open = FALSE;
-  foreach ($knowledge_fields as $field_name => $info) {
-    // Skip fields that entity does not have.
-    if (!$node->hasField($field_name)) {
-      continue;
-    }
-    // Do not make a string if knowledge is hidden.
-    $status = $node->get($field_name)->status;
-    if (\Drupal::currentUser()->hasPermission('access knowledge') && $status != KnowledgeItemInterface::HIDDEN) {
-      if ($status == KnowledgeItemInterface::OPEN) {
-        // At least one knowledge field is open.
-        $open = TRUE;
-      }
-      $knowledge_links += $node->get($field_name)->total_count;
-    }
-  }
-  // Do not make a string if there are no knowledge fields, or no knowledge
-  // exist or all knowledge fields are hidden.
-  if ($knowledge_links > 0 || $open) {
-    return ['knowledge' => \Drupal::translation()->formatPlural($knowledge_links, '1 knowledge', '@count knowledge')];
-  }
+  return \Drupal::service(KnowledgeHooks::class)->nodeSearchResult($node);
 }
 
 /**
  * Implements hook_user_cancel().
  */
+#[LegacyHook]
 function knowledge_user_cancel($edit, UserInterface $account, $method) {
-  switch ($method) {
-    case 'user_cancel_block_unpublish':
-      /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
-      $knowledge_links = \Drupal::entityTypeManager()->getStorage('knowledge')->loadByProperties(['uid' => $account->id()]);
-      foreach ($knowledge_links as $know) {
-        $know->setUnpublished();
-        $know->save();
-      }
-      break;
-
-    case 'user_cancel_reassign':
-      /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
-      $knowledge_links = \Drupal::entityTypeManager()->getStorage('knowledge')->loadByProperties(['uid' => $account->id()]);
-      foreach ($knowledge_links as $know) {
-        $know->setOwnerId(0);
-        $know->save();
-      }
-      break;
-  }
+  \Drupal::service(KnowledgeHooks::class)->userCancel($edit, $account, $method);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete() for user entities.
  */
+#[LegacyHook]
 function knowledge_user_predelete($account) {
-  $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
-  $entity_query->condition('uid', $account->id());
-  $kids = $entity_query->execute();
-  $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
-  $knowledge = $knowledge_storage->loadMultiple($kids);
-  $knowledge_storage->delete($knowledge);
+  \Drupal::service(KnowledgeHooks::class)->userPredelete($account);
 }
 
 /**
@@ -668,10 +319,9 @@ function knowledge_preview(KnowledgeInterface $knowledge, FormStateInterface $fo
 /**
  * Implements hook_preprocess_HOOK() for block templates.
  */
+#[LegacyHook]
 function knowledge_preprocess_block(&$variables) {
-  if (array_key_exists('provider', $variables['configuration']) && $variables['configuration']['provider'] == 'knowledge') {
-    $variables['attributes']['role'] = 'navigation';
-  }
+  \Drupal::service(KnowledgeHooks::class)->preprocessBlock($variables);
 }
 
 /**
@@ -849,61 +499,25 @@ function template_preprocess_knowledge_quality(array &$variables) {
 /**
  * Implements hook_ranking().
  */
+#[LegacyHook]
 function knowledge_ranking() {
-  return \Drupal::service('knowledge.statistics')->getRankingInfo();
+  return \Drupal::service(KnowledgeHooks::class)->ranking();
 }
 
 /**
  * Implements hook_ENTITY_TYPE_presave() for entity_view_display entities.
  */
+#[LegacyHook]
 function knowledge_entity_view_display_presave(EntityViewDisplayInterface $display) {
-  // Act only on knowledge view displays being disabled.
-  if ($display->isNew() || $display->getTargetEntityTypeId() !== 'knowledge' || $display->status()) {
-    return;
-  }
-  $storage = \Drupal::entityTypeManager()->getStorage('entity_view_display');
-  if (!$storage->loadUnchanged($display->getOriginalId())->status()) {
-    return;
-  }
-
-  // Disable the knowledge field formatter when the used view display
-  // is disabled.
-  foreach ($storage->loadMultiple() as $view_display) {
-    /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
-    $changed = FALSE;
-
-    foreach ($view_display->getComponents() as $field => $component) {
-      if (isset($component['type']) && ($component['type'] === 'knowledge_default')) {
-        if ($component['settings']['view_mode'] === $display->getMode()) {
-          $view_display->removeComponent($field);
-          /** @var \Drupal\Core\Entity\EntityViewModeInterface $mode */
-          $mode = EntityViewMode::load($display->getTargetEntityTypeId() . '.' . $display->getMode());
-          $arguments = [
-            '@id' => $view_display->id(),
-            '@name' => $field,
-            '@display' => $mode->label(),
-            '@mode' => $display->getMode(),
-          ];
-          \Drupal::logger('system')->warning("View display '@id': Knowledge field formatter '@name' was disabled because it is using the knowledge view display '@display' (@mode) that was just disabled.", $arguments);
-          $changed = TRUE;
-        }
-      }
-    }
-    if ($changed) {
-      $view_display->save();
-    }
-  }
+  \Drupal::service(KnowledgeHooks::class)->entityViewDisplayPresave($display);
 }
 
 /**
  * Implements hook_library_info_alter().
  */
+#[LegacyHook]
 function knowledge_library_info_alter(&$libraries, $extension) {
-  if ($extension === 'knowledge' && isset($libraries['activity'])) {
-    $libraries['activity']['dependencies'][] = \Drupal::config('moderation_dashboard.settings')->get('chart_js_cdn')
-      ? 'moderation_dashboard/chart.js.external'
-      : 'moderation_dashboard/chart.js.internal';
-  }
+  \Drupal::service(KnowledgeHooks::class)->libraryInfoAlter($libraries, $extension);
 }
 
 /**
@@ -934,235 +548,49 @@ function knowledge_audience_allowed_values(FieldStorageDefinitionInterface $defi
 /**
  * Implements hook_ENTITY_TYPE_access().
  */
+#[LegacyHook]
 function knowledge_node_access(NodeInterface $node, $op, AccountInterface $account) {
-  $user_roles = [];
-  if ($node->hasField('knowledge_governance_user_role') && $node_roles = $node->get('knowledge_governance_user_role')) {
-    foreach ($node_roles as $role) {
-      $user_roles[] = $role->target_id;
-    }
-  }
-
-  $users = [];
-  if ($node->hasField('knowledge_governance_user') && $node_users = $node->get('knowledge_governance_user')) {
-    foreach ($node_users as $user) {
-      $users[] = $user->target_id;
-    }
-  }
-
-  $access = AccessResult::neutral();
-  if (!$node->hasField('knowledge_audience')) {
-    return $access;
-  }
-  $audience = $node->knowledge_audience->value;
-
-  switch ($op) {
-    case 'update':
-      if ($audience != 'internal') {
-        if (!$account->hasPermission('edit knowledge audience external')) {
-          return AccessResult::forbidden();
-        }
-      }
-
-      if ($node->hasField('knowledge_governance') && $node->get('knowledge_governance')->value) {
-        if (!empty(array_intersect($account->getRoles(TRUE), $user_roles))) {
-          return AccessResult::allowed();
-        }
-        elseif (in_array($account->id(), $users)) {
-          return AccessResult::allowed();
-        }
-        else {
-          return AccessResult::forbidden();
-        }
-      }
-      break;
-
-    case 'view':
-      if ($audience != 'public') {
-        if (!$account->hasPermission('view knowledge audience ' . $audience)) {
-          return AccessResult::forbidden();
-        }
-
-      }
-      break;
-  }
-
-  return $access;
+  return \Drupal::service(KnowledgeHooks::class)->nodeAccess($node, $op, $account);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_access().
  */
+#[LegacyHook]
 function knowledge_user_role_access(EntityInterface $entity, $op, AccountInterface $account) {
-  if ($op != 'view') {
-    return AccessResult::neutral();
-  }
-
-  return AccessResult::allowedIfHasPermission($account, 'view knowledge governance');
+  return \Drupal::service(KnowledgeHooks::class)->userRoleAccess($entity, $op, $account);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_ID_presave().
  */
+#[LegacyHook]
 function knowledge_node_presave(EntityInterface $entity) {
-
-  if (!$entity->hasField('knowledge_governance')) {
-    return;
-  }
-
-  $governance = $entity->get('knowledge_governance')->value;
-  if ($governance) {
-    return;
-  }
-
-  if ($entity->hasField('knowledge_governance_user')) {
-    $entity->set('knowledge_governance_user', NULL);
-  }
-
-  if ($entity->hasField('knowledge_governance_user_role')) {
-    $entity->set('knowledge_governance_user_role', NULL);
-  }
-
+  \Drupal::service(KnowledgeHooks::class)->nodePresave($entity);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_ID_presave().
  */
+#[LegacyHook]
 function knowledge_user_presave(EntityInterface $entity) {
-  /** @var \Drupal\user\UserInterface $user */
-  $user = $entity;
-
-  \Drupal::service('knowledge.competency')->doRoleRemoval($user);
-
-  try {
-
-    $leadership_changed = FALSE;
-    if ($user->isNew()) {
-      $leadership_changed = TRUE;
-    }
-    else {
-      $old = $user->original->get('knowledge_leader')->target_id;
-      $new = $user->get('knowledge_leader')->target_id;
-      $leadership_changed = ($new != $old);
-    }
-    if ($leadership_changed) {
-      $leader_svc = \Drupal::service('knowledge.leader');
-
-      if ($user->get('knowledge_leader')->target_id) {
-        $leader = $user->get('knowledge_leader')->entity;
-        $roles = $leader->getRoles(TRUE);
-        if (!in_array('knowledge_leader', $roles)) {
-          $leader_svc->addKnowledgeLeader($leader);
-        }
-      }
-      if (!$user->isNew()) {
-        if ($user->original->get('knowledge_leader')->target_id) {
-          $original_leader = $user->original->get('knowledge_leader')->entity;
-          if ($original_leader) {
-            $leader_svc->updateKnowledgeLeader($user, $original_leader);
-          }
-        }
-      }
-    }
-  }
-  catch (\Exception $e) {
-    $logger = \Drupal::logger('knowledge');
-    Error::logException($logger, $e);
-  }
-
+  \Drupal::service(KnowledgeHooks::class)->userPresave($entity);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_ID_delete().
  */
+#[LegacyHook]
 function knowledge_user_delete(EntityInterface $entity) {
-  try {
-    /** @var \Drupal\user\UserInterface $user */
-    $user = $entity;
-    if ($user->get('knowledge_leader')->target_id) {
-      $leader = $user->get('knowledge_leader')->entity;
-      $leader_svc = \Drupal::service('knowledge.leader');
-      $leader_svc->removeKnowledgeLeader($leader);
-    }
-  }
-  catch (\Exception $e) {
-    $logger = \Drupal::logger('knowledge');
-    Error::logException($logger, $e);
-  }
+  \Drupal::service(KnowledgeHooks::class)->userDelete($entity);
 }
 
 /**
  * Implements hook_entity_field_access().
  */
+#[LegacyHook]
 function knowledge_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
-  $permission = NULL;
-  $field_name = $field_definition->getName();
-
-  $view_fields = [
-    'knowledge_governance',
-    'knowledge_governance_user',
-    'knowledge_governance_user_role',
-    'knowledge_audience',
-    'moderation_state',
-  ];
-  $edit_fields = [
-    'knowledge_leader',
-    'knowledge_coach',
-    'knowledge_wave',
-    'knowledge_governance',
-    'knowledge_governance_user',
-    'knowledge_governance_user_role',
-  ];
-  if ($operation == 'view') {
-    if (!in_array($field_name, $view_fields)) {
-      return AccessResult::neutral();
-    }
-    switch ($field_name) {
-      case 'knowledge_governance':
-      case 'knowledge_governance_user':
-      case 'knowledge_governance_user_role':
-        $permission = 'view knowledge governance';
-        break;
-
-      case 'knowledge_audience':
-        $permission = 'view knowledge audience';
-        break;
-
-      case 'moderation_state':
-        $permission = 'view knowledge confidence';
-        break;
-    }
-  }
-  if ($operation == 'edit') {
-    if (!in_array($field_name, $edit_fields)) {
-      return AccessResult::neutral();
-    }
-    switch ($field_name) {
-      case 'knowledge_leader':
-        $permission = 'edit knowledge leader';
-        break;
-
-      case 'knowledge_coach':
-        $permission = 'edit knowledge coach';
-        break;
-
-      case 'knowledge_wave':
-        $permission = 'edit wave field';
-        break;
-
-      case 'knowledge_governance':
-      case 'knowledge_governance_user':
-      case 'knowledge_governance_user_role':
-        $permission = 'edit knowledge governance';
-        break;
-    }
-
-  }
-
-  if ($permission && $account->hasPermission($permission)) {
-    return AccessResult::allowed();
-  }
-
-  return AccessResult::forbidden();
+  return \Drupal::service(KnowledgeHooks::class)->entityFieldAccess($operation, $field_definition, $account, $items);
 }
 
 /**
@@ -1186,59 +614,17 @@ function template_preprocess_knowledge_wave(array &$variables): void {
 /**
  * Implements hook_views_post_execute().
  */
+#[LegacyHook]
 function knowledge_views_post_execute(ViewExecutable $view) {
-  // Only alter the knowledge views.
-  if (strpos($view->id(), 'knowledge') !== 0) {
-    return;
-  }
-
-  $view->result = SequenceService::fill($view->result);
+  \Drupal::service(KnowledgeHooks::class)->viewsPostExecute($view);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function knowledge_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {
-
-  $entity = $form['#entity'];
-  $entity_type = $entity->getEntityTypeId();
-  if ($entity_type != 'knowledge_quality') {
-    return;
-  }
-
-  $field_config = $form_state->getFormObject()->getEntity();
-  $field_type = $field_config->getType();
-  if ($field_type != 'boolean') {
-    return;
-  }
-
-  $quality_settings = \Drupal::config('knowledge.quality.settings');
-  $quality_categories = $quality_settings->get('categories');
-  $options_pipe = explode(PHP_EOL, $quality_categories);
-
-  $options = [
-    '_none' => t('None'),
-  ];
-  foreach ($options_pipe as $option) {
-    $option = explode('|', $option);
-    $options[$option[0]] = $option[1] ?? $option[0];
-  }
-
-  $form['third_party_settings']['knowledge'] = [
-    '#type' => 'details',
-    '#title' => t('Knowledge Quality'),
-    '#open' => TRUE,
-  ];
-
-  $form['third_party_settings']['knowledge']['quality_category'] = [
-    '#type' => 'select',
-    '#title' => t('Category'),
-    '#description' => t('Enter a custom setting value.'),
-    '#default_value' => $field_config->getThirdPartySetting('knowledge', 'quality_category', '_none'),
-    '#options' => $options,
-  ];
-
-  $form['actions']['submit']['#submit'][] = 'knowledge_quality_field_config_edit_form_submit';
+  \Drupal::service(KnowledgeHooks::class)->formFieldConfigEditFormAlter($form, $form_state);
 }
 
 /**
diff --git a/knowledge.services.yml b/knowledge.services.yml
index 7c57dc5..d7e45d6 100644
--- a/knowledge.services.yml
+++ b/knowledge.services.yml
@@ -82,3 +82,15 @@ services:
       - '@string_translation'
       - '@messenger'
       - '@current_user'
+
+  Drupal\knowledge\Hook\KnowledgeHooks:
+    class: Drupal\knowledge\Hook\KnowledgeHooks
+    autowire: true
+
+  Drupal\knowledge\Hook\KnowledgeTokensHooks:
+    class: Drupal\knowledge\Hook\KnowledgeTokensHooks
+    autowire: true
+
+  Drupal\knowledge\Hook\KnowledgeViewsHooks:
+    class: Drupal\knowledge\Hook\KnowledgeViewsHooks
+    autowire: true
diff --git a/knowledge.tokens.inc b/knowledge.tokens.inc
index 0516fbb..9da7f04 100644
--- a/knowledge.tokens.inc
+++ b/knowledge.tokens.inc
@@ -5,218 +5,22 @@
  * Builds placeholder replacement tokens for knowledge-related data.
  */
 
-use Drupal\Core\Datetime\Entity\DateFormat;
-use Drupal\Core\Entity\ContentEntityInterface;
-use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge\Hook\KnowledgeTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function knowledge_token_info() {
-  $type = [
-    'name' => t('Knowledge'),
-    'description' => t('Tokens for knowledge posted on the site.'),
-    'needs-data' => 'knowledge',
-  ];
-
-  $tokens = [];
-  // Provides an integration for each entity type except knowledge.
-  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
-    if ($entity_type_id == 'knowledge' || !$entity_type->entityClassImplements(ContentEntityInterface::class)) {
-      continue;
-    }
-
-    if (\Drupal::service('knowledge.manager')->getFields($entity_type_id)) {
-      // Get the correct token type.
-      $token_type = ($entity_type_id == 'taxonomy_term') ? 'term' : $entity_type_id;
-
-      // @todo Make this work per field. See https://www.drupal.org/node/2031903.
-      $tokens[$token_type]['knowledge-count'] = [
-        'name' => t("Knowledge count"),
-        'description' => t("The number of knowledge posted on an entity."),
-      ];
-      $tokens[$token_type]['knowledge-count-new'] = [
-        'name' => t("New knowledge count"),
-        'description' => t("The number of knowledge posted on an entity since the reader last viewed it."),
-      ];
-    }
-  }
-
-  // Core knowledge tokens.
-  $knowledge['kid'] = [
-    'name' => t("Knowledge ID"),
-    'description' => t("The unique ID of the knowledge."),
-  ];
-  $knowledge['title'] = [
-    'name' => t("Title"),
-    'description' => t("The title of the knowledge."),
-  ];
-  $knowledge['langcode'] = [
-    'name' => t('Language code'),
-    'description' => t('The language code of the language the knowledge is written in.'),
-  ];
-  $knowledge['url'] = [
-    'name' => t("URL"),
-    'description' => t("The URL of the knowledge."),
-  ];
-  $knowledge['edit-url'] = [
-    'name' => t("Edit URL"),
-    'description' => t("The URL of the knowledge's edit page."),
-  ];
-
-  // Chained tokens for knowledge.
-  $knowledge['created'] = [
-    'name' => t("Date created"),
-    'description' => t("The date the knowledge was posted."),
-    'type' => 'date',
-  ];
-  $knowledge['changed'] = [
-    'name' => t("Date changed"),
-    'description' => t("The date the knowledge was most recently updated."),
-    'type' => 'date',
-  ];
-
-  $knowledge['entity'] = [
-    'name' => t("Entity"),
-    'description' => t("The entity the knowledge was posted to."),
-    'type' => 'entity',
-  ];
-  $knowledge['author'] = [
-    'name' => t("Author"),
-    'description' => t("The author name of the knowledge."),
-    'type' => 'user',
-  ];
-
-  return [
-    'types' => ['knowledge' => $type],
-    'tokens' => [
-      'knowledge' => $knowledge,
-    ] + $tokens,
-  ];
+  return \Drupal::service(KnowledgeTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function knowledge_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
-  $token_service = \Drupal::token();
-
-  $url_options = ['absolute' => TRUE];
-  if (isset($options['langcode'])) {
-    $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
-    $langcode = $options['langcode'];
-  }
-  else {
-    $langcode = NULL;
-  }
-  $replacements = [];
-
-  if ($type == 'knowledge' && !empty($data['knowledge'])) {
-    /** @var \Drupal\knowledge\KnowledgeInterface $knowledge */
-    $knowledge = $data['knowledge'];
-
-    foreach ($tokens as $name => $original) {
-      switch ($name) {
-        // Simple key values on the knowledge.
-        case 'kid':
-          $replacements[$original] = $knowledge->id();
-          break;
-
-        case 'title':
-          $replacements[$original] = $knowledge->label();
-          break;
-
-        case 'langcode':
-          $replacements[$original] = $knowledge->language()->getId();
-          break;
-
-        // Knowledge related URLs.
-        case 'url':
-          $url_options['fragment'] = 'knowledge-' . $knowledge->id();
-          $replacements[$original] = $knowledge->toUrl('canonical', $url_options)->toString();
-          break;
-
-        case 'edit-url':
-          $url_options['fragment'] = NULL;
-          $replacements[$original] = $knowledge->toUrl('edit-form', $url_options)->toString();
-          break;
-
-        case 'author':
-          $name = $knowledge->getAuthorName();
-          // Add the user cacheability metadata in case the author of the
-          // knowledge is not the anonymous user.
-          if ($knowledge->getOwnerId()) {
-            $bubbleable_metadata->addCacheableDependency($knowledge->getOwner());
-          }
-          $replacements[$original] = $name;
-          break;
-
-        case 'created':
-          $date_format = DateFormat::load('medium');
-          $bubbleable_metadata->addCacheableDependency($date_format);
-          $replacements[$original] = \Drupal::service('date.formatter')->format($knowledge->getCreatedTime(), 'medium', '', NULL, $langcode);
-          break;
-
-        case 'changed':
-          $date_format = DateFormat::load('medium');
-          $bubbleable_metadata->addCacheableDependency($date_format);
-          $replacements[$original] = \Drupal::service('date.formatter')->format($knowledge->getChangedTime(), 'medium', '', NULL, $langcode);
-          break;
-
-        case 'entity':
-          $entity = $knowledge->getKnowledgeedEntity();
-          $bubbleable_metadata->addCacheableDependency($entity);
-          $title = $entity->label();
-          $replacements[$original] = $title;
-          break;
-      }
-    }
-
-    // Chained token relationships.
-    if ($entity_tokens = $token_service->findwithPrefix($tokens, 'entity')) {
-      $entity = $knowledge->getKnowledgeedEntity();
-      $replacements += $token_service->generate($knowledge->getKnowledgeedEntityTypeId(), $entity_tokens, [$knowledge->getKnowledgeedEntityTypeId() => $entity], $options, $bubbleable_metadata);
-    }
-
-    if ($date_tokens = $token_service->findwithPrefix($tokens, 'created')) {
-      $replacements += $token_service->generate('date', $date_tokens, ['date' => $knowledge->getCreatedTime()], $options, $bubbleable_metadata);
-    }
-
-    if ($date_tokens = $token_service->findwithPrefix($tokens, 'changed')) {
-      $replacements += $token_service->generate('date', $date_tokens, ['date' => $knowledge->getChangedTime()], $options, $bubbleable_metadata);
-    }
-
-    if (($author_tokens = $token_service->findwithPrefix($tokens, 'author')) && $account = $knowledge->getOwner()) {
-      $replacements += $token_service->generate('user', $author_tokens, ['user' => $account], $options, $bubbleable_metadata);
-    }
-  }
-  // Replacement tokens for any content entities that have knowledge field.
-  elseif (!empty($data[$type]) && $data[$type] instanceof FieldableEntityInterface) {
-    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
-    $entity = $data[$type];
-
-    foreach ($tokens as $name => $original) {
-      switch ($name) {
-        case 'knowledge-count':
-          $count = 0;
-          $fields = array_keys(\Drupal::service('knowledge.manager')->getFields($entity->getEntityTypeId()));
-          $definitions = array_keys($entity->getFieldDefinitions());
-          $valid_fields = array_intersect($fields, $definitions);
-          foreach ($valid_fields as $field_name) {
-            /** @var \Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItemInterface $field */
-            $field = $entity->get($field_name);
-            $count += $field->total_count;
-          }
-          $replacements[$original] = $count;
-          break;
-
-        case 'knowledge-count-new':
-          $replacements[$original] = \Drupal::service('knowledge.manager')->getCountNewKnowledges($entity);
-          break;
-      }
-    }
-  }
-
-  return $replacements;
+  return \Drupal::service(KnowledgeTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
diff --git a/knowledge.views.inc b/knowledge.views.inc
index 50a3f38..88d905d 100644
--- a/knowledge.views.inc
+++ b/knowledge.views.inc
@@ -5,163 +5,13 @@
  * Provide views data for knowledge.module.
  */
 
-use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge\Hook\KnowledgeViewsHooks;
 
 /**
  * Implements hook_views_data_alter().
  */
+#[LegacyHook]
 function knowledge_views_data_alter(&$data) {
-
-  $data['node_field_data']['knowledge_node_type'] = [
-    'title' => t('Knowledge Content Types'),
-    'group' => t('Content'),
-    'filter' => [
-      'title' => t('Knowledge Content Types'),
-      'help' => t('Limit to "knowledge" content types.'),
-      'field' => 'type',
-      'id' => 'knowledge_node_type',
-    ],
-  ];
-
-  $data['node_field_data']['knowledge_merge'] = [
-    'field' => [
-      'title' => t('Merge link'),
-      'help' => t('Provide a simple link to merge duplicate articles.'),
-      'id' => 'knowledge_merge_link',
-    ],
-  ];
-
-  $data['knowledge_adherence']['knowledge_disposition'] = [
-    'title' => t('Disposition'),
-    'group' => t('Adherence'),
-    'filter' => [
-      'title' => t('Knowledge Adherence Disposition'),
-      'help' => t('Filters by disposition.'),
-      'field' => 'disposition',
-      'id' => 'knowledge_disposition',
-    ],
-  ];
-
-  $data['users_field_data']['knowledge_learners'] = [
-    'title' => t('Knowledge Learners'),
-    'group' => t('User'),
-    'filter' => [
-      'title' => t('Knowledge Learners'),
-      'help' => t('Limit a coaches learners.'),
-      'field' => 'uid',
-      'id' => 'knowledge_learners',
-    ],
-  ];
-  $data['users_field_data']['knowledge_leader_coach'] = [
-    'title' => t('Knowledge Leader Coach'),
-    'help' => t("The user's coach is also their leader."),
-    'filter' => [
-      'id' => 'knowledge_leader_coach',
-    ],
-  ];
-
-  // New knowledge is only supported for node table because it requires the
-  // history table.
-  $data['node']['new_knowledge'] = [
-    'title' => t('New knowledge'),
-    'help' => t('The amount of new knowledge on the node.'),
-    'field' => [
-      'id' => 'node_new_knowledge',
-      'no group by' => TRUE,
-    ],
-  ];
-
-  // Provides an integration for each entity type except knowledge.
-  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
-    if ($entity_type_id == 'knowledge' || !$entity_type->entityClassImplements(ContentEntityInterface::class) || !$entity_type->getBaseTable()) {
-      continue;
-    }
-    $fields = \Drupal::service('knowledge.manager')->getFields($entity_type_id);
-    $base_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
-    $args = ['@entity_type' => $entity_type_id];
-
-    if ($fields) {
-      $data[$base_table]['knowledge_link'] = [
-        'field' => [
-          'title' => t('Add knowledge link'),
-          'help' => t('Display the standard add knowledge link used on regular @entity_type, which will only display if the viewing user has access to add a knowledge.', $args),
-          'id' => 'knowledge_entity_link',
-        ],
-      ];
-
-      // Multilingual properties are stored in data table.
-      if (!($table = $entity_type->getDataTable())) {
-        $table = $entity_type->getBaseTable();
-      }
-      $data[$table]['uid_touch'] = [
-        'title' => t('User posted or knowledge'),
-        'help' => t('Display nodes only if a user posted the @entity_type or knowledge on the @entity_type.', $args),
-        'argument' => [
-          'field' => 'uid',
-          'name table' => 'users_field_data',
-          'name field' => 'name',
-          'id' => 'argument_knowledge_user_uid',
-          'no group by' => TRUE,
-          'entity_type' => $entity_type_id,
-          'entity_id' => $entity_type->getKey('id'),
-        ],
-        'filter' => [
-          'field' => 'uid',
-          'name table' => 'users_field_data',
-          'name field' => 'name',
-          'id' => 'knowledge_user_uid',
-          'entity_type' => $entity_type_id,
-          'entity_id' => $entity_type->getKey('id'),
-        ],
-      ];
-
-      foreach ($fields as $field_name => $field) {
-        $data[$base_table][$field_name . '_kid'] = [
-          'title' => t('Knowledge of the @entity_type using field: @field_name', $args + ['@field_name' => $field_name]),
-          'help' => t('Relate all knowledge on the @entity_type. This will create 1 duplicate record for every knowledge. Usually if you need this it is better to create a knowledge view.', $args),
-          'relationship' => [
-            'group' => t('Knowledge'),
-            'label' => t('Knowledge'),
-            'base' => 'knowledge',
-            'base field' => 'entity_id',
-            'relationship field' => $entity_type->getKey('id'),
-            'id' => 'standard',
-            'extra' => [
-              [
-                'field' => 'entity_type',
-                'value' => $entity_type_id,
-              ],
-              [
-                'field' => 'field_name',
-                'value' => $field_name,
-              ],
-            ],
-          ],
-        ];
-      }
-    }
-  }
-
-  $data['users_field_data']['knowledge_approve'] = [
-    'title' => t('Knowledge Competency Approval'),
-    'help' => t("Link to approve user's promotion."),
-    'field' => [
-      'id' => 'knowledge_competency_approve',
-    ],
-
-  ];
-
-  $data['knowledge_competency']['progress'] = [
-    'title' => t("Progress"),
-    'help' => t('The progress of mastery.'),
-    'field' => [
-      'id' => 'knowledge_competency_percentage',
-      'no group by' => TRUE,
-    ],
-    'sort' => [
-      'id' => 'knowledge_competency_percentage',
-      'no group by' => TRUE,
-    ],
-  ];
-
+  \Drupal::service(KnowledgeViewsHooks::class)->viewsDataAlter($data);
 }
diff --git a/modules/knowledge_ldap/knowledge_ldap.module b/modules/knowledge_ldap/knowledge_ldap.module
index 9309094..cb2a3c2 100644
--- a/modules/knowledge_ldap/knowledge_ldap.module
+++ b/modules/knowledge_ldap/knowledge_ldap.module
@@ -5,14 +5,15 @@
  * Hooks provided by the Knowledge LDAP module.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge_ldap\Hook\KnowledgeLdapHooks;
 use Drupal\user\UserInterface;
 use Symfony\Component\Ldap\Entry;
 
 /**
  * Implements hook_ldap_user_edit_user_alter().
  */
+#[LegacyHook]
 function knowledge_ldap_ldap_user_edit_user_alter(UserInterface $account, Entry $entry, array $context) {
-  $service = \Drupal::service('knowledge_ldap.leader');
-  $service->updateLeaderField($account, $entry, $context['ldap_server']);
-
+  \Drupal::service(KnowledgeLdapHooks::class)->ldapUserEditUserAlter($account, $entry, $context);
 }
diff --git a/modules/knowledge_ldap/knowledge_ldap.services.yml b/modules/knowledge_ldap/knowledge_ldap.services.yml
index 693d8c0..f7a8eac 100644
--- a/modules/knowledge_ldap/knowledge_ldap.services.yml
+++ b/modules/knowledge_ldap/knowledge_ldap.services.yml
@@ -13,3 +13,7 @@ services:
       - '@ldap.bridge'
       - '@ldap.group_manager'
       - '@ldap.drupal_user_processor'
+
+  Drupal\knowledge_ldap\Hook\KnowledgeLdapHooks:
+    class: Drupal\knowledge_ldap\Hook\KnowledgeLdapHooks
+    autowire: true
diff --git a/modules/knowledge_ldap/src/Hook/KnowledgeLdapHooks.php b/modules/knowledge_ldap/src/Hook/KnowledgeLdapHooks.php
new file mode 100644
index 0000000..979a89d
--- /dev/null
+++ b/modules/knowledge_ldap/src/Hook/KnowledgeLdapHooks.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\knowledge_ldap\Hook;
+
+use Symfony\Component\Ldap\Entry;
+use Drupal\user\UserInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for knowledge_ldap.
+ */
+class KnowledgeLdapHooks {
+
+  /**
+   * Implements hook_ldap_user_edit_user_alter().
+   */
+  #[Hook('ldap_user_edit_user_alter')]
+  public function ldapUserEditUserAlter(UserInterface $account, Entry $entry, array $context) {
+    $service = \Drupal::service('knowledge_ldap.leader');
+    $service->updateLeaderField($account, $entry, $context['ldap_server']);
+  }
+
+}
diff --git a/src/Controller/CompetencyController.php b/src/Controller/CompetencyController.php
index 8899f82..0a74496 100644
--- a/src/Controller/CompetencyController.php
+++ b/src/Controller/CompetencyController.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\knowledge\Controller;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\views\Views;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Controller\ControllerBase;
@@ -348,7 +350,7 @@ class CompetencyController extends ControllerBase implements ContainerInjectionI
       $fields[$field_id] = $role;
     }
 
-    $result = views_get_view_result('knowledge_competency_progress');
+    $result = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => Views::getViewResult('knowledge_competency_progress'), fn() => views_get_view_result('knowledge_competency_progress'));
     if (empty($result)) {
       return [];
     }
diff --git a/src/Hook/KnowledgeHooks.php b/src/Hook/KnowledgeHooks.php
new file mode 100644
index 0000000..7603815
--- /dev/null
+++ b/src/Hook/KnowledgeHooks.php
@@ -0,0 +1,932 @@
+<?php
+
+namespace Drupal\knowledge\Hook;
+
+use Drupal\knowledge\Service\SequenceService;
+use Drupal\views\ViewExecutable;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Utility\Error;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Entity\Entity\EntityViewMode;
+use Drupal\user\UserInterface;
+use Drupal\user\RoleInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\node\NodeInterface;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItemInterface;
+use Drupal\field\FieldConfigInterface;
+use Drupal\knowledge\Entity\KnowledgeType;
+use Drupal\Core\Url;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for knowledge.
+ */
+class KnowledgeHooks {
+  use StringTranslationTrait;
+  /**
+   * The time cutoff for knowledge marked as read for entity types other node.
+   *
+   * Knowledge changed before this time are always marked as read.
+   * Knowledge changed after this time may be marked new, updated, or read,
+   * depending on their state for the current user. Defaults to 30 days ago.
+   *
+   * @todo Remove when https://www.drupal.org/node/1029708 lands.
+   */
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      case 'help.page.knowledge':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('The Knowledge module allows users to knowledge on site content, set Knowledge defaults and permissions, and moderate Knowledge. For more information, see the <a href=":knowledge">online documentation for the Knowledge module</a>.', [
+          ':knowledge' => 'https://www.drupal.org/documentation/modules/knowledge',
+        ]) . '</p>';
+        $output .= '<h3>' . $this->t('Uses') . '</h3>';
+        $output .= '<dl>';
+        $output .= '<dt>' . $this->t('Enabling Knowledge') . '</dt>';
+        $output .= '<dd>' . $this->t('Knowledge functionality can be enabled for any entity sub-type (for example, a <a href=":content-type">content type</a>) by adding a <em>Knowledge</em> field on its <em>Manage fields page</em>. Adding or removing knowledge links for an entity through the user interface requires the <a href=":field_ui">Field UI</a> module to be enabled, even though the knowledge functionality works without it. For more information on fields and entities, see the <a href=":field">Field module help page</a>.', [
+          ':content-type' => \Drupal::moduleHandler()->moduleExists('node') ? Url::fromRoute('entity.node_type.collection')->toString() : '#',
+          ':field' => Url::fromRoute('help.page', [
+            'name' => 'field',
+          ])->toString(),
+          ':field_ui' => \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', [
+            'name' => 'field_ui',
+          ])->toString() : '#',
+        ]) . '</dd>';
+        $output .= '<dt>' . $this->t('Configuring Knowledge settings') . '</dt>';
+        $output .= '<dd>' . $this->t('Knowledge settings can be configured by editing the <em>Knowledge</em> field on the <em>Manage fields page</em> of an entity type if the <em>Field UI module</em> is enabled. Configuration includes the label of the knowledge field, the amount of knowledge to be displayed, and whether they are shown in threaded list. Knowledge can be configured as: <em>Open</em> to allow new knowledge, <em>Closed</em> to view existing knowledge, but prevent new knowledge, or <em>Hidden</em> to hide existing knowledge and prevent new knowledge. Changing this configuration for an entity type will not change existing entity items.') . '</dd>';
+        $output .= '<dt>' . $this->t('Overriding default settings') . '</dt>';
+        $output .= '<dd>' . $this->t('Users with the appropriate permissions can override the default Knowledge settings of an entity type when they create an item of that type.') . '</dd>';
+        $output .= '<dt>' . $this->t('Adding knowledge types') . '</dt>';
+        $output .= '<dd>' . $this->t('Additional <em>knowledge types</em> can be created per entity sub-type and added on the <a href=":field">Knowledge types page</a>. If there are multiple knowledge types available you can select the appropriate one after adding a <em>Knowledge field</em>.', [
+          ':field' => Url::fromRoute('entity.knowledge_type.collection')->toString(),
+        ]) . '</dd>';
+        $output .= '<dt>' . $this->t('Approving and managing knowledge') . '</dt>';
+        $output .= '<dd>' . $this->t('Knowledge from users who have the <em>Skip knowledge approval</em> permission are published immediately. All other Knowledge are placed in the <a href=":knowledge-approval">Unlinked knowledge</a> queue, until a user who has permission to <em>Administer knowledge and knowledge settings</em> publishes or deletes them. Published knowledge can be bulk managed on the <a href=":admin-knowledge">Published knowledge</a> administration page. When a knowledge has no replies, it remains editable by its author, as long as the author has <em>Edit own knowledge</em> permission.', [
+          ':knowledge-approval' => Url::fromRoute('knowledge.admin_approval')->toString(),
+          ':admin-knowledge' => Url::fromRoute('knowledge.admin')->toString(),
+        ]) . '</dd>';
+        $output .= '</dl>';
+        return $output;
+
+      case 'entity.knowledge_type.collection':
+        $output = '<p>' . $this->t('This page provides a list of all knowledge types on the site and allows you to manage the fields, form and display settings for each.') . '</p>';
+        return $output;
+    }
+  }
+
+  /**
+   * Implements hook_entity_extra_field_info().
+   */
+  #[Hook('entity_extra_field_info')]
+  public function entityExtraFieldInfo() {
+    $return = [];
+    foreach (KnowledgeType::loadMultiple() as $knowledge_type) {
+      $return['knowledge'][$knowledge_type->id()] = [
+        'form' => [
+          'author' => [
+            'label' => $this->t('Author'),
+            'description' => $this->t('Author textfield'),
+            'weight' => -2,
+          ],
+        ],
+      ];
+      $return['knowledge'][$knowledge_type->id()]['display']['links'] = [
+        'label' => $this->t('Links'),
+        'description' => $this->t('Knowledge operation links'),
+        'weight' => 100,
+        'visible' => TRUE,
+      ];
+    }
+    return $return;
+  }
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public function theme() {
+    return [
+      'knowledge' => [
+        'render element' => 'elements',
+      ],
+      'field__knowledge' => [
+        'base hook' => 'field',
+      ],
+      'knowledge_adherence_form' => [
+        'render element' => 'form',
+      ],
+      'knowledge_quality' => [
+        'render element' => 'elements',
+      ],
+      'block__social__plugin_id__knowledge_suffa' => [
+        'render element' => 'elements',
+        'base hook' => 'block',
+      ],
+      'knowledge_wave' => [
+        'render element' => 'elements',
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_create() for 'field_config'.
+   */
+  #[Hook('field_config_create')]
+  public function fieldConfigCreate(FieldConfigInterface $field) {
+    if ($field->getType() == 'knowledge' && !$field->isSyncing()) {
+      // Assign default values for the field.
+      $default_value = $field->getDefaultValueLiteral();
+      $default_value += [
+            [],
+      ];
+      $default_value[0] += [
+        'status' => KnowledgeItemInterface::OPEN,
+        'first_knowledge' => 0,
+        'first_knowledge_timestamp' => 0,
+        'first_knowledge_uid' => 0,
+        'last_knowledge' => 0,
+        'last_knowledge_timestamp' => 0,
+        'last_knowledge_uid' => 0,
+        'total_count' => 0,
+        'long_count' => 0,
+        'medium_count' => 0,
+        'short_count' => 0,
+        'total_citation' => 0,
+        'long_citation' => 0,
+        'medium_citation' => 0,
+        'short_citation' => 0,
+      ];
+      $field->setDefaultValue($default_value);
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_update() for 'field_config'.
+   */
+  #[Hook('field_config_update')]
+  public function fieldConfigUpdate(FieldConfigInterface $field) {
+    if ($field->getType() == 'knowledge') {
+      // Knowledge field settings also affects the rendering of *knowledge*
+      // entities, not only the *linked* entities.
+      \Drupal::entityTypeManager()->getViewBuilder('knowledge')->resetCache();
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_insert() for 'field_storage_config'.
+   */
+  #[Hook('field_storage_config_insert')]
+  public function fieldStorageConfigInsert(FieldStorageConfigInterface $field_storage) {
+    if ($field_storage->getType() == 'knowledge') {
+      // Check that the target entity type uses an integer ID.
+      $entity_type_id = $field_storage->getTargetEntityTypeId();
+      if (!_knowledge_entity_uses_integer_id($entity_type_id)) {
+        throw new \UnexpectedValueException('You cannot attach a knowledge field to an entity with a non-integer ID field');
+      }
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
+   */
+  #[Hook('field_config_delete')]
+  public function fieldConfigDelete(FieldConfigInterface $field) {
+    if ($field->getType() == 'knowledge') {
+      // Delete all knowledge that used by the entity bundle.
+      $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
+      $entity_query->condition('entity_type', $field->getEntityTypeId());
+      $entity_query->condition('field_name', $field->getName());
+      $kids = $entity_query->execute();
+      $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
+      $knowledge_links = $knowledge_storage->loadMultiple($kids);
+      $knowledge_storage->delete($knowledge_links);
+    }
+  }
+
+  /**
+   * Implements hook_node_links_alter().
+   */
+  #[Hook('node_links_alter')]
+  public function nodeLinksAlter(array &$links, NodeInterface $node, array &$context) {
+    // Knowledge links are only added to node entity type for backwards
+    // compatibility. Should you require knowledge links for other entity types
+    // you can do so by implementing a new field formatter.
+    // @todo Make this configurable from the formatter. See
+    //   https://www.drupal.org/node/1901110.
+    $knowledge_links = \Drupal::service('knowledge.link_builder')->buildKnowledgeedEntityLinks($node, $context);
+    $links += $knowledge_links;
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_view_alter() for node entities.
+   */
+  #[Hook('node_view_alter')]
+  public function nodeViewAlter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
+    if (\Drupal::moduleHandler()->moduleExists('history')) {
+      $build['#attributes']['data-history-node-id'] = $node->id();
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter() for field_ui_field_storage_add_form.
+   */
+  #[Hook('form_field_ui_field_storage_add_form_alter')]
+  public function formFieldUiFieldStorageAddFormAlter(&$form, FormStateInterface $form_state) {
+    $route_match = \Drupal::routeMatch();
+    if ($form_state->get('entity_type_id') == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
+      $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
+    }
+    if (!_knowledge_entity_uses_integer_id($form_state->get('entity_type_id'))) {
+      $optgroup = (string) $this->t('General');
+      // You cannot use knowledge fields on entity types with non-integer IDs.
+      unset($form['add']['new_storage_type']['#options'][$optgroup]['knowledge']);
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_field_ui_form_display_overview_form_alter')]
+  public function formFieldUiFormDisplayOverviewFormAlter(&$form, FormStateInterface $form_state) {
+    $route_match = \Drupal::routeMatch();
+    if ($form['#entity_type'] == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
+      $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_field_ui_display_overview_form_alter')]
+  public function formFieldUiDisplayOverviewFormAlter(&$form, FormStateInterface $form_state) {
+    $route_match = \Drupal::routeMatch();
+    if ($form['#entity_type'] == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
+      $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_node_form_alter')]
+  public function formNodeFormAlter(&$form, $form_state) {
+    if (array_key_exists('knowledge_audience', $form)) {
+      $form['knowledge_audience']['#group'] = 'meta';
+      $form['knowledge_audience']['widget']['#wrapper_attributes'] = [
+        'class' => [
+          'container-inline',
+        ],
+      ];
+    }
+    if (array_key_exists('status', $form)) {
+      $form['status']['#group'] = 'meta';
+    }
+    if (array_key_exists('moderation_state', $form)) {
+      $form['moderation_state']['#group'] = 'meta';
+    }
+    if (array_key_exists('knowledge_governance', $form) && \Drupal::currentUser()->hasPermission('edit knowledge governance')) {
+      $form['#validate'][] = '_knowledge_governance_form_validate';
+      $entity = $form_state->getFormObject()->getEntity();
+      $knowledge_governance = (bool) $entity->get('knowledge_governance')->value;
+      $form['governance'] = [
+        '#type' => 'details',
+        '#title' => $this->t('Governance'),
+            // Open the details when the selected value is different to the stored
+            // default values for the field.
+        '#open' => $knowledge_governance,
+        '#group' => 'advanced',
+        '#attributes' => [
+          'class' => [
+            'knowledge-governance-' . Html::getClass($entity->getEntityTypeId()) . '-settings-form',
+          ],
+        ],
+        '#attached' => [],
+      ];
+      $form['knowledge_governance']['#group'] = 'governance';
+      if (array_key_exists('knowledge_governance_user', $form)) {
+        $form['knowledge_governance_user']['#group'] = 'governance';
+        $form['knowledge_governance_user']['#states']['visible'] = [
+          ':input[name="knowledge_governance"]' => [
+            'value' => 1,
+          ],
+        ];
+      }
+      if (array_key_exists('knowledge_governance_user_role', $form)) {
+        $governance_settings = \Drupal::config('knowledge.governance.settings');
+        $roles = $governance_settings->get('roles');
+        $roles = array_filter($roles);
+        $form['knowledge_governance_user_role']['#group'] = 'governance';
+        $form['knowledge_governance_user_role']['#states']['visible'] = [
+          ':input[name="knowledge_governance"]' => [
+            'value' => 1,
+          ],
+        ];
+        $roles_remove = [];
+        $role_options = $form['knowledge_governance_user_role']['widget']['#options'] ?? [];
+        foreach ($role_options as $role => $role_label) {
+          $remove = !empty($roles) && !in_array($role, $roles);
+          if ($role == '_none') {
+            $remove = TRUE;
+          }
+          if ($remove) {
+            $roles_remove[] = $role;
+          }
+        }
+        foreach ($roles_remove as $role) {
+          unset($form['knowledge_governance_user_role']['widget']['#options'][$role]);
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_entity_storage_load().
+   *
+   * @see \Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItem::propertyDefinitions()
+   */
+  #[Hook('entity_storage_load')]
+  public function entityStorageLoad($entities, $entity_type) {
+    // Knowledge can only be attached to content entities, so skip others.
+    if (!\Drupal::entityTypeManager()->getDefinition($entity_type)->entityClassImplements(FieldableEntityInterface::class)) {
+      return;
+    }
+    if (!\Drupal::service('knowledge.manager')->getFields($entity_type)) {
+      // Do not query database when entity has no knowledge fields.
+      return;
+    }
+    // Load knowledge information from the database and update the entity's
+    // knowledge statistics properties, which are defined on each KnowledgeItem
+    // field.
+    $result = \Drupal::service('knowledge.statistics')->read($entities, $entity_type);
+    foreach ($result as $record) {
+      // Skip fields that entity does not have.
+      if (!$entities[$record->entity_id]->hasField($record->field_name)) {
+        continue;
+      }
+      $knowledge_statistics = $entities[$record->entity_id]->get($record->field_name);
+      $knowledge_statistics->first_knowledge = $record->first_knowledge;
+      $knowledge_statistics->first_knowledge_timestamp = $record->first_knowledge_timestamp;
+      $knowledge_statistics->first_knowledge_uid = $record->first_knowledge_uid;
+      $knowledge_statistics->last_knowledge = $record->last_knowledge;
+      $knowledge_statistics->last_knowledge_timestamp = $record->last_knowledge_timestamp;
+      $knowledge_statistics->last_knowledge_uid = $record->last_knowledge_uid;
+      $knowledge_statistics->total_count = $record->total_count;
+      $knowledge_statistics->long_count = $record->long_count;
+      $knowledge_statistics->medium_count = $record->medium_count;
+      $knowledge_statistics->short_count = $record->short_count;
+      $knowledge_statistics->total_citation = $record->total_citation;
+      $knowledge_statistics->long_citation = $record->long_citation;
+      $knowledge_statistics->medium_citation = $record->medium_citation;
+      $knowledge_statistics->short_citation = $record->short_citation;
+    }
+  }
+
+  /**
+   * Implements hook_entity_insert().
+   */
+  #[Hook('entity_insert')]
+  public function entityInsert(EntityInterface $entity) {
+    // Allow bulk updates and inserts to temporarily disable the
+    // maintenance of the {knowledge_entity_statistics} table.
+    if (\Drupal::state()->get('knowledge.maintain_entity_statistics') && $fields = \Drupal::service('knowledge.manager')->getFields($entity->getEntityTypeId())) {
+      \Drupal::service('knowledge.statistics')->create($entity, $fields);
+    }
+  }
+
+  /**
+   * Implements hook_entity_predelete().
+   */
+  #[Hook('entity_predelete')]
+  public function entityPredelete(EntityInterface $entity) {
+    // Entities can have non-numeric IDs, but {knowledge} and
+    // {knowledge_entity_statistics} tables have integer columns for entity ID,
+    // and PostgreSQL throws exceptions if you attempt query conditions with
+    // mismatched types. So, we need to verify that the ID is numeric (even for an
+    // entity type that has an integer ID, $entity->id() might be a string
+    // containing a number), and then cast it to an integer when querying.
+    if ($entity instanceof FieldableEntityInterface && is_numeric($entity->id())) {
+      $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
+      $entity_query->condition('entity_id', (int) $entity->id());
+      $entity_query->condition('entity_type', $entity->getEntityTypeId());
+      $kids = $entity_query->execute();
+      $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
+      $knowledge_links = $knowledge_storage->loadMultiple($kids);
+      $knowledge_storage->delete($knowledge_links);
+      \Drupal::service('knowledge.statistics')->delete($entity);
+    }
+  }
+
+  /**
+   * Implements hook_node_update_index().
+   */
+  #[Hook('node_update_index')]
+  public function nodeUpdateIndex(EntityInterface $node) {
+    $index_knowledge =& drupal_static('knowledge_node_update_index');
+    if ($index_knowledge === NULL) {
+      // Do not index in the following three cases:
+      // 1. 'Authenticated user' can search content but can't access knowledge.
+      // 2. 'Anonymous user' can search content but can't access knowledge.
+      // 3. Any role can search content but can't access knowledge and access
+      // knowledge is not granted by the 'authenticated user' role. In this case
+      // all users might have both permissions from various roles but it is also
+      // possible to set up a user to have only search content and so a user
+      // edit could change the security situation so it is not safe to index the
+      // knowledge.
+      $index_knowledge = TRUE;
+      $roles = \Drupal::entityTypeManager()->getStorage('user_role')->loadMultiple();
+      $authenticated_can_access = $roles[RoleInterface::AUTHENTICATED_ID]->hasPermission('access knowledge');
+      foreach ($roles as $rid => $role) {
+        if ($role->hasPermission('search content') && !$role->hasPermission('access knowledge')) {
+          if ($rid == RoleInterface::AUTHENTICATED_ID || $rid == RoleInterface::ANONYMOUS_ID || !$authenticated_can_access) {
+            $index_knowledge = FALSE;
+            break;
+          }
+        }
+      }
+    }
+    $build = [];
+    if ($index_knowledge) {
+      foreach (\Drupal::service('knowledge.manager')->getFields('node') as $field_name => $info) {
+        // Skip fields that entity does not have.
+        if (!$node->hasField($field_name)) {
+          continue;
+        }
+        $field_definition = $node->getFieldDefinition($field_name);
+        $mode = $field_definition->getSetting('default_mode');
+        $knowledge_per_page = $field_definition->getSetting('per_page');
+        if ($node->get($field_name)->status) {
+          /** @var \Drupal\knowledge\KnowledgeStorageInterface $knowledge_storage */
+          $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
+          /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
+          $knowledge_links = $knowledge_storage->loadThread($node, $field_name, $mode, $knowledge_per_page);
+          if ($knowledge_links) {
+            $build[] = \Drupal::entityTypeManager()->getViewBuilder('knowledge')->viewMultiple($knowledge_links);
+          }
+        }
+      }
+    }
+    return \Drupal::service('renderer')->renderInIsolation($build);
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public function cron() {
+    // Store the maximum possible knowledge per thread (used for node search
+    // ranking by reply count).
+    \Drupal::state()->set('knowledge.node_knowledge_statistics_scale', 1.0 / max(1, \Drupal::service('knowledge.statistics')->getMaximumCount('node')));
+    $today = strtotime('today');
+    $statistics_calculated = \Drupal::state()->get('knowledge.knowledge_statistics_calculated', 0);
+    if ($today > $statistics_calculated) {
+      $database = \Drupal::database();
+      $query = $database->query('SELECT last_knowledge
+    FROM knowledge_entity_statistics
+    WHERE last_knowledge <> 0');
+      $results = $query->fetchAll();
+      $ids = [];
+      foreach ($results as $result) {
+        $ids[] = $result->last_knowledge;
+      }
+      $knowledge_links = \Drupal::service('entity_type.manager')->getStorage('knowledge')->loadMultiple($ids);
+      foreach ($knowledge_links as $link) {
+        \Drupal::service('knowledge.statistics')->update($link);
+      }
+      \Drupal::state()->set('knowledge.knowledge_statistics_calculated', \Drupal::time()->getRequestTime());
+    }
+  }
+
+  /**
+   * Implements hook_node_search_result().
+   *
+   * Formats a knowledge count string and returns it, for display with search
+   * results.
+   */
+  #[Hook('node_search_result')]
+  public function nodeSearchResult(EntityInterface $node) {
+    /** @var \Drupal\node\NodeInterface $node */
+    $knowledge_fields = \Drupal::service('knowledge.manager')->getFields('node');
+    $knowledge_links = 0;
+    $open = FALSE;
+    foreach ($knowledge_fields as $field_name => $info) {
+      // Skip fields that entity does not have.
+      if (!$node->hasField($field_name)) {
+        continue;
+      }
+      // Do not make a string if knowledge is hidden.
+      $status = $node->get($field_name)->status;
+      if (\Drupal::currentUser()->hasPermission('access knowledge') && $status != KnowledgeItemInterface::HIDDEN) {
+        if ($status == KnowledgeItemInterface::OPEN) {
+          // At least one knowledge field is open.
+          $open = TRUE;
+        }
+        $knowledge_links += $node->get($field_name)->total_count;
+      }
+    }
+    // Do not make a string if there are no knowledge fields, or no knowledge
+    // exist or all knowledge fields are hidden.
+    if ($knowledge_links > 0 || $open) {
+      return [
+        'knowledge' => \Drupal::translation()->formatPlural($knowledge_links, '1 knowledge', '@count knowledge'),
+      ];
+    }
+  }
+
+  /**
+   * Implements hook_user_cancel().
+   */
+  #[Hook('user_cancel')]
+  public function userCancel($edit, UserInterface $account, $method) {
+    switch ($method) {
+      case 'user_cancel_block_unpublish':
+        /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
+        $knowledge_links = \Drupal::entityTypeManager()->getStorage('knowledge')->loadByProperties([
+          'uid' => $account->id(),
+        ]);
+        foreach ($knowledge_links as $know) {
+          $know->setUnpublished();
+          $know->save();
+        }
+        break;
+
+      case 'user_cancel_reassign':
+        /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
+        $knowledge_links = \Drupal::entityTypeManager()->getStorage('knowledge')->loadByProperties([
+          'uid' => $account->id(),
+        ]);
+        foreach ($knowledge_links as $know) {
+          $know->setOwnerId(0);
+          $know->save();
+        }
+        break;
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete() for user entities.
+   */
+  #[Hook('user_predelete')]
+  public function userPredelete($account) {
+    $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
+    $entity_query->condition('uid', $account->id());
+    $kids = $entity_query->execute();
+    $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
+    $knowledge = $knowledge_storage->loadMultiple($kids);
+    $knowledge_storage->delete($knowledge);
+  }
+
+  /**
+   * Implements hook_preprocess_HOOK() for block templates.
+   */
+  #[Hook('preprocess_block')]
+  public function preprocessBlock(&$variables) {
+    if (array_key_exists('provider', $variables['configuration']) && $variables['configuration']['provider'] == 'knowledge') {
+      $variables['attributes']['role'] = 'navigation';
+    }
+  }
+
+  /**
+   * Implements hook_ranking().
+   */
+  #[Hook('ranking')]
+  public function ranking() {
+    return \Drupal::service('knowledge.statistics')->getRankingInfo();
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_presave() for entity_view_display entities.
+   */
+  #[Hook('entity_view_display_presave')]
+  public function entityViewDisplayPresave(EntityViewDisplayInterface $display) {
+    // Act only on knowledge view displays being disabled.
+    if ($display->isNew() || $display->getTargetEntityTypeId() !== 'knowledge' || $display->status()) {
+      return;
+    }
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_view_display');
+    if (!$storage->loadUnchanged($display->getOriginalId())->status()) {
+      return;
+    }
+    // Disable the knowledge field formatter when the used view display
+    // is disabled.
+    foreach ($storage->loadMultiple() as $view_display) {
+      /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
+      $changed = FALSE;
+      foreach ($view_display->getComponents() as $field => $component) {
+        if (isset($component['type']) && $component['type'] === 'knowledge_default') {
+          if ($component['settings']['view_mode'] === $display->getMode()) {
+            $view_display->removeComponent($field);
+            /** @var \Drupal\Core\Entity\EntityViewModeInterface $mode */
+            $mode = EntityViewMode::load($display->getTargetEntityTypeId() . '.' . $display->getMode());
+            $arguments = [
+              '@id' => $view_display->id(),
+              '@name' => $field,
+              '@display' => $mode->label(),
+              '@mode' => $display->getMode(),
+            ];
+            \Drupal::logger('system')->warning("View display '@id': Knowledge field formatter '@name' was disabled because it is using the knowledge view display '@display' (@mode) that was just disabled.", $arguments);
+            $changed = TRUE;
+          }
+        }
+      }
+      if ($changed) {
+        $view_display->save();
+      }
+    }
+  }
+
+  /**
+   * Implements hook_library_info_alter().
+   */
+  #[Hook('library_info_alter')]
+  public function libraryInfoAlter(&$libraries, $extension) {
+    if ($extension === 'knowledge' && isset($libraries['activity'])) {
+      $libraries['activity']['dependencies'][] = \Drupal::config('moderation_dashboard.settings')->get('chart_js_cdn') ? 'moderation_dashboard/chart.js.external' : 'moderation_dashboard/chart.js.internal';
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_access().
+   */
+  #[Hook('node_access')]
+  public function nodeAccess(NodeInterface $node, $op, AccountInterface $account) {
+    $user_roles = [];
+    if ($node->hasField('knowledge_governance_user_role') && $node_roles = $node->get('knowledge_governance_user_role')) {
+      foreach ($node_roles as $role) {
+        $user_roles[] = $role->target_id;
+      }
+    }
+    $users = [];
+    if ($node->hasField('knowledge_governance_user') && $node_users = $node->get('knowledge_governance_user')) {
+      foreach ($node_users as $user) {
+        $users[] = $user->target_id;
+      }
+    }
+    $access = AccessResult::neutral();
+    if (!$node->hasField('knowledge_audience')) {
+      return $access;
+    }
+    $audience = $node->knowledge_audience->value;
+    switch ($op) {
+      case 'update':
+        if ($audience != 'internal') {
+          if (!$account->hasPermission('edit knowledge audience external')) {
+            return AccessResult::forbidden();
+          }
+        }
+        if ($node->hasField('knowledge_governance') && $node->get('knowledge_governance')->value) {
+          if (!empty(array_intersect($account->getRoles(TRUE), $user_roles))) {
+            return AccessResult::allowed();
+          }
+          elseif (in_array($account->id(), $users)) {
+            return AccessResult::allowed();
+          }
+          else {
+            return AccessResult::forbidden();
+          }
+        }
+        break;
+
+      case 'view':
+        if ($audience != 'public') {
+          if (!$account->hasPermission('view knowledge audience ' . $audience)) {
+            return AccessResult::forbidden();
+          }
+        }
+        break;
+    }
+    return $access;
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_access().
+   */
+  #[Hook('user_role_access')]
+  public function userRoleAccess(EntityInterface $entity, $op, AccountInterface $account) {
+    if ($op != 'view') {
+      return AccessResult::neutral();
+    }
+    return AccessResult::allowedIfHasPermission($account, 'view knowledge governance');
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_ID_presave().
+   */
+  #[Hook('node_presave')]
+  public function nodePresave(EntityInterface $entity) {
+    if (!$entity->hasField('knowledge_governance')) {
+      return;
+    }
+    $governance = $entity->get('knowledge_governance')->value;
+    if ($governance) {
+      return;
+    }
+    if ($entity->hasField('knowledge_governance_user')) {
+      $entity->set('knowledge_governance_user', NULL);
+    }
+    if ($entity->hasField('knowledge_governance_user_role')) {
+      $entity->set('knowledge_governance_user_role', NULL);
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_ID_presave().
+   */
+  #[Hook('user_presave')]
+  public function userPresave(EntityInterface $entity) {
+    /** @var \Drupal\user\UserInterface $user */
+    $user = $entity;
+    \Drupal::service('knowledge.competency')->doRoleRemoval($user);
+    try {
+      $leadership_changed = FALSE;
+      if ($user->isNew()) {
+        $leadership_changed = TRUE;
+      }
+      else {
+        $old = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $user->getOriginal(), fn() => $user->original)->get('knowledge_leader')->target_id;
+        $new = $user->get('knowledge_leader')->target_id;
+        $leadership_changed = $new != $old;
+      }
+      if ($leadership_changed) {
+        $leader_svc = \Drupal::service('knowledge.leader');
+        if ($user->get('knowledge_leader')->target_id) {
+          $leader = $user->get('knowledge_leader')->entity;
+          $roles = $leader->getRoles(TRUE);
+          if (!in_array('knowledge_leader', $roles)) {
+            $leader_svc->addKnowledgeLeader($leader);
+          }
+        }
+        if (!$user->isNew()) {
+          if (DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $user->getOriginal(), fn() => $user->original)->get('knowledge_leader')->target_id) {
+            $original_leader = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $user->getOriginal(), fn() => $user->original)->get('knowledge_leader')->entity;
+            if ($original_leader) {
+              $leader_svc->updateKnowledgeLeader($user, $original_leader);
+            }
+          }
+        }
+      }
+    }
+    catch (\Exception $e) {
+      $logger = \Drupal::logger('knowledge');
+      Error::logException($logger, $e);
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_ID_delete().
+   */
+  #[Hook('user_delete')]
+  public function userDelete(EntityInterface $entity) {
+    try {
+      /** @var \Drupal\user\UserInterface $user */
+      $user = $entity;
+      if ($user->get('knowledge_leader')->target_id) {
+        $leader = $user->get('knowledge_leader')->entity;
+        $leader_svc = \Drupal::service('knowledge.leader');
+        $leader_svc->removeKnowledgeLeader($leader);
+      }
+    }
+    catch (\Exception $e) {
+      $logger = \Drupal::logger('knowledge');
+      Error::logException($logger, $e);
+    }
+  }
+
+  /**
+   * Implements hook_entity_field_access().
+   */
+  #[Hook('entity_field_access')]
+  public function entityFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
+    $permission = NULL;
+    $field_name = $field_definition->getName();
+    $view_fields = [
+      'knowledge_governance',
+      'knowledge_governance_user',
+      'knowledge_governance_user_role',
+      'knowledge_audience',
+      'moderation_state',
+    ];
+    $edit_fields = [
+      'knowledge_leader',
+      'knowledge_coach',
+      'knowledge_wave',
+      'knowledge_governance',
+      'knowledge_governance_user',
+      'knowledge_governance_user_role',
+    ];
+    if ($operation == 'view') {
+      if (!in_array($field_name, $view_fields)) {
+        return AccessResult::neutral();
+      }
+      switch ($field_name) {
+        case 'knowledge_governance':
+        case 'knowledge_governance_user':
+        case 'knowledge_governance_user_role':
+          $permission = 'view knowledge governance';
+          break;
+
+        case 'knowledge_audience':
+          $permission = 'view knowledge audience';
+          break;
+
+        case 'moderation_state':
+          $permission = 'view knowledge confidence';
+          break;
+      }
+    }
+    if ($operation == 'edit') {
+      if (!in_array($field_name, $edit_fields)) {
+        return AccessResult::neutral();
+      }
+      switch ($field_name) {
+        case 'knowledge_leader':
+          $permission = 'edit knowledge leader';
+          break;
+
+        case 'knowledge_coach':
+          $permission = 'edit knowledge coach';
+          break;
+
+        case 'knowledge_wave':
+          $permission = 'edit wave field';
+          break;
+
+        case 'knowledge_governance':
+        case 'knowledge_governance_user':
+        case 'knowledge_governance_user_role':
+          $permission = 'edit knowledge governance';
+          break;
+      }
+    }
+    if ($permission && $account->hasPermission($permission)) {
+      return AccessResult::allowed();
+    }
+    return AccessResult::forbidden();
+  }
+
+  /**
+   * Implements hook_views_post_execute().
+   */
+  #[Hook('views_post_execute')]
+  public function viewsPostExecute(ViewExecutable $view) {
+    // Only alter the knowledge views.
+    if (strpos($view->id(), 'knowledge') !== 0) {
+      return;
+    }
+    $view->result = SequenceService::fill($view->result);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_field_config_edit_form_alter')]
+  public function formFieldConfigEditFormAlter(&$form, FormStateInterface $form_state) {
+    $entity = $form['#entity'];
+    $entity_type = $entity->getEntityTypeId();
+    if ($entity_type != 'knowledge_quality') {
+      return;
+    }
+    $field_config = $form_state->getFormObject()->getEntity();
+    $field_type = $field_config->getType();
+    if ($field_type != 'boolean') {
+      return;
+    }
+    $quality_settings = \Drupal::config('knowledge.quality.settings');
+    $quality_categories = $quality_settings->get('categories');
+    $options_pipe = explode(PHP_EOL, $quality_categories);
+    $options = [
+      '_none' => $this->t('None'),
+    ];
+    foreach ($options_pipe as $option) {
+      $option = explode('|', $option);
+      $options[$option[0]] = $option[1] ?? $option[0];
+    }
+    $form['third_party_settings']['knowledge'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Knowledge Quality'),
+      '#open' => TRUE,
+    ];
+    $form['third_party_settings']['knowledge']['quality_category'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Category'),
+      '#description' => $this->t('Enter a custom setting value.'),
+      '#default_value' => $field_config->getThirdPartySetting('knowledge', 'quality_category', '_none'),
+      '#options' => $options,
+    ];
+    $form['actions']['submit']['#submit'][] = 'knowledge_quality_field_config_edit_form_submit';
+  }
+
+}
diff --git a/src/Hook/KnowledgeTokensHooks.php b/src/Hook/KnowledgeTokensHooks.php
new file mode 100644
index 0000000..2014f0f
--- /dev/null
+++ b/src/Hook/KnowledgeTokensHooks.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace Drupal\knowledge\Hook;
+
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Datetime\Entity\DateFormat;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for knowledge.
+ */
+class KnowledgeTokensHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public function tokenInfo() {
+    $type = [
+      'name' => $this->t('Knowledge'),
+      'description' => $this->t('Tokens for knowledge posted on the site.'),
+      'needs-data' => 'knowledge',
+    ];
+    $tokens = [];
+    // Provides an integration for each entity type except knowledge.
+    foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+      if ($entity_type_id == 'knowledge' || !$entity_type->entityClassImplements(ContentEntityInterface::class)) {
+        continue;
+      }
+      if (\Drupal::service('knowledge.manager')->getFields($entity_type_id)) {
+        // Get the correct token type.
+        $token_type = $entity_type_id == 'taxonomy_term' ? 'term' : $entity_type_id;
+        // @todo Make this work per field. See https://www.drupal.org/node/2031903.
+        $tokens[$token_type]['knowledge-count'] = [
+          'name' => $this->t("Knowledge count"),
+          'description' => $this->t("The number of knowledge posted on an entity."),
+        ];
+        $tokens[$token_type]['knowledge-count-new'] = [
+          'name' => $this->t("New knowledge count"),
+          'description' => $this->t("The number of knowledge posted on an entity since the reader last viewed it."),
+        ];
+      }
+    }
+    // Core knowledge tokens.
+    $knowledge['kid'] = [
+      'name' => $this->t("Knowledge ID"),
+      'description' => $this->t("The unique ID of the knowledge."),
+    ];
+    $knowledge['title'] = [
+      'name' => $this->t("Title"),
+      'description' => $this->t("The title of the knowledge."),
+    ];
+    $knowledge['langcode'] = [
+      'name' => $this->t('Language code'),
+      'description' => $this->t('The language code of the language the knowledge is written in.'),
+    ];
+    $knowledge['url'] = [
+      'name' => $this->t("URL"),
+      'description' => $this->t("The URL of the knowledge."),
+    ];
+    $knowledge['edit-url'] = [
+      'name' => $this->t("Edit URL"),
+      'description' => $this->t("The URL of the knowledge's edit page."),
+    ];
+    // Chained tokens for knowledge.
+    $knowledge['created'] = [
+      'name' => $this->t("Date created"),
+      'description' => $this->t("The date the knowledge was posted."),
+      'type' => 'date',
+    ];
+    $knowledge['changed'] = [
+      'name' => $this->t("Date changed"),
+      'description' => $this->t("The date the knowledge was most recently updated."),
+      'type' => 'date',
+    ];
+    $knowledge['entity'] = [
+      'name' => $this->t("Entity"),
+      'description' => $this->t("The entity the knowledge was posted to."),
+      'type' => 'entity',
+    ];
+    $knowledge['author'] = [
+      'name' => $this->t("Author"),
+      'description' => $this->t("The author name of the knowledge."),
+      'type' => 'user',
+    ];
+    return [
+      'types' => [
+        'knowledge' => $type,
+      ],
+      'tokens' => [
+        'knowledge' => $knowledge,
+      ] + $tokens,
+    ];
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
+    $token_service = \Drupal::token();
+    $url_options = [
+      'absolute' => TRUE,
+    ];
+    if (isset($options['langcode'])) {
+      $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
+      $langcode = $options['langcode'];
+    }
+    else {
+      $langcode = NULL;
+    }
+    $replacements = [];
+    if ($type == 'knowledge' && !empty($data['knowledge'])) {
+      /** @var \Drupal\knowledge\KnowledgeInterface $knowledge */
+      $knowledge = $data['knowledge'];
+      foreach ($tokens as $name => $original) {
+        switch ($name) {
+          // Simple key values on the knowledge.
+          case 'kid':
+            $replacements[$original] = $knowledge->id();
+            break;
+
+          case 'title':
+            $replacements[$original] = $knowledge->label();
+            break;
+
+          case 'langcode':
+            $replacements[$original] = $knowledge->language()->getId();
+            break;
+
+          // Knowledge related URLs.
+          case 'url':
+            $url_options['fragment'] = 'knowledge-' . $knowledge->id();
+            $replacements[$original] = $knowledge->toUrl('canonical', $url_options)->toString();
+            break;
+
+          case 'edit-url':
+            $url_options['fragment'] = NULL;
+            $replacements[$original] = $knowledge->toUrl('edit-form', $url_options)->toString();
+            break;
+
+          case 'author':
+            $name = $knowledge->getAuthorName();
+            // Add the user cacheability metadata in case the author of the
+            // knowledge is not the anonymous user.
+            if ($knowledge->getOwnerId()) {
+              $bubbleable_metadata->addCacheableDependency($knowledge->getOwner());
+            }
+            $replacements[$original] = $name;
+            break;
+
+          case 'created':
+            $date_format = DateFormat::load('medium');
+            $bubbleable_metadata->addCacheableDependency($date_format);
+            $replacements[$original] = \Drupal::service('date.formatter')->format($knowledge->getCreatedTime(), 'medium', '', NULL, $langcode);
+            break;
+
+          case 'changed':
+            $date_format = DateFormat::load('medium');
+            $bubbleable_metadata->addCacheableDependency($date_format);
+            $replacements[$original] = \Drupal::service('date.formatter')->format($knowledge->getChangedTime(), 'medium', '', NULL, $langcode);
+            break;
+
+          case 'entity':
+            $entity = $knowledge->getKnowledgeedEntity();
+            $bubbleable_metadata->addCacheableDependency($entity);
+            $title = $entity->label();
+            $replacements[$original] = $title;
+            break;
+        }
+      }
+      // Chained token relationships.
+      if ($entity_tokens = $token_service->findwithPrefix($tokens, 'entity')) {
+        $entity = $knowledge->getKnowledgeedEntity();
+        $replacements += $token_service->generate($knowledge->getKnowledgeedEntityTypeId(), $entity_tokens, [
+          $knowledge->getKnowledgeedEntityTypeId() => $entity,
+        ], $options, $bubbleable_metadata);
+      }
+      if ($date_tokens = $token_service->findwithPrefix($tokens, 'created')) {
+        $replacements += $token_service->generate('date', $date_tokens, [
+          'date' => $knowledge->getCreatedTime(),
+        ], $options, $bubbleable_metadata);
+      }
+      if ($date_tokens = $token_service->findwithPrefix($tokens, 'changed')) {
+        $replacements += $token_service->generate('date', $date_tokens, [
+          'date' => $knowledge->getChangedTime(),
+        ], $options, $bubbleable_metadata);
+      }
+      if (($author_tokens = $token_service->findwithPrefix($tokens, 'author')) && $account = $knowledge->getOwner()) {
+        $replacements += $token_service->generate('user', $author_tokens, [
+          'user' => $account,
+        ], $options, $bubbleable_metadata);
+      }
+    }
+    elseif (!empty($data[$type]) && $data[$type] instanceof FieldableEntityInterface) {
+      /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
+      $entity = $data[$type];
+      foreach ($tokens as $name => $original) {
+        switch ($name) {
+          case 'knowledge-count':
+            $count = 0;
+            $fields = array_keys(\Drupal::service('knowledge.manager')->getFields($entity->getEntityTypeId()));
+            $definitions = array_keys($entity->getFieldDefinitions());
+            $valid_fields = array_intersect($fields, $definitions);
+            foreach ($valid_fields as $field_name) {
+              /** @var \Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItemInterface $field */
+              $field = $entity->get($field_name);
+              $count += $field->total_count;
+            }
+            $replacements[$original] = $count;
+            break;
+
+          case 'knowledge-count-new':
+            $replacements[$original] = \Drupal::service('knowledge.manager')->getCountNewKnowledges($entity);
+            break;
+        }
+      }
+    }
+    return $replacements;
+  }
+
+}
diff --git a/src/Hook/KnowledgeViewsHooks.php b/src/Hook/KnowledgeViewsHooks.php
new file mode 100644
index 0000000..6a3532f
--- /dev/null
+++ b/src/Hook/KnowledgeViewsHooks.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace Drupal\knowledge\Hook;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for knowledge.
+ */
+class KnowledgeViewsHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_views_data_alter().
+   */
+  #[Hook('views_data_alter')]
+  public function viewsDataAlter(&$data) {
+    $data['node_field_data']['knowledge_node_type'] = [
+      'title' => $this->t('Knowledge Content Types'),
+      'group' => $this->t('Content'),
+      'filter' => [
+        'title' => $this->t('Knowledge Content Types'),
+        'help' => $this->t('Limit to "knowledge" content types.'),
+        'field' => 'type',
+        'id' => 'knowledge_node_type',
+      ],
+    ];
+    $data['node_field_data']['knowledge_merge'] = [
+      'field' => [
+        'title' => $this->t('Merge link'),
+        'help' => $this->t('Provide a simple link to merge duplicate articles.'),
+        'id' => 'knowledge_merge_link',
+      ],
+    ];
+    $data['knowledge_adherence']['knowledge_disposition'] = [
+      'title' => $this->t('Disposition'),
+      'group' => $this->t('Adherence'),
+      'filter' => [
+        'title' => $this->t('Knowledge Adherence Disposition'),
+        'help' => $this->t('Filters by disposition.'),
+        'field' => 'disposition',
+        'id' => 'knowledge_disposition',
+      ],
+    ];
+    $data['users_field_data']['knowledge_learners'] = [
+      'title' => $this->t('Knowledge Learners'),
+      'group' => $this->t('User'),
+      'filter' => [
+        'title' => $this->t('Knowledge Learners'),
+        'help' => $this->t('Limit a coaches learners.'),
+        'field' => 'uid',
+        'id' => 'knowledge_learners',
+      ],
+    ];
+    $data['users_field_data']['knowledge_leader_coach'] = [
+      'title' => $this->t('Knowledge Leader Coach'),
+      'help' => $this->t("The user's coach is also their leader."),
+      'filter' => [
+        'id' => 'knowledge_leader_coach',
+      ],
+    ];
+    // New knowledge is only supported for node table because it requires the
+    // history table.
+    $data['node']['new_knowledge'] = [
+      'title' => $this->t('New knowledge'),
+      'help' => $this->t('The amount of new knowledge on the node.'),
+      'field' => [
+        'id' => 'node_new_knowledge',
+        'no group by' => TRUE,
+      ],
+    ];
+    // Provides an integration for each entity type except knowledge.
+    foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+      if ($entity_type_id == 'knowledge' || !$entity_type->entityClassImplements(ContentEntityInterface::class) || !$entity_type->getBaseTable()) {
+        continue;
+      }
+      $fields = \Drupal::service('knowledge.manager')->getFields($entity_type_id);
+      $base_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
+      $args = [
+        '@entity_type' => $entity_type_id,
+      ];
+      if ($fields) {
+        $data[$base_table]['knowledge_link'] = [
+          'field' => [
+            'title' => $this->t('Add knowledge link'),
+            'help' => $this->t('Display the standard add knowledge link used on regular @entity_type, which will only display if the viewing user has access to add a knowledge.', $args),
+            'id' => 'knowledge_entity_link',
+          ],
+        ];
+        // Multilingual properties are stored in data table.
+        if (!$table = $entity_type->getDataTable()) {
+          $table = $entity_type->getBaseTable();
+        }
+        $data[$table]['uid_touch'] = [
+          'title' => $this->t('User posted or knowledge'),
+          'help' => $this->t('Display nodes only if a user posted the @entity_type or knowledge on the @entity_type.', $args),
+          'argument' => [
+            'field' => 'uid',
+            'name table' => 'users_field_data',
+            'name field' => 'name',
+            'id' => 'argument_knowledge_user_uid',
+            'no group by' => TRUE,
+            'entity_type' => $entity_type_id,
+            'entity_id' => $entity_type->getKey('id'),
+          ],
+          'filter' => [
+            'field' => 'uid',
+            'name table' => 'users_field_data',
+            'name field' => 'name',
+            'id' => 'knowledge_user_uid',
+            'entity_type' => $entity_type_id,
+            'entity_id' => $entity_type->getKey('id'),
+          ],
+        ];
+        foreach ($fields as $field_name => $field) {
+          $data[$base_table][$field_name . '_kid'] = [
+            'title' => $this->t('Knowledge of the @entity_type using field: @field_name', $args + [
+              '@field_name' => $field_name,
+            ]),
+            'help' => $this->t('Relate all knowledge on the @entity_type. This will create 1 duplicate record for every knowledge. Usually if you need this it is better to create a knowledge view.', $args),
+            'relationship' => [
+              'group' => $this->t('Knowledge'),
+              'label' => $this->t('Knowledge'),
+              'base' => 'knowledge',
+              'base field' => 'entity_id',
+              'relationship field' => $entity_type->getKey('id'),
+              'id' => 'standard',
+              'extra' => [
+                        [
+                          'field' => 'entity_type',
+                          'value' => $entity_type_id,
+                        ],
+                        [
+                          'field' => 'field_name',
+                          'value' => $field_name,
+                        ],
+              ],
+            ],
+          ];
+        }
+      }
+    }
+    $data['users_field_data']['knowledge_approve'] = [
+      'title' => $this->t('Knowledge Competency Approval'),
+      'help' => $this->t("Link to approve user's promotion."),
+      'field' => [
+        'id' => 'knowledge_competency_approve',
+      ],
+    ];
+    $data['knowledge_competency']['progress'] = [
+      'title' => $this->t("Progress"),
+      'help' => $this->t('The progress of mastery.'),
+      'field' => [
+        'id' => 'knowledge_competency_percentage',
+        'no group by' => TRUE,
+      ],
+      'sort' => [
+        'id' => 'knowledge_competency_percentage',
+        'no group by' => TRUE,
+      ],
+    ];
+  }
+
+}
diff --git a/src/KnowledgeLazyBuilders.php b/src/KnowledgeLazyBuilders.php
index 9c7f7a4..465ced4 100644
--- a/src/KnowledgeLazyBuilders.php
+++ b/src/KnowledgeLazyBuilders.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\knowledge;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\EntityFormBuilderInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -297,7 +298,7 @@ class KnowledgeLazyBuilders implements TrustedCallbackInterface {
    * Wraps content_translation_translate_access.
    */
   protected function access(EntityInterface $entity) {
-    return content_translation_translate_access($entity);
+    return DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('content_translation.manager')->access($entity), fn() => content_translation_translate_access($entity));
   }
 
   /**
diff --git a/src/Plugin/Block/ArticleAudience.php b/src/Plugin/Block/ArticleAudience.php
index 815a9dd..b4a9ce1 100644
--- a/src/Plugin/Block/ArticleAudience.php
+++ b/src/Plugin/Block/ArticleAudience.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\knowledge\Plugin\Block;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Database\Statement\FetchAs;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Database\Connection;
@@ -134,8 +136,9 @@ class ArticleAudience extends BlockBase implements ContainerFactoryPluginInterfa
     }
 
     $contentStateQuery = $this->contentTypeQuery();
-    $states = $this->database->query($contentStateQuery, $parameters)
-      ->fetchAllAssoc('knowledge_audience_value', \PDO::FETCH_ASSOC);
+    $states = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $this->database->query($contentStateQuery, $parameters)
+      ->fetchAllAssoc('knowledge_audience_value', FetchAs::Associative), fn() => $this->database->query($contentStateQuery, $parameters)
+      ->fetchAllAssoc('knowledge_audience_value', \PDO::FETCH_ASSOC));
     sort($states);
 
     if (!$states) {
@@ -182,8 +185,6 @@ class ArticleAudience extends BlockBase implements ContainerFactoryPluginInterfa
       ],
     ];
 
-    $this->renderer->addCacheableDependency($build, $config);
-
     return $build;
   }
 
diff --git a/src/Plugin/Block/ArticleStates.php b/src/Plugin/Block/ArticleStates.php
index d7f909e..900a0e3 100644
--- a/src/Plugin/Block/ArticleStates.php
+++ b/src/Plugin/Block/ArticleStates.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\knowledge\Plugin\Block;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Database\Statement\FetchAs;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Database\Connection;
@@ -134,8 +136,9 @@ class ArticleStates extends BlockBase implements ContainerFactoryPluginInterface
     }
 
     $contentStateQuery = $this->contentStateQuery();
-    $states = $this->database->query($contentStateQuery, $parameters)
-      ->fetchAllAssoc('moderation_state', \PDO::FETCH_ASSOC);
+    $states = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $this->database->query($contentStateQuery, $parameters)
+      ->fetchAllAssoc('moderation_state', FetchAs::Associative), fn() => $this->database->query($contentStateQuery, $parameters)
+      ->fetchAllAssoc('moderation_state', \PDO::FETCH_ASSOC));
     sort($states);
 
     if (!$states) {
@@ -182,8 +185,6 @@ class ArticleStates extends BlockBase implements ContainerFactoryPluginInterface
       ],
     ];
 
-    $this->renderer->addCacheableDependency($build, $config);
-
     return $build;
   }
 
diff --git a/src/Plugin/Block/ArticleTypes.php b/src/Plugin/Block/ArticleTypes.php
index 867fd64..479680e 100644
--- a/src/Plugin/Block/ArticleTypes.php
+++ b/src/Plugin/Block/ArticleTypes.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\knowledge\Plugin\Block;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Database\Statement\FetchAs;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Cache\Cache;
@@ -135,8 +137,9 @@ class ArticleTypes extends BlockBase implements ContainerFactoryPluginInterface
     }
 
     $contentStateQuery = $this->contentTypeQuery();
-    $states = $this->database->query($contentStateQuery, $parameters)
-      ->fetchAllAssoc('type', \PDO::FETCH_ASSOC);
+    $states = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $this->database->query($contentStateQuery, $parameters)
+      ->fetchAllAssoc('type', FetchAs::Associative), fn() => $this->database->query($contentStateQuery, $parameters)
+      ->fetchAllAssoc('type', \PDO::FETCH_ASSOC));
     sort($states);
 
     if (!$states) {
@@ -183,8 +186,6 @@ class ArticleTypes extends BlockBase implements ContainerFactoryPluginInterface
       ],
     ];
 
-    $this->renderer->addCacheableDependency($build, $config);
-
     return $build;
   }
 
diff --git a/src/Plugin/Block/KnowledgeActivity.php b/src/Plugin/Block/KnowledgeActivity.php
index 8b70858..936b02b 100644
--- a/src/Plugin/Block/KnowledgeActivity.php
+++ b/src/Plugin/Block/KnowledgeActivity.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\knowledge\Plugin\Block;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Database\Statement\FetchAs;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Database\Connection;
@@ -158,12 +160,14 @@ class KnowledgeActivity extends BlockBase implements ContainerFactoryPluginInter
       $parameters[':uid'] = $uid;
     }
     $linkedContentQuery = $this->contentLinkedQuery();
-    $results2 = $this->database->query($linkedContentQuery, $parameters)
-      ->fetchAllAssoc('date', \PDO::FETCH_ASSOC);
+    $results2 = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $this->database->query($linkedContentQuery, $parameters)
+      ->fetchAllAssoc('date', FetchAs::Associative), fn() => $this->database->query($linkedContentQuery, $parameters)
+      ->fetchAllAssoc('date', \PDO::FETCH_ASSOC));
 
     $createdContentQuery = $this->contentCreatedQuery();
-    $results1 = $this->database->query($createdContentQuery, $parameters)
-      ->fetchAllAssoc('date', \PDO::FETCH_ASSOC);
+    $results1 = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $this->database->query($createdContentQuery, $parameters)
+      ->fetchAllAssoc('date', FetchAs::Associative), fn() => $this->database->query($createdContentQuery, $parameters)
+      ->fetchAllAssoc('date', \PDO::FETCH_ASSOC));
 
     $dates = array_merge(array_keys($results1), array_keys($results2));
     $dates = array_unique($dates);
@@ -220,8 +224,6 @@ class KnowledgeActivity extends BlockBase implements ContainerFactoryPluginInter
       ],
     ];
 
-    $this->renderer->addCacheableDependency($build, $config);
-
     return $build;
   }
 
diff --git a/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.module b/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.module
index 0625780..ec690d1 100644
--- a/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.module
+++ b/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.module
@@ -5,24 +5,22 @@
  * A module for testing making knowledge base fields' displays configurable.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge_display_configurable_test\Hook\KnowledgeDisplayConfigurableTestHooks;
 use Drupal\Core\Entity\EntityTypeInterface;
 
 /**
  * Implements hook_entity_base_field_info_alter().
  */
+#[LegacyHook]
 function knowledge_display_configurable_test_entity_base_field_info_alter(&$base_field_definitions, EntityTypeInterface $entity_type) {
-  if ($entity_type->id() == 'knowledge') {
-    foreach (['created', 'uid'] as $field) {
-      /** @var \Drupal\Core\Field\BaseFieldDefinition[] $base_field_definitions */
-      $base_field_definitions[$field]->setDisplayConfigurable('view', TRUE);
-    }
-  }
+  \Drupal::service(KnowledgeDisplayConfigurableTestHooks::class)->entityBaseFieldInfoAlter($base_field_definitions, $entity_type);
 }
 
 /**
  * Implements hook_entity_type_build().
  */
+#[LegacyHook]
 function knowledge_display_configurable_test_entity_type_build(array &$entity_types) {
-  // Allow skipping of extra preprocessing for configurable display.
-  $entity_types['knowledge']->set('enable_base_field_custom_preprocess_skipping', TRUE);
+  \Drupal::service(KnowledgeDisplayConfigurableTestHooks::class)->entityTypeBuild($entity_types);
 }
diff --git a/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.services.yml b/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.services.yml
new file mode 100644
index 0000000..4479836
--- /dev/null
+++ b/tests/modules/knowledge_display_configurable_test/knowledge_display_configurable_test.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\knowledge_display_configurable_test\Hook\KnowledgeDisplayConfigurableTestHooks:
+    class: Drupal\knowledge_display_configurable_test\Hook\KnowledgeDisplayConfigurableTestHooks
+    autowire: true
diff --git a/tests/modules/knowledge_display_configurable_test/src/Hook/KnowledgeDisplayConfigurableTestHooks.php b/tests/modules/knowledge_display_configurable_test/src/Hook/KnowledgeDisplayConfigurableTestHooks.php
new file mode 100644
index 0000000..30ee14b
--- /dev/null
+++ b/tests/modules/knowledge_display_configurable_test/src/Hook/KnowledgeDisplayConfigurableTestHooks.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\knowledge_display_configurable_test\Hook;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for knowledge_display_configurable_test.
+ */
+class KnowledgeDisplayConfigurableTestHooks {
+
+  /**
+   * Implements hook_entity_base_field_info_alter().
+   */
+  #[Hook('entity_base_field_info_alter')]
+  public function entityBaseFieldInfoAlter(&$base_field_definitions, EntityTypeInterface $entity_type) {
+    if ($entity_type->id() == 'knowledge') {
+      foreach ([
+        'created',
+        'uid',
+      ] as $field) {
+        /** @var \Drupal\Core\Field\BaseFieldDefinition[] $base_field_definitions */
+        $base_field_definitions[$field]->setDisplayConfigurable('view', TRUE);
+      }
+    }
+  }
+
+  /**
+   * Implements hook_entity_type_build().
+   */
+  #[Hook('entity_type_build')]
+  public function entityTypeBuild(array &$entity_types) {
+    // Allow skipping of extra preprocessing for configurable display.
+    $entity_types['knowledge']->set('enable_base_field_custom_preprocess_skipping', TRUE);
+  }
+
+}
diff --git a/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.module b/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.module
index 940ba3f..72abb5d 100644
--- a/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.module
+++ b/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.module
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge_empty_title_test\Hook\KnowledgeEmptyTitleTestHooks;
+
 /**
  * @file
  * Dummy module emptying knowledge titles.
@@ -11,6 +18,7 @@
 /**
  * Implements hook_preprocess_knowledge().
  */
+#[LegacyHook]
 function knowledge_empty_title_test_preprocess_knowledge(&$variables) {
-  $variables['title'] = '';
+  \Drupal::service(KnowledgeEmptyTitleTestHooks::class)->preprocessKnowledge($variables);
 }
diff --git a/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.services.yml b/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.services.yml
new file mode 100644
index 0000000..6651570
--- /dev/null
+++ b/tests/modules/knowledge_empty_title_test/knowledge_empty_title_test.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\knowledge_empty_title_test\Hook\KnowledgeEmptyTitleTestHooks:
+    class: Drupal\knowledge_empty_title_test\Hook\KnowledgeEmptyTitleTestHooks
+    autowire: true
diff --git a/tests/modules/knowledge_empty_title_test/src/Hook/KnowledgeEmptyTitleTestHooks.php b/tests/modules/knowledge_empty_title_test/src/Hook/KnowledgeEmptyTitleTestHooks.php
new file mode 100644
index 0000000..6fab6a1
--- /dev/null
+++ b/tests/modules/knowledge_empty_title_test/src/Hook/KnowledgeEmptyTitleTestHooks.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\knowledge_empty_title_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for knowledge_empty_title_test.
+ */
+class KnowledgeEmptyTitleTestHooks {
+  /**
+   * @file
+   * Dummy module emptying knowledge titles.
+   *
+   * To test for appropriate and accessible markup in edge case scenarios where
+   * knowledge have empty titles.
+   */
+
+  /**
+   * Implements hook_preprocess_knowledge().
+   */
+  #[Hook('preprocess_knowledge')]
+  public function preprocessKnowledge(&$variables) {
+    $variables['title'] = '';
+  }
+
+}
diff --git a/tests/modules/knowledge_test/knowledge_test.module b/tests/modules/knowledge_test/knowledge_test.module
index be450e0..7e19d5e 100644
--- a/tests/modules/knowledge_test/knowledge_test.module
+++ b/tests/modules/knowledge_test/knowledge_test.module
@@ -5,39 +5,22 @@
  * Dummy module implementing knowledge related hooks to test API interaction.
  */
 
-use Drupal\Core\Url;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\knowledge_test\Hook\KnowledgeTestHooks;
 use Drupal\knowledge\KnowledgeInterface;
 
 /**
  * Implements hook_entity_type_alter().
  */
+#[LegacyHook]
 function knowledge_test_entity_type_alter(array &$entity_types) {
-  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
-  if (\Drupal::languageManager()->isMultilingual()) {
-    // Enable language handling for knowledge fields.
-    $translation = $entity_types['knowledge']->get('translation');
-    $translation['knowledge_test'] = TRUE;
-    $entity_types['knowledge']->set('translation', $translation);
-  }
+  \Drupal::service(KnowledgeTestHooks::class)->entityTypeAlter($entity_types);
 }
 
 /**
  * Implements hook_knowledge_links_alter().
  */
+#[LegacyHook]
 function knowledge_test_knowledge_links_alter(array &$links, KnowledgeInterface &$entity, array &$context) {
-  // Allow tests to enable or disable this alter hook.
-  if (!\Drupal::state()->get('knowledge_test_links_alter_enabled', FALSE)) {
-    return;
-  }
-
-  $links['knowledge_test'] = [
-    '#theme' => 'links__knowledge__knowledge_test',
-    '#attributes' => ['class' => ['links', 'inline']],
-    '#links' => [
-      'knowledge-report' => [
-        'title' => t('Report'),
-        'url' => Url::fromRoute('knowledge_test.report', ['knowledge' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("knowledge/{$entity->id()}/report")]]),
-      ],
-    ],
-  ];
+  \Drupal::service(KnowledgeTestHooks::class)->knowledgeLinksAlter($links, $entity, $context);
 }
diff --git a/tests/modules/knowledge_test/knowledge_test.services.yml b/tests/modules/knowledge_test/knowledge_test.services.yml
new file mode 100644
index 0000000..8aeaaea
--- /dev/null
+++ b/tests/modules/knowledge_test/knowledge_test.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\knowledge_test\Hook\KnowledgeTestHooks:
+    class: Drupal\knowledge_test\Hook\KnowledgeTestHooks
+    autowire: true
diff --git a/tests/modules/knowledge_test/src/Hook/KnowledgeTestHooks.php b/tests/modules/knowledge_test/src/Hook/KnowledgeTestHooks.php
new file mode 100644
index 0000000..a3212d8
--- /dev/null
+++ b/tests/modules/knowledge_test/src/Hook/KnowledgeTestHooks.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\knowledge_test\Hook;
+
+use Drupal\Core\Url;
+use Drupal\knowledge\KnowledgeInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for knowledge_test.
+ */
+class KnowledgeTestHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_entity_type_alter().
+   */
+  #[Hook('entity_type_alter')]
+  public function entityTypeAlter(array &$entity_types) {
+    /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
+    if (\Drupal::languageManager()->isMultilingual()) {
+      // Enable language handling for knowledge fields.
+      $translation = $entity_types['knowledge']->get('translation');
+      $translation['knowledge_test'] = TRUE;
+      $entity_types['knowledge']->set('translation', $translation);
+    }
+  }
+
+  /**
+   * Implements hook_knowledge_links_alter().
+   */
+  #[Hook('knowledge_links_alter')]
+  public function knowledgeLinksAlter(array &$links, KnowledgeInterface &$entity, array &$context) {
+    // Allow tests to enable or disable this alter hook.
+    if (!\Drupal::state()->get('knowledge_test_links_alter_enabled', FALSE)) {
+      return;
+    }
+    $links['knowledge_test'] = [
+      '#theme' => 'links__knowledge__knowledge_test',
+      '#attributes' => [
+        'class' => [
+          'links',
+          'inline',
+        ],
+      ],
+      '#links' => [
+        'knowledge-report' => [
+          'title' => $this->t('Report'),
+          'url' => Url::fromRoute('knowledge_test.report', [
+            'knowledge' => $entity->id(),
+          ], [
+            'query' => [
+              'token' => \Drupal::getContainer()->get('csrf_token')->get("knowledge/{$entity->id()}/report"),
+            ],
+          ]),
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Functional/CompetencyPromotionTest.php b/tests/src/Functional/CompetencyPromotionTest.php
index 7ee4153..3d5f470 100644
--- a/tests/src/Functional/CompetencyPromotionTest.php
+++ b/tests/src/Functional/CompetencyPromotionTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\BrowserTestBase;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\BrowserTestBase;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class CompetencyPromotionTest extends BrowserTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeAccessTest.php b/tests/src/Functional/KnowledgeAccessTest.php
index 49a6d62..09bba77 100644
--- a/tests/src/Functional/KnowledgeAccessTest.php
+++ b/tests/src/Functional/KnowledgeAccessTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\knowledge\Entity\Knowledge;
 use Drupal\knowledge\Tests\KnowledgeTestTrait;
@@ -12,6 +14,8 @@ use Drupal\node\Entity\NodeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeAccessTest extends BrowserTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Functional/KnowledgeAdminTest.php b/tests/src/Functional/KnowledgeAdminTest.php
index b39854f..2d0c16f 100644
--- a/tests/src/Functional/KnowledgeAdminTest.php
+++ b/tests/src/Functional/KnowledgeAdminTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Utility\Html;
 use Drupal\knowledge\Entity\Knowledge;
 use Drupal\user\RoleInterface;
@@ -11,6 +13,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeAdminTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeAnonymousTest.php b/tests/src/Functional/KnowledgeAnonymousTest.php
index ee2d768..ae7b372 100644
--- a/tests/src/Functional/KnowledgeAnonymousTest.php
+++ b/tests/src/Functional/KnowledgeAnonymousTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\knowledge\KnowledgeInterface;
 use Drupal\user\RoleInterface;
 
@@ -10,6 +12,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeAnonymousTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeBlockTest.php b/tests/src/Functional/KnowledgeBlockTest.php
index eb66423..0862a61 100644
--- a/tests/src/Functional/KnowledgeBlockTest.php
+++ b/tests/src/Functional/KnowledgeBlockTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\user\RoleInterface;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeBlockTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeCSSTest.php b/tests/src/Functional/KnowledgeCSSTest.php
index 799ca8e..8d3f928 100644
--- a/tests/src/Functional/KnowledgeCSSTest.php
+++ b/tests/src/Functional/KnowledgeCSSTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Tests\Traits\Core\GeneratePermutationsTrait;
 use Drupal\knowledge\Entity\Knowledge;
@@ -13,6 +15,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeCSSTest extends KnowledgeTestBase {
 
   use GeneratePermutationsTrait;
diff --git a/tests/src/Functional/KnowledgeCacheTagsTest.php b/tests/src/Functional/KnowledgeCacheTagsTest.php
index 3719169..8021456 100644
--- a/tests/src/Functional/KnowledgeCacheTagsTest.php
+++ b/tests/src/Functional/KnowledgeCacheTagsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Language\LanguageInterface;
@@ -20,6 +22,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Functional/KnowledgeDisplayConfigurableTest.php b/tests/src/Functional/KnowledgeDisplayConfigurableTest.php
index 6cae16c..d1d2314 100644
--- a/tests/src/Functional/KnowledgeDisplayConfigurableTest.php
+++ b/tests/src/Functional/KnowledgeDisplayConfigurableTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\knowledge\Entity\Knowledge;
@@ -13,6 +15,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeDisplayConfigurableTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeEntityTest.php b/tests/src/Functional/KnowledgeEntityTest.php
index 55b0506..b8e2ae8 100644
--- a/tests/src/Functional/KnowledgeEntityTest.php
+++ b/tests/src/Functional/KnowledgeEntityTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
 use Drupal\knowledge\Entity\Knowledge;
@@ -14,6 +16,8 @@ use Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItemInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeEntityTest extends KnowledgeTestBase {
 
   use TaxonomyTestTrait;
diff --git a/tests/src/Functional/KnowledgeFieldsTest.php b/tests/src/Functional/KnowledgeFieldsTest.php
index b84bbff..2470c13 100644
--- a/tests/src/Functional/KnowledgeFieldsTest.php
+++ b/tests/src/Functional/KnowledgeFieldsTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\knowledge\Entity\KnowledgeType;
 
@@ -10,6 +13,8 @@ use Drupal\knowledge\Entity\KnowledgeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeFieldsTest extends KnowledgeTestBase {
 
   /**
@@ -164,7 +169,7 @@ class KnowledgeFieldsTest extends KnowledgeTestBase {
 
     // Purge field data now to allow knowledge module to be uninstalled once the
     // field has been deleted.
-    field_purge_batch(10);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('Drupal\Core\Field\FieldPurger')->purgeBatch(10), fn() => field_purge_batch(10));
 
     // Uninstall the knowledge module.
     $edit = [];
diff --git a/tests/src/Functional/KnowledgeInterfaceTest.php b/tests/src/Functional/KnowledgeInterfaceTest.php
index fb25fc1..0e6e9df 100644
--- a/tests/src/Functional/KnowledgeInterfaceTest.php
+++ b/tests/src/Functional/KnowledgeInterfaceTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Entity\Entity\EntityViewMode;
 use Drupal\Core\Url;
@@ -16,6 +18,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeInterfaceTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeLanguageTest.php b/tests/src/Functional/KnowledgeLanguageTest.php
index 91b1dc8..6edd576 100644
--- a/tests/src/Functional/KnowledgeLanguageTest.php
+++ b/tests/src/Functional/KnowledgeLanguageTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -14,6 +16,8 @@ use Drupal\knowledge\Tests\KnowledgeTestTrait;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeLanguageTest extends BrowserTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Functional/KnowledgeLinksAlterTest.php b/tests/src/Functional/KnowledgeLinksAlterTest.php
index 2f1d013..28344ad 100644
--- a/tests/src/Functional/KnowledgeLinksAlterTest.php
+++ b/tests/src/Functional/KnowledgeLinksAlterTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Tests knowledge links altering.
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeLinksAlterTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeLinksTest.php b/tests/src/Functional/KnowledgeLinksTest.php
index 002136c..94912fe 100644
--- a/tests/src/Functional/KnowledgeLinksTest.php
+++ b/tests/src/Functional/KnowledgeLinksTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\knowledge\Entity\Knowledge;
 use Drupal\knowledge\KnowledgeInterface;
@@ -14,6 +16,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeLinksTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeNewIndicatorTest.php b/tests/src/Functional/KnowledgeNewIndicatorTest.php
index 1568de7..744819b 100644
--- a/tests/src/Functional/KnowledgeNewIndicatorTest.php
+++ b/tests/src/Functional/KnowledgeNewIndicatorTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Serialization\Json;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Url;
@@ -13,6 +15,8 @@ use Drupal\knowledge\KnowledgeInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeNewIndicatorTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeNodeAccessTest.php b/tests/src/Functional/KnowledgeNodeAccessTest.php
index 48e6539..ad08e34 100644
--- a/tests/src/Functional/KnowledgeNodeAccessTest.php
+++ b/tests/src/Functional/KnowledgeNodeAccessTest.php
@@ -2,6 +2,10 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\node\NodeAccessRebuild;
 use Drupal\knowledge\KnowledgeManagerInterface;
 
 /**
@@ -12,6 +16,8 @@ use Drupal\knowledge\KnowledgeManagerInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeNodeAccessTest extends KnowledgeTestBase {
 
   /**
@@ -32,7 +38,7 @@ class KnowledgeNodeAccessTest extends KnowledgeTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    node_access_rebuild();
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service(NodeAccessRebuild::class)->rebuild(), fn() => node_access_rebuild());
 
     // Re-create user.
     $this->webUser = $this->drupalCreateUser([
diff --git a/tests/src/Functional/KnowledgeNodeChangesTest.php b/tests/src/Functional/KnowledgeNodeChangesTest.php
index e9017e4..0936e35 100644
--- a/tests/src/Functional/KnowledgeNodeChangesTest.php
+++ b/tests/src/Functional/KnowledgeNodeChangesTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\knowledge\Entity\Knowledge;
@@ -11,6 +13,8 @@ use Drupal\knowledge\Entity\Knowledge;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeNodeChangesTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeNonNodeTest.php b/tests/src/Functional/KnowledgeNonNodeTest.php
index b5ad971..12cb7c1 100644
--- a/tests/src/Functional/KnowledgeNonNodeTest.php
+++ b/tests/src/Functional/KnowledgeNonNodeTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Tests\BrowserTestBase;
@@ -21,6 +23,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeNonNodeTest extends BrowserTestBase {
 
   use FieldUiTestTrait;
@@ -215,7 +219,7 @@ class KnowledgeNonNodeTest extends BrowserTestBase {
       $regex .= $knowledge->knowledge_body->value . '(.*?)';
       $regex .= '/s';
 
-      return (boolean) preg_match($regex, $this->getSession()->getPage()->getContent());
+      return (bool) preg_match($regex, $this->getSession()->getPage()->getContent());
     }
     else {
       return FALSE;
diff --git a/tests/src/Functional/KnowledgePagerTest.php b/tests/src/Functional/KnowledgePagerTest.php
index fe3d939..42aa631 100644
--- a/tests/src/Functional/KnowledgePagerTest.php
+++ b/tests/src/Functional/KnowledgePagerTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\knowledge\KnowledgeManagerInterface;
 use Drupal\node\Entity\Node;
@@ -11,6 +13,8 @@ use Drupal\node\Entity\Node;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgePagerTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgePreviewTest.php b/tests/src/Functional/KnowledgePreviewTest.php
index 5a72fe6..11b4eb5 100644
--- a/tests/src/Functional/KnowledgePreviewTest.php
+++ b/tests/src/Functional/KnowledgePreviewTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Render\MarkupInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Tests\TestFileCreationTrait;
@@ -13,6 +15,8 @@ use Drupal\knowledge\KnowledgeManagerInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgePreviewTest extends KnowledgeTestBase {
 
   use TestFileCreationTrait {
diff --git a/tests/src/Functional/KnowledgeRolesTest.php b/tests/src/Functional/KnowledgeRolesTest.php
index 80d9a42..6fb20f4 100644
--- a/tests/src/Functional/KnowledgeRolesTest.php
+++ b/tests/src/Functional/KnowledgeRolesTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Tests knowledge statistics on nodes.
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeRolesTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeStatisticsTest.php b/tests/src/Functional/KnowledgeStatisticsTest.php
index 95d7507..b4f47de 100644
--- a/tests/src/Functional/KnowledgeStatisticsTest.php
+++ b/tests/src/Functional/KnowledgeStatisticsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\knowledge\Entity\Knowledge;
 use Drupal\knowledge\KnowledgeManagerInterface;
 use Drupal\user\RoleInterface;
@@ -11,6 +13,8 @@ use Drupal\user\RoleInterface;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeStatisticsTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeTitleTest.php b/tests/src/Functional/KnowledgeTitleTest.php
index 3e72ea3..4908043 100644
--- a/tests/src/Functional/KnowledgeTitleTest.php
+++ b/tests/src/Functional/KnowledgeTitleTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Tests to ensure that appropriate and accessible markup is created for titles.
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTitleTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeTokenReplaceTest.php b/tests/src/Functional/KnowledgeTokenReplaceTest.php
index 99d3ba5..bdde1c4 100644
--- a/tests/src/Functional/KnowledgeTokenReplaceTest.php
+++ b/tests/src/Functional/KnowledgeTokenReplaceTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\BubbleableMetadata;
@@ -19,6 +21,8 @@ use Drupal\user\Entity\User;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTokenReplaceTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/KnowledgeTypeTest.php b/tests/src/Functional/KnowledgeTypeTest.php
index 5025c83..19d4341 100644
--- a/tests/src/Functional/KnowledgeTypeTest.php
+++ b/tests/src/Functional/KnowledgeTypeTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Url;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -15,6 +17,8 @@ use Drupal\node\Entity\Node;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/Rest/KnowledgeJsonAnonTest.php b/tests/src/Functional/Rest/KnowledgeJsonAnonTest.php
index 0fa3508..b591d9d 100644
--- a/tests/src/Functional/Rest/KnowledgeJsonAnonTest.php
+++ b/tests/src/Functional/Rest/KnowledgeJsonAnonTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeJsonAnonTest extends KnowledgeResourceTestBase {
 
   use AnonResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeJsonBasicAuthTest.php b/tests/src/Functional/Rest/KnowledgeJsonBasicAuthTest.php
index 2bdd4c5..79b0dce 100644
--- a/tests/src/Functional/Rest/KnowledgeJsonBasicAuthTest.php
+++ b/tests/src/Functional/Rest/KnowledgeJsonBasicAuthTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeJsonBasicAuthTest extends KnowledgeResourceTestBase {
 
   use BasicAuthResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeJsonCookieTest.php b/tests/src/Functional/Rest/KnowledgeJsonCookieTest.php
index 4383abd..1247224 100644
--- a/tests/src/Functional/Rest/KnowledgeJsonCookieTest.php
+++ b/tests/src/Functional/Rest/KnowledgeJsonCookieTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeJsonCookieTest extends KnowledgeResourceTestBase {
 
   use CookieResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeTypeJsonAnonTest.php b/tests/src/Functional/Rest/KnowledgeTypeJsonAnonTest.php
index f615ced..06a67b0 100644
--- a/tests/src/Functional/Rest/KnowledgeTypeJsonAnonTest.php
+++ b/tests/src/Functional/Rest/KnowledgeTypeJsonAnonTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeJsonAnonTest extends KnowledgeTypeResourceTestBase {
 
   use AnonResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeTypeJsonBasicAuthTest.php b/tests/src/Functional/Rest/KnowledgeTypeJsonBasicAuthTest.php
index 863ec37..ccabd6e 100644
--- a/tests/src/Functional/Rest/KnowledgeTypeJsonBasicAuthTest.php
+++ b/tests/src/Functional/Rest/KnowledgeTypeJsonBasicAuthTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeJsonBasicAuthTest extends KnowledgeTypeResourceTestBase {
 
   use BasicAuthResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeTypeJsonCookieTest.php b/tests/src/Functional/Rest/KnowledgeTypeJsonCookieTest.php
index 5407e18..fad9953 100644
--- a/tests/src/Functional/Rest/KnowledgeTypeJsonCookieTest.php
+++ b/tests/src/Functional/Rest/KnowledgeTypeJsonCookieTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeJsonCookieTest extends KnowledgeTypeResourceTestBase {
 
   use CookieResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeTypeXmlAnonTest.php b/tests/src/Functional/Rest/KnowledgeTypeXmlAnonTest.php
index 1b764c3..dec21bd 100644
--- a/tests/src/Functional/Rest/KnowledgeTypeXmlAnonTest.php
+++ b/tests/src/Functional/Rest/KnowledgeTypeXmlAnonTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
 
@@ -10,6 +12,8 @@ use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrai
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeXmlAnonTest extends KnowledgeTypeResourceTestBase {
 
   use AnonResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeTypeXmlBasicAuthTest.php b/tests/src/Functional/Rest/KnowledgeTypeXmlBasicAuthTest.php
index 4d2e56e..a011f39 100644
--- a/tests/src/Functional/Rest/KnowledgeTypeXmlBasicAuthTest.php
+++ b/tests/src/Functional/Rest/KnowledgeTypeXmlBasicAuthTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
 
@@ -10,6 +12,8 @@ use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrai
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeXmlBasicAuthTest extends KnowledgeTypeResourceTestBase {
 
   use BasicAuthResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeTypeXmlCookieTest.php b/tests/src/Functional/Rest/KnowledgeTypeXmlCookieTest.php
index c943215..28e8683 100644
--- a/tests/src/Functional/Rest/KnowledgeTypeXmlCookieTest.php
+++ b/tests/src/Functional/Rest/KnowledgeTypeXmlCookieTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
 
@@ -10,6 +12,8 @@ use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrai
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeTypeXmlCookieTest extends KnowledgeTypeResourceTestBase {
 
   use CookieResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeXmlAnonTest.php b/tests/src/Functional/Rest/KnowledgeXmlAnonTest.php
index 3f4de03..67bd1b1 100644
--- a/tests/src/Functional/Rest/KnowledgeXmlAnonTest.php
+++ b/tests/src/Functional/Rest/KnowledgeXmlAnonTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
 
@@ -10,6 +12,8 @@ use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrai
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeXmlAnonTest extends KnowledgeResourceTestBase {
 
   use AnonResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeXmlBasicAuthTest.php b/tests/src/Functional/Rest/KnowledgeXmlBasicAuthTest.php
index 157c318..0330981 100644
--- a/tests/src/Functional/Rest/KnowledgeXmlBasicAuthTest.php
+++ b/tests/src/Functional/Rest/KnowledgeXmlBasicAuthTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
 
@@ -10,6 +12,8 @@ use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrai
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeXmlBasicAuthTest extends KnowledgeResourceTestBase {
 
   use BasicAuthResourceTestTrait;
diff --git a/tests/src/Functional/Rest/KnowledgeXmlCookieTest.php b/tests/src/Functional/Rest/KnowledgeXmlCookieTest.php
index 1027bc4..333c56c 100644
--- a/tests/src/Functional/Rest/KnowledgeXmlCookieTest.php
+++ b/tests/src/Functional/Rest/KnowledgeXmlCookieTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Rest;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
 
@@ -10,6 +12,8 @@ use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrai
  *
  * @group rest
  */
+#[Group('rest')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeXmlCookieTest extends KnowledgeResourceTestBase {
 
   use CookieResourceTestTrait;
diff --git a/tests/src/Functional/Views/DefaultViewRecentKnowledgesTest.php b/tests/src/Functional/Views/DefaultViewRecentKnowledgesTest.php
index eb029d2..e36489c 100644
--- a/tests/src/Functional/Views/DefaultViewRecentKnowledgesTest.php
+++ b/tests/src/Functional/Views/DefaultViewRecentKnowledgesTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Tests\views\Functional\ViewTestBase;
 use Drupal\knowledge\Entity\Knowledge;
@@ -14,6 +16,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class DefaultViewRecentKnowledgesTest extends ViewTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Functional/Views/KnowledgeAdminTest.php b/tests/src/Functional/Views/KnowledgeAdminTest.php
index af270c7..99d5081 100644
--- a/tests/src/Functional/Views/KnowledgeAdminTest.php
+++ b/tests/src/Functional/Views/KnowledgeAdminTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Tests\knowledge\Functional\KnowledgeTestBase as KnowledgeBrowserTestBase;
@@ -17,6 +19,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeAdminTest extends KnowledgeBrowserTestBase {
 
   /**
diff --git a/tests/src/Functional/Views/KnowledgeEditTest.php b/tests/src/Functional/Views/KnowledgeEditTest.php
index cb85ca3..4d0664f 100644
--- a/tests/src/Functional/Views/KnowledgeEditTest.php
+++ b/tests/src/Functional/Views/KnowledgeEditTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\knowledge\Functional\KnowledgeTestBase as KnowledgeBrowserTestBase;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\Tests\knowledge\Functional\KnowledgeTestBase as KnowledgeBrowserTestB
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeEditTest extends KnowledgeBrowserTestBase {
 
   /**
diff --git a/tests/src/Functional/Views/KnowledgeFieldFilterTest.php b/tests/src/Functional/Views/KnowledgeFieldFilterTest.php
index b3bdde9..358791c 100644
--- a/tests/src/Functional/Views/KnowledgeFieldFilterTest.php
+++ b/tests/src/Functional/Views/KnowledgeFieldFilterTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\knowledge\Entity\Knowledge;
 use Drupal\language\Entity\ConfigurableLanguage;
 
@@ -10,6 +12,8 @@ use Drupal\language\Entity\ConfigurableLanguage;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeFieldFilterTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/Views/KnowledgeOperationsTest.php b/tests/src/Functional/Views/KnowledgeOperationsTest.php
index 7bcdbf2..49e23c0 100644
--- a/tests/src/Functional/Views/KnowledgeOperationsTest.php
+++ b/tests/src/Functional/Views/KnowledgeOperationsTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Tests knowledge operations.
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeOperationsTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/Views/KnowledgeRowTest.php b/tests/src/Functional/Views/KnowledgeRowTest.php
index 2353e17..af36a7f 100644
--- a/tests/src/Functional/Views/KnowledgeRowTest.php
+++ b/tests/src/Functional/Views/KnowledgeRowTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Tests the knowledge row plugin.
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeRowTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/Views/NodeKnowledgesTest.php b/tests/src/Functional/Views/NodeKnowledgesTest.php
index cef4bc1..53c17ca 100644
--- a/tests/src/Functional/Views/NodeKnowledgesTest.php
+++ b/tests/src/Functional/Views/NodeKnowledgesTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Tests knowledge on nodes.
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class NodeKnowledgesTest extends KnowledgeTestBase {
 
   /**
diff --git a/tests/src/Functional/Views/WizardTest.php b/tests/src/Functional/Views/WizardTest.php
index 109cfb1..1334c5c 100644
--- a/tests/src/Functional/Views/WizardTest.php
+++ b/tests/src/Functional/Views/WizardTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Functional\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\views\Functional\Wizard\WizardTestBase;
 use Drupal\knowledge\Tests\KnowledgeTestTrait;
 use Drupal\views\Views;
@@ -12,6 +14,8 @@ use Drupal\views\Views;
  * @group knowledge
  * @see \Drupal\knowledge\Plugin\views\wizard\Knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class WizardTest extends WizardTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Kernel/KnowledgeActionsTest.php b/tests/src/Kernel/KnowledgeActionsTest.php
index 833da94..a10956e 100644
--- a/tests/src/Kernel/KnowledgeActionsTest.php
+++ b/tests/src/Kernel/KnowledgeActionsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Datetime\Entity\DateFormat;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\entity_test\Entity\EntityTest;
@@ -16,6 +18,8 @@ use Drupal\system\Entity\Action;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeActionsTest extends EntityKernelTestBase {
   use KnowledgeTestTrait;
 
diff --git a/tests/src/Kernel/KnowledgeBaseFieldTest.php b/tests/src/Kernel/KnowledgeBaseFieldTest.php
index d2e35b0..8abac59 100644
--- a/tests/src/Kernel/KnowledgeBaseFieldTest.php
+++ b/tests/src/Kernel/KnowledgeBaseFieldTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\knowledge\Entity\Knowledge;
@@ -14,6 +16,8 @@ use Drupal\knowledge_base_field_test\Entity\KnowledgeTestBaseField;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeBaseFieldTest extends KernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/KnowledgeBundlesTest.php b/tests/src/Kernel/KnowledgeBundlesTest.php
index a5e714d..381971c 100644
--- a/tests/src/Kernel/KnowledgeBundlesTest.php
+++ b/tests/src/Kernel/KnowledgeBundlesTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\knowledge\Entity\KnowledgeType;
 
@@ -10,6 +12,8 @@ use Drupal\knowledge\Entity\KnowledgeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeBundlesTest extends KernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/KnowledgeDefaultFormatterCacheTagsTest.php b/tests/src/Kernel/KnowledgeDefaultFormatterCacheTagsTest.php
index da1fe91..79291f3 100644
--- a/tests/src/Kernel/KnowledgeDefaultFormatterCacheTagsTest.php
+++ b/tests/src/Kernel/KnowledgeDefaultFormatterCacheTagsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Cache\Cache;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\entity_test\Entity\EntityTest;
@@ -18,6 +20,8 @@ use Symfony\Component\HttpFoundation\Session\Session;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeDefaultFormatterCacheTagsTest extends EntityKernelTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Kernel/KnowledgeFieldAccessTest.php b/tests/src/Kernel/KnowledgeFieldAccessTest.php
index 68792ef..e288f44 100644
--- a/tests/src/Kernel/KnowledgeFieldAccessTest.php
+++ b/tests/src/Kernel/KnowledgeFieldAccessTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
@@ -21,6 +23,9 @@ use Drupal\user\RoleInterface;
  * @group knowledge
  * @group Access
  */
+#[Group('knowledge')]
+#[Group('Access')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeFieldAccessTest extends EntityKernelTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Kernel/KnowledgeIntegrationTest.php b/tests/src/Kernel/KnowledgeIntegrationTest.php
index e6d817c..10d6060 100644
--- a/tests/src/Kernel/KnowledgeIntegrationTest.php
+++ b/tests/src/Kernel/KnowledgeIntegrationTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Entity\Entity\EntityViewMode;
@@ -17,6 +19,8 @@ use Drupal\knowledge\Entity\KnowledgeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeIntegrationTest extends KernelTestBase {
 
   use UserCreationTrait;
diff --git a/tests/src/Kernel/KnowledgeItemTest.php b/tests/src/Kernel/KnowledgeItemTest.php
index b85ff3a..b529c53 100644
--- a/tests/src/Kernel/KnowledgeItemTest.php
+++ b/tests/src/Kernel/KnowledgeItemTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Tests\field\Kernel\FieldKernelTestBase;
 use Drupal\entity_test\Entity\EntityTest;
@@ -14,6 +16,8 @@ use Drupal\knowledge\Tests\KnowledgeTestTrait;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeItemTest extends FieldKernelTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Kernel/KnowledgeOrphanTest.php b/tests/src/Kernel/KnowledgeOrphanTest.php
index d93a795..147a01e 100644
--- a/tests/src/Kernel/KnowledgeOrphanTest.php
+++ b/tests/src/Kernel/KnowledgeOrphanTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Datetime\Entity\DateFormat;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\Tests\EntityViewTrait;
@@ -12,6 +15,8 @@ use Drupal\field\Entity\FieldStorageConfig;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeOrphanTest extends EntityKernelTestBase {
 
   use EntityViewTrait;
@@ -49,6 +54,7 @@ class KnowledgeOrphanTest extends EntityKernelTestBase {
    *
    * @dataProvider providerTestOrphan
    */
+  #[DataProvider('providerTestOrphan')]
   public function testOrphan($property) {
 
     DateFormat::create([
diff --git a/tests/src/Kernel/KnowledgeStringIdEntitiesTest.php b/tests/src/Kernel/KnowledgeStringIdEntitiesTest.php
index edf7de1..80c0377 100644
--- a/tests/src/Kernel/KnowledgeStringIdEntitiesTest.php
+++ b/tests/src/Kernel/KnowledgeStringIdEntitiesTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\knowledge\Entity\KnowledgeType;
@@ -11,6 +13,8 @@ use Drupal\knowledge\Entity\KnowledgeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeStringIdEntitiesTest extends KernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/KnowledgeUninstallTest.php b/tests/src/Kernel/KnowledgeUninstallTest.php
index 99d6052..f23e0e7 100644
--- a/tests/src/Kernel/KnowledgeUninstallTest.php
+++ b/tests/src/Kernel/KnowledgeUninstallTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Extension\ModuleUninstallValidatorException;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -13,6 +16,8 @@ use Drupal\node\Entity\NodeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeUninstallTest extends KernelTestBase {
 
   use KnowledgeTestTrait;
@@ -91,7 +96,7 @@ class KnowledgeUninstallTest extends KernelTestBase {
     $field_storage = FieldStorageConfig::loadByName('node', 'knowledge');
     $this->assertNull($field_storage);
 
-    field_purge_batch(10);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('Drupal\Core\Field\FieldPurger')->purgeBatch(10), fn() => field_purge_batch(10));
     // Ensure that uninstall succeeds even if the field has already been deleted
     // manually beforehand.
     $this->container->get('module_installer')->uninstall(['knowledge']);
diff --git a/tests/src/Kernel/KnowledgeValidationTest.php b/tests/src/Kernel/KnowledgeValidationTest.php
index 308ee2d..c618718 100644
--- a/tests/src/Kernel/KnowledgeValidationTest.php
+++ b/tests/src/Kernel/KnowledgeValidationTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\knowledge\KnowledgeInterface;
@@ -13,6 +15,8 @@ use Drupal\user\Entity\User;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeValidationTest extends EntityKernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/Migrate/MigrateKnowledgeStubTest.php b/tests/src/Kernel/Migrate/MigrateKnowledgeStubTest.php
index a410f90..aa6f565 100644
--- a/tests/src/Kernel/Migrate/MigrateKnowledgeStubTest.php
+++ b/tests/src/Kernel/Migrate/MigrateKnowledgeStubTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Migrate;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
 use Drupal\knowledge\Entity\KnowledgeType;
 use Drupal\migrate_drupal\Tests\StubTestTrait;
@@ -12,6 +14,8 @@ use Drupal\node\Entity\NodeType;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class MigrateKnowledgeStubTest extends MigrateDrupalTestBase {
 
   use StubTestTrait;
diff --git a/tests/src/Kernel/Views/FilterAndArgumentUserUidTest.php b/tests/src/Kernel/Views/FilterAndArgumentUserUidTest.php
index 39d4939..45a898c 100644
--- a/tests/src/Kernel/Views/FilterAndArgumentUserUidTest.php
+++ b/tests/src/Kernel/Views/FilterAndArgumentUserUidTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\Tests\user\Traits\UserCreationTrait;
@@ -18,6 +20,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class FilterAndArgumentUserUidTest extends KernelTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Kernel/Views/KnowledgeAdminViewTest.php b/tests/src/Kernel/Views/KnowledgeAdminViewTest.php
index c6dd2f6..f61689c 100644
--- a/tests/src/Kernel/Views/KnowledgeAdminViewTest.php
+++ b/tests/src/Kernel/Views/KnowledgeAdminViewTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\knowledge\Entity\Knowledge;
@@ -16,6 +18,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeAdminViewTest extends ViewsKernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/Views/KnowledgeFieldNameTest.php b/tests/src/Kernel/Views/KnowledgeFieldNameTest.php
index 05f8ca4..893bb0f 100644
--- a/tests/src/Kernel/Views/KnowledgeFieldNameTest.php
+++ b/tests/src/Kernel/Views/KnowledgeFieldNameTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Render\RenderContext;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\Tests\node\Traits\NodeCreationTrait;
@@ -19,6 +21,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeFieldNameTest extends KernelTestBase {
 
   use KnowledgeTestTrait;
diff --git a/tests/src/Kernel/Views/KnowledgeLinksTest.php b/tests/src/Kernel/Views/KnowledgeLinksTest.php
index 80ffdc1..fc85c3f 100644
--- a/tests/src/Kernel/Views/KnowledgeLinksTest.php
+++ b/tests/src/Kernel/Views/KnowledgeLinksTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Link;
 use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\Core\Url;
@@ -16,6 +18,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeLinksTest extends KnowledgeViewsKernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/Views/KnowledgeUserNameTest.php b/tests/src/Kernel/Views/KnowledgeUserNameTest.php
index 852a7a6..55c03e1 100644
--- a/tests/src/Kernel/Views/KnowledgeUserNameTest.php
+++ b/tests/src/Kernel/Views/KnowledgeUserNameTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
 use Drupal\entity_test\Entity\EntityTest;
@@ -16,6 +18,8 @@ use Drupal\views\Views;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeUserNameTest extends ViewsKernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/Views/KnowledgeViewsFieldAccessTest.php b/tests/src/Kernel/Views/KnowledgeViewsFieldAccessTest.php
index ed2f840..0d05fc7 100644
--- a/tests/src/Kernel/Views/KnowledgeViewsFieldAccessTest.php
+++ b/tests/src/Kernel/Views/KnowledgeViewsFieldAccessTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\knowledge\Kernel\Views;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\views\Kernel\Handler\FieldFieldAccessTestBase;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\knowledge\Entity\Knowledge;
@@ -12,6 +14,8 @@ use Drupal\user\Entity\User;
  *
  * @group knowledge
  */
+#[Group('knowledge')]
+#[RunTestsInSeparateProcesses]
 class KnowledgeViewsFieldAccessTest extends FieldFieldAccessTestBase {
 
   /**
diff --git a/tests/src/Unit/CompetencyAccessControlHandlerTest.php b/tests/src/Unit/CompetencyAccessControlHandlerTest.php
index b40a615..e322a51 100644
--- a/tests/src/Unit/CompetencyAccessControlHandlerTest.php
+++ b/tests/src/Unit/CompetencyAccessControlHandlerTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\knowledge\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Tests\UnitTestCase;
 use Drupal\knowledge\CompetencyAccessControlHandler;
@@ -16,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
  * @coversDefaultClass \Drupal\knowledge\CompetencyAccessControlHandler
  * @group knowledge
  */
+#[Group('knowledge')]
 class CompetencyAccessControlHandlerTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/CompetencyHtmlRouteProviderTest.php b/tests/src/Unit/CompetencyHtmlRouteProviderTest.php
index cf03e1f..599abf5 100644
--- a/tests/src/Unit/CompetencyHtmlRouteProviderTest.php
+++ b/tests/src/Unit/CompetencyHtmlRouteProviderTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\knowledge\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Tests\UnitTestCase;
 use Drupal\knowledge\CompetencyHtmlRouteProvider;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -13,6 +14,7 @@ use Symfony\Component\Routing\RouteCollection;
  * @coversDefaultClass \Drupal\knowledge\CompetencyHtmlRouteProvider
  * @group knowledge
  */
+#[Group('knowledge')]
 class CompetencyHtmlRouteProviderTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/CompetencyListBuilderTest.php b/tests/src/Unit/CompetencyListBuilderTest.php
index 82ffd53..237bf74 100644
--- a/tests/src/Unit/CompetencyListBuilderTest.php
+++ b/tests/src/Unit/CompetencyListBuilderTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\knowledge\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Routing\RedirectDestinationInterface;
@@ -15,6 +16,7 @@ use Drupal\knowledge\CompetencyListBuilder;
  * @coversDefaultClass \Drupal\knowledge\CompetencyListBuilder
  * @group knowledge
  */
+#[Group('knowledge')]
 class CompetencyListBuilderTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/CompetencyStorageTest.php b/tests/src/Unit/CompetencyStorageTest.php
index 78e7486..e1fe1de 100644
--- a/tests/src/Unit/CompetencyStorageTest.php
+++ b/tests/src/Unit/CompetencyStorageTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\knowledge\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Tests\UnitTestCase;
 use Drupal\knowledge\CompetencyStorage;
 use Drupal\knowledge\KnowledgeCompetencyInterface;
@@ -13,6 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
  * @coversDefaultClass \Drupal\knowledge\CompetencyStorage
  * @group knowledge
  */
+#[Group('knowledge')]
 class CompetencyStorageTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/KnowledgeManagerTest.php b/tests/src/Unit/KnowledgeManagerTest.php
index e87de20..cea6f52 100644
--- a/tests/src/Unit/KnowledgeManagerTest.php
+++ b/tests/src/Unit/KnowledgeManagerTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\knowledge\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -16,6 +17,7 @@ use Prophecy\Prophet;
  * @coversDefaultClass \Drupal\knowledge\KnowledgeManager
  * @group knowledge
  */
+#[Group('knowledge')]
 class KnowledgeManagerTest extends UnitTestCase {
 
   use ProphecyTrait;
diff --git a/tests/src/Unit/KnowledgeStatisticsUnitTest.php b/tests/src/Unit/KnowledgeStatisticsUnitTest.php
index 5b2f269..80ba3cc 100644
--- a/tests/src/Unit/KnowledgeStatisticsUnitTest.php
+++ b/tests/src/Unit/KnowledgeStatisticsUnitTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\knowledge\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Tests\UnitTestCase;
 use Drupal\knowledge\KnowledgeStatistics;
 
@@ -9,6 +10,7 @@ use Drupal\knowledge\KnowledgeStatistics;
  * @coversDefaultClass \Drupal\knowledge\KnowledgeStatistics
  * @group knowledge
  */
+#[Group('knowledge')]
 class KnowledgeStatisticsUnitTest extends UnitTestCase {
 
   /**
@@ -101,6 +103,8 @@ class KnowledgeStatisticsUnitTest extends UnitTestCase {
    * @group Drupal
    * @group Knowledge
    */
+  #[Group('Drupal')]
+  #[Group('Knowledge')]
   public function testRead() {
     $this->callsToFetch = 0;
     $results = $this->knowledgeStatistics->read(['1' => 'boo', '2' => 'foo'], 'snafus');
diff --git a/tests/src/Unit/Plugin/views/field/KnowledgeBulkFormTest.php b/tests/src/Unit/Plugin/views/field/KnowledgeBulkFormTest.php
index faa3086..dd3a95d 100644
--- a/tests/src/Unit/Plugin/views/field/KnowledgeBulkFormTest.php
+++ b/tests/src/Unit/Plugin/views/field/KnowledgeBulkFormTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\knowledge\Unit\Plugin\views\field;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityRepositoryInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -12,6 +13,7 @@ use Drupal\knowledge\Plugin\views\field\KnowledgeBulkForm;
  * @coversDefaultClass \Drupal\knowledge\Plugin\views\field\KnowledgeBulkForm
  * @group knowledge
  */
+#[Group('knowledge')]
 class KnowledgeBulkFormTest extends UnitTestCase {
 
   /**
@@ -92,7 +94,6 @@ class KnowledgeBulkFormTest extends UnitTestCase {
     $knowledge_bulk_form->init($executable, $display, $options);
 
     $reflected_actions = (new \ReflectionObject($knowledge_bulk_form))->getProperty('actions');
-    $reflected_actions->setAccessible(TRUE);
     $this->assertEquals(array_slice($actions, 0, -1, TRUE), $reflected_actions->getValue($knowledge_bulk_form));
   }
 
diff --git a/tests/src/Unit/Service/CompetencyServiceTest.php b/tests/src/Unit/Service/CompetencyServiceTest.php
index 031eda7..734bfd9 100644
--- a/tests/src/Unit/Service/CompetencyServiceTest.php
+++ b/tests/src/Unit/Service/CompetencyServiceTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\knowledge\Service\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Tests\UnitTestCase;
 use Drupal\knowledge\Service\CompetencyService;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -12,6 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
  * @coversDefaultClass \Drupal\knowledge\Service\CompetencyService
  * @group knowledge
  */
+#[Group('knowledge')]
 class CompetencyServiceTest extends UnitTestCase {
 
   /**
