diff --git a/composer.json b/composer.json
index 8f6ce7c42090af7ec0bb18539e9968261b233475..751ed330caefe91bb2e8e43549849888a7e54c06 100644
--- a/composer.json
+++ b/composer.json
@@ -152,7 +152,7 @@
         "drupal/roleassign": "2.0.0",
         "drupal/scheduler": "1.5",
         "drupal/simple_gmap": "3.0.1",
-        "drupal/simple_sitemap": "3.11",
+        "drupal/simple_sitemap": "4.1.6",
         "drupal/simplesamlphp_auth": "3.3",
         "drupal/smtp": "1.2",
         "drupal/social_media": "1.9-rc2",
diff --git a/composer.lock b/composer.lock
index 60e98eb1e2e2c45ad4e8a7f5a13beacdf3f8c88b..22bb9305e2e06d3d6b9d23f633856b0dfd69b087 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "c64c633705bdb8aa91c3829fd66c169d",
+    "content-hash": "b22644da8ddacc5d709951bf2d5654df",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -6712,27 +6712,27 @@
         },
         {
             "name": "drupal/simple_sitemap",
-            "version": "3.11.0",
+            "version": "4.1.6",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/simple_sitemap.git",
-                "reference": "8.x-3.11"
+                "reference": "4.1.6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/simple_sitemap-8.x-3.11.zip",
-                "reference": "8.x-3.11",
-                "shasum": "5fdd4ed5af5e37e3c707e401d094a179f52e7711"
+                "url": "https://ftp.drupal.org/files/projects/simple_sitemap-4.1.6.zip",
+                "reference": "4.1.6",
+                "shasum": "5ea5ee97ab4d59b43db86dd6279c3ac5ecbe69b9"
             },
             "require": {
-                "drupal/core": "^8 || ^9",
+                "drupal/core": "^9.3 || ^10",
                 "ext-xmlwriter": "*"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-3.11",
-                    "datestamp": "1658781789",
+                    "version": "4.1.6",
+                    "datestamp": "1686288643",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -6740,7 +6740,7 @@
                 },
                 "drush": {
                     "services": {
-                        "drush.services.yml": "^9 || ^10"
+                        "drush.services.yml": ">=9"
                     }
                 }
             },
@@ -6764,8 +6764,7 @@
             "homepage": "https://drupal.org/project/simple_sitemap",
             "support": {
                 "source": "https://cgit.drupalcode.org/simple_sitemap",
-                "issues": "https://drupal.org/project/issues/simple_sitemap",
-                "irc": "irc://irc.freenode.org/drupal-contribute"
+                "issues": "https://drupal.org/project/issues/simple_sitemap"
             }
         },
         {
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 3032973fe60c9050e5cf1cffc3c069d953132b78..d9ba62ab2dd4cae1b888ce8c62647a6879d1bb88 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -6982,28 +6982,28 @@
         },
         {
             "name": "drupal/simple_sitemap",
-            "version": "3.11.0",
-            "version_normalized": "3.11.0.0",
+            "version": "4.1.6",
+            "version_normalized": "4.1.6.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/simple_sitemap.git",
-                "reference": "8.x-3.11"
+                "reference": "4.1.6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/simple_sitemap-8.x-3.11.zip",
-                "reference": "8.x-3.11",
-                "shasum": "5fdd4ed5af5e37e3c707e401d094a179f52e7711"
+                "url": "https://ftp.drupal.org/files/projects/simple_sitemap-4.1.6.zip",
+                "reference": "4.1.6",
+                "shasum": "5ea5ee97ab4d59b43db86dd6279c3ac5ecbe69b9"
             },
             "require": {
-                "drupal/core": "^8 || ^9",
+                "drupal/core": "^9.3 || ^10",
                 "ext-xmlwriter": "*"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-3.11",
-                    "datestamp": "1634344305",
+                    "version": "4.1.6",
+                    "datestamp": "1686288643",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -7011,7 +7011,7 @@
                 },
                 "drush": {
                     "services": {
-                        "drush.services.yml": "^9 || ^10"
+                        "drush.services.yml": ">=9"
                     }
                 }
             },
@@ -7028,16 +7028,15 @@
                     "role": "Maintainer"
                 },
                 {
-                    "name": "gbyte",
-                    "homepage": "https://www.drupal.org/user/2381352"
+                    "name": "WalkingDexter",
+                    "homepage": "https://www.drupal.org/user/3251330"
                 }
             ],
             "description": "Creates a standard conform hreflang XML sitemap of the site content and provides a framework for developing other sitemap types.",
             "homepage": "https://drupal.org/project/simple_sitemap",
             "support": {
                 "source": "https://cgit.drupalcode.org/simple_sitemap",
-                "issues": "https://drupal.org/project/issues/simple_sitemap",
-                "irc": "irc://irc.freenode.org/drupal-contribute"
+                "issues": "https://drupal.org/project/issues/simple_sitemap"
             },
             "install-path": "../../web/modules/simple_sitemap"
         },
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index d980eafe00a40f3030a7b822e505e75659cef686..ea4e8db0ae1bc45aea0c9dfdafaeaf8ab7666bf1 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'osu-asc-webservices/d8-upstream',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '8b6599c359bbbfb3d28e9653ead3438e884fe4b4',
+        'reference' => 'ce96cd96905635f8fc3770f87e19409d391efa0b',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -1121,9 +1121,9 @@
             'dev_requirement' => false,
         ),
         'drupal/simple_sitemap' => array(
-            'pretty_version' => '3.11.0',
-            'version' => '3.11.0.0',
-            'reference' => '8.x-3.11',
+            'pretty_version' => '4.1.6',
+            'version' => '4.1.6.0',
+            'reference' => '4.1.6',
             'type' => 'drupal-module',
             'install_path' => __DIR__ . '/../../web/modules/simple_sitemap',
             'aliases' => array(),
@@ -1540,7 +1540,7 @@
         'osu-asc-webservices/d8-upstream' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '8b6599c359bbbfb3d28e9653ead3438e884fe4b4',
+            'reference' => 'ce96cd96905635f8fc3770f87e19409d391efa0b',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
diff --git a/web/modules/simple_sitemap/README.md b/web/modules/simple_sitemap/README.md
index 8a625cda07287b893195dcb02f07ed60c0b2c321..f087a272a2f9fe599dd3dfa88ecdbd9548026598 100644
--- a/web/modules/simple_sitemap/README.md
+++ b/web/modules/simple_sitemap/README.md
@@ -13,7 +13,7 @@
 
 Author and maintainer: Pawel Ginalski (gbyte)
  * Drupal: https://www.drupal.org/u/gbyte
- * Personal: https://gbyte.dev/
+ * Homepage: https://gbyte.dev/
 
 The module generates multilingual XML sitemaps which adhere to Google's new
 hreflang standard. Out of the box the sitemaps index most of Drupal's
@@ -31,7 +31,7 @@ as well. On top of that custom links and view pages can be added to sitemaps.
 To learn about XML sitemaps, see https://en.wikipedia.org/wiki/Sitemaps.
 
 The module also provides an API allowing to create any type of sitemap (not
-necessary an XML one) holding links to a local or remote source.
+necessarily an XML one) holding links to a local or remote source.
 
 ## INSTALLATION ##
 
@@ -43,26 +43,38 @@ for instructions on how to install or update Drupal modules.
 ### PERMISSIONS ###
 
 The module permission 'administer sitemap settings' can be configured under
-/admin/people/permissions.
+admin/people/permissions.
 
-### VARIANTS ###
+### SITEMAP VARIANTS ###
 
 It is possible to have several sitemap instances of different sitemap types with
 specific links accessible under certain URLs. These sitemap variants can be
-configured under admin/config/search/simplesitemap/variants.
+configured under admin/config/search/simplesitemap. The module comes with the
+default sitemap 'default' which is accessible under /sitemap.xml by default.
+
+There is also the 'sitemap index' variant which is disabled by default and which
+can be used to index all other variants. If there are multiple sitemap variants
+it may make sense to enable the sitemap index variant and make it available
+under /sitemap.xml by setting it as the default variant in the module's
+settings.
+
+### SITEMAP TYPES ###
+
+A sitemap type is a configuration entity consisting of one sitemap generator
+plugin and several URL generator plugins. These plugins can be implemented by
+custom modules. The module comes with the sitemap types 'default_hreflang'
+and 'index'. Sitemap types can be defined via the UI under
+admin/config/search/simplesitemap/types.
 
 ### ENTITIES ###
 
-Initially only the home page is indexed in the default sitemap variant. To
+Initially only the home page is indexed in the default sitemap. To
 include content into a sitemap, visit
-/admin/config/search/simplesitemap/entities to enable support for entity types
-of your choosing. Bundleless entity types can be configured right on that page,
-for bundles of entity types visit the bundle's configuration pages, e.g.
-
- * /admin/structure/types/manage/[content type] for nodes
- * /admin/structure/taxonomy/manage/[taxonomy vocabulary] for taxonomy terms
- * /admin/structure/menu/manage/[menu] for menu items
- * ...
+admin/config/search/simplesitemap/entities to enable support for entity types
+of your choosing. After enabling support for an entity type, indexation settings
+for each sitemap variant can be set by pressing 'Configure'. For entity type
+bundles, one can optionally set the indexation settings right on the bundle's
+configuration pages, e.g. admin/structure/types/manage/[content type] for nodes.
 
 When including an entity type or bundle into a sitemap, the priority setting
 can be set which will set the 'priority' parameter for all entities of that
@@ -75,11 +87,11 @@ basis. Just head over to a bundle instance edit form (e.g. node/1/edit) to
 override its sitemap settings.
 
 If you wish for the sitemap to reflect the new configuration instantly, check
-'Regenerate sitemaps after clicking save'. This setting only appears if a change
-in the settings has been detected.
+'Regenerate sitemaps after clicking save'. This setting may only appear if a
+change in the settings has been detected.
 
-Once variants are set up in admin/config/search/simplesitemap/variants, all the
-above settings can be configured and overwritten on a per variant basis right
+Once sitemaps are set up in admin/config/search/simplesitemap, all the
+above settings can be configured and overwritten on a per sitemap basis right
 from the UI.
 
 As the sitemaps are accessible to anonymous users, bear in mind that only links
@@ -91,66 +103,88 @@ checks for links added through the module's hooks (see below).
 To index views, enable the included, optional module Simple XML Sitemap (Views)
 (simple_sitemap_views).
 
-Simple views as well as views with arguments can be indexed on the view edit
-page. For views with arguments, links to all view variants will be included in
-the sitemap.
+Simple views as well as views with arguments can be indexed on a per-variant
+basis on the view edit page. For views with arguments, links to all view
+variants will be included in the sitemap.
 
 ### CUSTOM LINKS ###
 
 To include custom links into a sitemap, visit
-/admin/config/search/simplesitemap/custom.
-
-### AUTOMATIC SUBMISSION ###
+admin/config/search/simplesitemap/custom.
 
-It is possible to have the module automatically submit specific sitemap
-variants to search engines. Google and Bing are preconfigured.
+### SEARCH ENGINES ###
 
-This functionality is available through the included simple_sitemap_engines
+The module can submit sitemap changes to search engines as well as notify search
+engines of entity changes directly.
+These functionalities are available through the included simple_sitemap_engines
 submodule. After enabling this module, go to
 admin/config/search/simplesitemap/engines/settings to set it up.
 
+#### SITEMAP SUBMISSION ####
+
+It is possible to have the module automatically submit specific sitemap
+variants to search engines. Google is preconfigured and new engines can be added
+programmatically via simple_sitemap_engine entities. Specific sitemap variants
+can be submitted to specific search engines, the time interval is configurable.
+
+#### INDEXNOW SUBMISSION ####
+
+The module also supports the IndexNow service provided by Bing and Yandex. The
+two search engines are preconfigured and new engines can be added
+programmatically via simple_sitemap_engine entities.
+
+For the submission to work, a key needs to be generated under
+admin/config/search/simplesitemap/engines/settings. This key will be saved to
+Drupal's state, but it is recommended to store it in the `settings.php` or
+`settings.local.php` file by adding the line
+`$settings['simple_sitemap_engines.index_now.key'] = xxx;`
+
+Do not forget to include entities under
+admin/config/search/simplesitemap/entities.
+
 ### PERFORMANCE ###
 
 The module can be tuned via UI for a vast improvement in generation speeds on
 huge sites. To speed up generation, go to
-admin/config/search/simplesitemap/engines/settings and increase
+admin/config/search/simplesitemap/settings and increase
 'Entities per queue item' and 'Sitemap generation max duration'.
 
 Further things that can be tweaked are unchecking 'Exclude duplicate links' and
 increasing 'Maximum links in a sitemap'.
 
 These settings will increase the demand for PHP  execution time and memory, so
-please make sure  to test the sitemap generation behaviour. See 'PERFORMANCE TEST'.
+please make sure to test the sitemap generation behaviour. See
+'PERFORMANCE TEST'.
 
 ### OTHER SETTINGS ###
 
 Other settings can be found under admin/config/search/simplesitemap/settings.
 
-## USAGE ## 
+## USAGE ##
 
 The sitemaps are accessible to the whole world under [variant name]/sitemap.xml.
-In addition to that, the default sitemap is accessible under /sitemap.xml. To
-view the XML source, press ctrl+u.
+Additionally, the default sitemap is accessible under /sitemap.xml. To view the
+XML source, press ctrl+u.
 
 If the cron generation is turned on, the sitemaps will be regenerated according
 to the 'Sitemap generation interval' setting.
 
 A manual generation is possible on admin/config/search/simplesitemap. This is
-also the place that shows the overall and variant specific generation status.
+also the place that shows the overall and sitemap specific generation status.
 
-The sitemap can be also generated via drush:
+The sitemap can also be generated via drush:
  * `simple-sitemap:generate` or `ssg`: Generates the sitemap (continues
    generating from queue, or rebuilds queue for all variants beforehand if
    nothing is queued).
-   
- * `simple-sitemap:rebuild-queue` or `ssr`: Deletes queue and queues elements
+
+ * `simple-sitemap:rebuild-queue` or `ssr`: Deletes the queue and queues elements
    for all or specific sitemap variants. Add `--variants` flag and specify a
-   comma separated list of variants if wanting to queue only specific variants
-   for the upcoming generation.
+   comma separated list of variants if you intend to queue only specific
+   sitemap variants for the upcoming generation.
 
 Generation of hundreds of thousands of links can take time. Each variant gets
 published as soon as all of its links have been generated. The previous version
-of the sitemap variant is accessible during the generation process.
+of the sitemap is accessible during the generation process.
 
 ## Debugging ##
 
@@ -173,75 +207,96 @@ to generation:
 
 There are API methods for altering stored inclusion settings, status queries and
 programmatic sitemap generation. These include:
-
- * getSetting
- * saveSetting
- * setVariants
- * getSitemap
- * removeSitemap
- * queue
- * rebuildQueue
- * generateSitemap
- * enableEntityType
- * disableEntityType
- * setBundleSettings
- * getBundleSettings
- * removeBundleSettings
- * setEntityInstanceSettings
- * getEntityInstanceSettings
- * removeEntityInstanceSettings
- * bundleIsIndexed
- * entityTypeIsEnabled
- * addCustomLink
- * getCustomLinks
- * removeCustomLinks
- * getSitemapManager
-    * getSitemapVariants
-    * addSitemapVariant
-    * removeSitemapVariants
- * getQueueWorker
-    * getInitialElementCount
-    * getQueuedElementCount
-    * getStashedResultCount
-    * getProcessedElementCount
-    * generationInProgress
-
-
-These service methods can be chained like so:
+ * simple_sitemap.generator
+   * setVariants
+   * getSetting
+   * saveSetting
+   * getContent
+   * generate
+   * queue
+   * rebuildQueue
+   * entityManager
+     * enableEntityType
+     * disableEntityType
+     * setBundleSettings
+     * getBundleSettings
+     * getAllBundleSettings
+     * removeBundleSettings
+     * setEntityInstanceSettings
+     * getEntityInstanceSettings
+     * removeEntityInstanceSettings
+     * bundleIsIndexed
+     * entityTypeIsEnabled
+   * customLinkManager
+     * add
+     * get
+     * remove
+
+These service methods can be used/chained like so:
 
 ```php
+// Create a new sitemap 'test' of the default_hreflang sitemap type.
+\Drupal\simple_sitemap\Entity\SimpleSitemap::create(['id' => 'test', 'type' => 'default_hreflang', 'label' => 'Test'])->save();
+
+/** @var \Drupal\simple_sitemap\Manager\Generator $generator */
 $generator = \Drupal::service('simple_sitemap.generator');
 
+// Set some random settings (global, not sitemap variant specific).
+if ($generator->getSetting('cron_generate')) {
+  $generator
+    ->saveSetting('generate_duration', 20000)
+    ->saveSetting('base_url', 'https://test');
+}
+
+// Set an entity bundle to be indexed in the 'default' and 'test' sitemaps.
 $generator
-  ->getSitemapManager()
-  ->addSitemapVariant('test');
-  
-$generator
-  ->saveSetting('remove_duplicates', TRUE)
+  ->entityManager()
   ->enableEntityType('node')
-  ->setVariants(['default', 'test'])
-  ->setBundleSettings('node', 'page', ['index' => TRUE, 'priority' => 0.5])
-  ->removeCustomLinks()
-  ->addCustomLink('/some/view/page', ['priority' => 0.5])
-  ->generateSitemap();
+  ->setVariants(['default', 'test']) // All following operations will concern these sitemap variants.
+  ->setBundleSettings('node', 'page', ['index' => TRUE, 'priority' => 0.5]);
+
+// Remove all custom links from the 'default' and 'test' sitemaps and Set a
+// custom link to be indexed in the 'test' sitemap.
+$generator
+  ->customLinkManager()
+  ->remove() // Remove all custom links from all variants.
+  ->setVariants(['test']) // All following operations will concern these variants.
+  ->add('/some/view/page', ['priority' => 0.5]);
+
+// Queues the 'test' sitemap for generation and generates it.
+$generator
+  ->rebuildQueue()
+  ->generate();
 ```
 
-See https://gbyte.dev/projects/simple-xml-sitemap and code documentation in 
-Drupal\simple_sitemap\Simplesitemap for further details.
+To query data of and manipulate a specific sitemap, load it and use its
+various methods. Some arbitrary example:
+
+```php
+$sitemap = \Drupal\simple_sitemap\Entity\SimpleSitemap::load('default');
+// Be aware, that $sitemap->status() only returns TRUE if the sitemap is enabled
+// and published. To check if it is enabled only, use $sitemap->isEnabled().
+if ($sitemap->status() && !$sitemap->isDefault() && $sitemap->getCreated() < $some_timestamp) {
+  $sitemap->disable();
+}
+```
+
+See https://gbyte.dev/projects/simple-xml-sitemap and code documentation for
+further details.
 
 ### API HOOKS ###
 
 It is possible to hook into link generation by implementing
-`hook_simple_sitemap_links_alter(&$links, $sitemap_variant){}` in a custom module and altering the
+`hook_simple_sitemap_links_alter(&$links, $sitemap){}` in a custom module and altering the
 link array shortly before it is transformed to XML.
 
 Adding arbitrary links is possible through the use of
-`hook_simple_sitemap_arbitrary_links_alter(&$arbitrary_links, $sitemap_variant){}`. There are no
+`hook_simple_sitemap_arbitrary_links_alter(&$arbitrary_links, $sitemap){}`. There are no
 checks performed on these links (i.e. if they are internal/valid/accessible)
 and parameters like priority/lastmod/changefreq have to be added manually.
 
 Altering sitemap attributes and sitemap index attributes is possible through the
-use of `hook_simple_sitemap_attributes_alter(&$attributes, $sitemap_variant){}` and
+use of `hook_simple_sitemap_attributes_alter(&$attributes, $sitemap){}` and
 `hook_simple_sitemap_index_attributes_alter(&$index_attributes, $sitemap_variant){}`.
 
 Altering URL generators is possible through
@@ -250,22 +305,15 @@ the use of `hook_simple_sitemap_url_generators_alter(&$url_generators){}`.
 Altering sitemap generators is possible through
 the use of `hook_simple_sitemap_sitemap_generators_alter(&$sitemap_generators){}`.
 
-Altering sitemap types is possible through
-the use of `hook_simple_sitemap_sitemap_types_alter(&$sitemap_types){}`.
+Sitemaps as well as sitemap types can be altered through the usual entity hooks.
 
 ### WRITING PLUGINS ###
 
-There are three types of plugins that allow to create any type of sitemap. See
+There are two types of plugins that allow to create any type of sitemap. See
 the generator plugins included in this module and check the API docs
 (https://www.drupal.org/docs/8/api/plugin-api/plugin-api-overview) to learn how
 to implement plugins.
 
-#### SITEMAP TYPE PLUGINS ####
-
-This plugin defines a sitemap type. A sitemap type consists of a sitemap
-generator and several URL generators. This plugin is very simple, as it
-only requires some class annotation to define which sitemap/URL plugins to use.
-
 #### SITEMAP GENERATOR PLUGINS ####
 
 This plugin defines how a sitemap type is supposed to look. It handles all
@@ -273,7 +321,7 @@ aspects of the sitemap except its links/URLs.
 
 #### URL GENERATOR PLUGINS ####
 
-This plugin defines a way of generating URLs for one or more sitemap types.
+This plugin defines a way of generating URLs for a sitemap type.
 
 Note:
 Overwriting the default EntityUrlGenerator for a single entity type is possible
@@ -290,16 +338,17 @@ See https://gbyte.dev/projects/simple-xml-sitemap for further details.
    possible help out by submitting patches.
    http://drupal.org/project/issues/simple_sitemap
 
- * Do you know a non-English language? Help translating the module.
+ * Do you know a non-English language? Help to translate the module.
    https://localize.drupal.org/translate/projects/simple_sitemap
 
  * If you would like to say thanks and support the development of this module, a
    donation will be much appreciated.
    https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5AFYRSBLGSC3W
-   
+
  * Feel free to contact me for paid support: https://gbyte.dev/contact
 
 ## MAINTAINERS ##
 
 Current maintainers:
  * Pawel Ginalski (gbyte) - https://www.drupal.org/u/gbyte
+ * Andrey Tymchuk (WalkingDexter) - https://www.drupal.org/u/walkingdexter
diff --git a/web/modules/simple_sitemap/composer.json b/web/modules/simple_sitemap/composer.json
index 0fc798ba86d45774e89ccd3311bd72aa2569304f..ddfbf167cfab1bef8910ad1f14d2e6f4c67fc31a 100644
--- a/web/modules/simple_sitemap/composer.json
+++ b/web/modules/simple_sitemap/composer.json
@@ -13,7 +13,6 @@
   ],
   "support": {
     "issues": "https://drupal.org/project/issues/simple_sitemap",
-    "irc": "irc://irc.freenode.org/drupal-contribute",
     "source": "https://cgit.drupalcode.org/simple_sitemap"
   },
   "license": "GPL-2.0-or-later",
@@ -24,7 +23,7 @@
   "extra": {
     "drush": {
       "services": {
-        "drush.services.yml": "^9 || ^10"
+        "drush.services.yml": ">=9"
       }
     }
   }
diff --git a/web/modules/simple_sitemap/config/install/simple_sitemap.settings.yml b/web/modules/simple_sitemap/config/install/simple_sitemap.settings.yml
index 314ad84bd5214c128805116a3a9937d86f10caf5..c60a1cefb7d8593b01bd2a9f083a6b292a6826d0 100644
--- a/web/modules/simple_sitemap/config/install/simple_sitemap.settings.yml
+++ b/web/modules/simple_sitemap/config/install/simple_sitemap.settings.yml
@@ -2,15 +2,17 @@ max_links: 2000
 cron_generate: true
 cron_generate_interval: 0
 generate_duration: 10000
+entities_per_queue_item: 50
 remove_duplicates: true
 skip_untranslated: true
 xsl: true
 base_url: ''
 default_variant: 'default'
 custom_links_include_images: false
+disable_language_hreflang: false
+hide_branding: false
 excluded_languages: []
 enabled_entity_types:
   - 'node'
   - 'taxonomy_term'
   - 'menu_link_content'
-entities_per_queue_item: 50
diff --git a/web/modules/simple_sitemap/config/install/simple_sitemap.sitemap.default.yml b/web/modules/simple_sitemap/config/install/simple_sitemap.sitemap.default.yml
new file mode 100644
index 0000000000000000000000000000000000000000..08d6a0168d6655bb183bad64e1da2698b3f89124
--- /dev/null
+++ b/web/modules/simple_sitemap/config/install/simple_sitemap.sitemap.default.yml
@@ -0,0 +1,9 @@
+id: default
+label: Default
+description: 'The default hreflang sitemap - lists URLs to be indexed by modern search engines.'
+type: default_hreflang
+weight: 0
+status: true
+dependencies:
+  config:
+    - simple_sitemap.type.default_hreflang
diff --git a/web/modules/simple_sitemap/config/install/simple_sitemap.sitemap.index.yml b/web/modules/simple_sitemap/config/install/simple_sitemap.sitemap.index.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de0fe4ea1917e9d945ef9dbd62a17f0d77b73a63
--- /dev/null
+++ b/web/modules/simple_sitemap/config/install/simple_sitemap.sitemap.index.yml
@@ -0,0 +1,9 @@
+id: index
+label: Sitemap Index
+description: 'The sitemap index listing all other sitemaps - useful if there are at least two other sitemaps. In most cases this sitemap should be last in the generation queue and set as the default sitemap.'
+type: index
+weight: 1000
+status: false
+dependencies:
+  config:
+    - simple_sitemap.type.index
diff --git a/web/modules/simple_sitemap/config/install/simple_sitemap.type.default_hreflang.yml b/web/modules/simple_sitemap/config/install/simple_sitemap.type.default_hreflang.yml
new file mode 100644
index 0000000000000000000000000000000000000000..838d7b57d7df9e913cd127b47e3463b801ca1ff7
--- /dev/null
+++ b/web/modules/simple_sitemap/config/install/simple_sitemap.type.default_hreflang.yml
@@ -0,0 +1,9 @@
+id: default_hreflang
+label: Default hreflang
+description: 'The default hreflang sitemap type. A sitemap of this type is understood by most modern search engines.'
+sitemap_generator: default
+url_generators:
+  - custom
+  - entity
+  - entity_menu_link_content
+  - arbitrary
diff --git a/web/modules/simple_sitemap/config/install/simple_sitemap.type.index.yml b/web/modules/simple_sitemap/config/install/simple_sitemap.type.index.yml
new file mode 100644
index 0000000000000000000000000000000000000000..494d1e6b5f8b13c1e90c06ca979a8d7eaa3b0ddf
--- /dev/null
+++ b/web/modules/simple_sitemap/config/install/simple_sitemap.type.index.yml
@@ -0,0 +1,6 @@
+id: index
+label: Sitemap Index
+description: 'The sitemap index sitemap type. A sitemap of this type lists sitemaps of all other types.'
+sitemap_generator: index
+url_generators:
+  - index
diff --git a/web/modules/simple_sitemap/config/install/simple_sitemap.variants.default_hreflang.yml b/web/modules/simple_sitemap/config/install/simple_sitemap.variants.default_hreflang.yml
deleted file mode 100644
index fd7847d28dc5913cc2707b67629371cc7e7a9d71..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/config/install/simple_sitemap.variants.default_hreflang.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-variants:
-  default:
-    label: 'Default'
-    weight: 0
diff --git a/web/modules/simple_sitemap/config/schema/simple_sitemap.schema.yml b/web/modules/simple_sitemap/config/schema/simple_sitemap.schema.yml
index cb2bae89bba80cb2d564e4cb43ee01efaa73d452..1885b41900d1fe308c7827bc3946a9ea47524c08 100644
--- a/web/modules/simple_sitemap/config/schema/simple_sitemap.schema.yml
+++ b/web/modules/simple_sitemap/config/schema/simple_sitemap.schema.yml
@@ -13,15 +13,15 @@ simple_sitemap.settings:
     generate_duration:
       label: 'Generation duration'
       type: integer
+    entities_per_queue_item:
+      label: 'Entities per queue item'
+      type: integer
     remove_duplicates:
       label: 'Remove duplicates'
       type: boolean
     skip_untranslated:
       label: 'Skip untranslated'
       type: boolean
-    disable_language_hreflang:
-      label: 'Disable language hreflang'
-      type: boolean
     xsl:
       label: 'Include a stylesheet in the sitemaps for humans'
       type: boolean
@@ -34,6 +34,12 @@ simple_sitemap.settings:
     custom_links_include_images:
       label: 'Include images of custom links'
       type: boolean
+    disable_language_hreflang:
+      label: 'Disable language hreflang'
+      type: boolean
+    hide_branding:
+      label: 'Hide branding'
+      type: boolean
     excluded_languages:
       label: 'Excluded languages'
       type: sequence
@@ -44,9 +50,6 @@ simple_sitemap.settings:
       type: sequence
       sequence:
         type: string
-    entities_per_queue_item:
-      label: 'Entities per queue item'
-      type: integer
 
 simple_sitemap.bundle_settings.*.*.*:
   label: 'Entity bundle settings'
@@ -85,19 +88,47 @@ simple_sitemap.custom_links.*:
             label: 'Change frequency'
             type: string
 
-simple_sitemap.variants.*:
-  label: 'Sitemap variants'
-  type: config_object
+simple_sitemap.sitemap.*:
+  label: 'Sitemaps'
+  type: config_entity
+  mapping:
+    id:
+      label: 'ID'
+      type: string
+    label:
+      label: 'Label'
+      type: label
+    description:
+      label: 'Description'
+      type: text
+    type:
+      label: 'Type'
+      type: string
+    weight:
+      label: 'Weight'
+      type: integer
+    status:
+      label: 'Status'
+      type: boolean
+
+simple_sitemap.type.*:
+  label: 'Sitemap types'
+  type: config_entity
   mapping:
-    variants:
-      label: 'Sitemap variants'
+    id:
+      label: 'ID'
+      type: string
+    label:
+      label: 'Label'
+      type: label
+    description:
+      label: 'Description'
+      type: text
+    sitemap_generator:
+      label: 'Sitemap generator'
+      type: string
+    url_generators:
+      label: 'URL generators'
       type: sequence
       sequence:
-        type: mapping
-        mapping:
-          label:
-            label: 'Variant label'
-            type: string
-          weight:
-            label: 'Weight'
-            type: integer
+        type: string
diff --git a/web/modules/simple_sitemap/css/simple_sitemap.sitemaps.css b/web/modules/simple_sitemap/css/simple_sitemap.sitemaps.css
index 7d98ac484248a7e203752eab86e3fc7bc6a62696..6a468745d232acf8830e3367680c8ee025d9643d 100644
--- a/web/modules/simple_sitemap/css/simple_sitemap.sitemaps.css
+++ b/web/modules/simple_sitemap/css/simple_sitemap.sitemaps.css
@@ -1,3 +1,3 @@
-#simple-sitemap-sitemaps-form .progress__bar {
+.progress .progress__bar {
   background-image: none;
 }
diff --git a/web/modules/simple_sitemap/drush.services.yml b/web/modules/simple_sitemap/drush.services.yml
index d2c7a694695df2fb14204cff70660711dd44d343..c127c2771946e7d3c8e15e8a80c53996c77d5bf4 100644
--- a/web/modules/simple_sitemap/drush.services.yml
+++ b/web/modules/simple_sitemap/drush.services.yml
@@ -1,6 +1,6 @@
 services:
   simple_sitemap.commands:
-    class: \Drupal\simple_sitemap\Commands\SimplesitemapCommands
+    class: Drupal\simple_sitemap\Commands\SimpleSitemapCommands
     arguments:
       - '@simple_sitemap.generator'
     tags:
diff --git a/web/modules/simple_sitemap/js/simple_sitemap.fieldsetSummaries.js b/web/modules/simple_sitemap/js/simple_sitemap.fieldsetSummaries.js
index 7de0e47622d965e70d1c4ae5cc4a0df6af7481f6..1e240ad1526a8bcd38df5086302f66142dc6e871 100644
--- a/web/modules/simple_sitemap/js/simple_sitemap.fieldsetSummaries.js
+++ b/web/modules/simple_sitemap/js/simple_sitemap.fieldsetSummaries.js
@@ -2,28 +2,33 @@
  * @file
  * Attaches simple_sitemap behaviors to the entity form.
  */
-(function($) {
+(function ($, Drupal) {
 
   "use strict";
 
-  Drupal.behaviors.simple_sitemapFieldsetSummaries = {
-    attach: function(context, settings) {
-      $(context).find('#edit-simple-sitemap').drupalSetSummary(function(context) {
-        var enabledVariants = [];
-        $('input:radio.enabled-for-variant').each(function() {
-          if ($(this).is(':checked') && $(this).val() == 1) {
-            enabledVariants.push($(this).attr('class').split(' ')[1])
-          }
+  Drupal.behaviors.simpleSitemapFieldsetSummaries = {
+    attach: function (context) {
+      $(context).find('.simple-sitemap-fieldset').drupalSetSummary(function (context) {
+        let summary = '', enabledVariants = [];
+
+        $(context).find('input:checkbox[name*="simple_sitemap_index_now"]').each(function () {
+          summary = (this.checked ? Drupal.t('IndexNow notification enabled') : Drupal.t('IndexNow notification disabled')) + ', ';
+        });
+
+        $(context).find('input:radio:checked[data-simple-sitemap-label][value="1"]').each(function () {
+          enabledVariants.push(this.dataset.simpleSitemapLabel);
         });
 
         if (enabledVariants.length > 0) {
-          return Drupal.t('Included in sitemap variants: ') + enabledVariants.join(', ');
+          summary += Drupal.t('Included in sitemaps: ') + enabledVariants.join(', ');
         }
         else {
-          return Drupal.t('Excluded from all sitemap variants');
+          summary += Drupal.t('Excluded from all sitemaps');
         }
 
+        return summary;
       });
     }
   };
-})(jQuery);
+
+})(jQuery, Drupal);
diff --git a/web/modules/simple_sitemap/js/simple_sitemap.sitemapEntities.js b/web/modules/simple_sitemap/js/simple_sitemap.sitemapEntities.js
index 0c4984ed80984c5d6d30f4c2d26ca76c338596ba..3b5a1a78f1290fa5e848d810f4f820120afa9adf 100644
--- a/web/modules/simple_sitemap/js/simple_sitemap.sitemapEntities.js
+++ b/web/modules/simple_sitemap/js/simple_sitemap.sitemapEntities.js
@@ -2,31 +2,39 @@
  * @file
  * Attaches simple_sitemap behaviors to the sitemap entities form.
  */
-(function($) {
+(function ($, Drupal) {
 
   "use strict";
 
-  Drupal.behaviors.simple_sitemapSitemapEntities = {
-    attach: function(context, settings) {
-      $.each(settings.simple_sitemap.all_entities, function(index, entityId) {
-        var target = '#edit-' + entityId + '-enabled';
-        triggerVisibility(target, entityId);
+  Drupal.behaviors.simpleSitemapEntities = {
+    attach: function () {
+      let $checkboxes = $('table tr input:checkbox:checked').once('simple-sitemap-entities');
 
-        $(target).change(function() {
-          triggerVisibility(target, entityId);
+      if ($checkboxes.length) {
+        $checkboxes.on('change', function () {
+          let $row = $(this).closest('tr');
+          let $table = $row.closest('table');
+
+          $row.toggleClass('color-success color-warning');
+
+          let showWarning = $table.find('tr.color-warning').length > 0;
+          let $warning = $('.simple-sitemap-entities-warning');
+
+          if (showWarning && !$warning.length) {
+            $(Drupal.theme('simpleSitemapEntitiesWarning')).insertBefore($table);
+          }
+          if (!showWarning && $warning.length) {
+            $warning.remove();
+          }
         });
-      });
-
-      function triggerVisibility(target, entityId) {
-        if ($(target).is(':checked')) {
-          $('#warning-' + entityId).hide();
-          $('#indexed-bundles-' + entityId).show();
-        }
-        else {
-          $('#warning-' + entityId).show();
-          $('#indexed-bundles-' + entityId).hide();
-        }
       }
     }
   };
-})(jQuery);
+
+  $.extend(Drupal.theme, {
+    simpleSitemapEntitiesWarning: function simpleSitemapEntitiesWarning() {
+      return '<div class="simple-sitemap-entities-warning messages messages--warning" role="alert">'.concat(Drupal.t('The sitemap settings and any per-entity overrides will be deleted for the unchecked entity types.'), '</div>');
+    }
+  });
+
+})(jQuery, Drupal);
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.settings.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.settings.yml
index bd3d661f5d6fc1e5b907e430aeb3d5b8f0144a72..f9616f76894f1756befb2481217890b83953f7ec 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.settings.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.settings.yml
@@ -1,2 +1,5 @@
 enabled: true
 submission_interval: 24
+index_now_enabled: true
+index_now_preferred_engine: ''
+index_now_on_entity_save: false
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.bing.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.bing.yml
index 3c03ba1f9576d7002236da42d827d8702bef29f8..2779c42d9dda9ff5947105cb88a27422a406be8d 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.bing.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.bing.yml
@@ -1,4 +1,5 @@
 id: bing
 label: 'Bing'
-url: https://www.bing.com/ping?sitemap=[sitemap]
+url: null
+index_now_url: https://bing.com/indexnow?url=[url]&key=[key]
 sitemap_variants: {  }
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.google.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.google.yml
index 30abc518e10fe19cf4e7a9cdcc59bf10b8aaf05d..1b1cf3dd452eb7b05e5e854b5aadd10e08ee2bf9 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.google.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.google.yml
@@ -1,4 +1,5 @@
 id: google
 label: 'Google'
 url: https://www.google.com/ping?sitemap=[sitemap]
+index_now_url: null
 sitemap_variants: {  }
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.index_now.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.index_now.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d6503a50fff99398c0ce066541090395fc4d527e
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.index_now.yml
@@ -0,0 +1,5 @@
+id: index_now
+label: 'IndexNow'
+url: null
+index_now_url: https://api.indexnow.org/indexnow?url=[url]&key=[key]
+sitemap_variants: {  }
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.yandex.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.yandex.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5dae8b85fc2505200bbdcae788e37e3a1e1aaed8
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/install/simple_sitemap_engines.simple_sitemap_engine.yandex.yml
@@ -0,0 +1,5 @@
+id: yandex
+label: 'Yandex'
+url: null
+index_now_url: https://yandex.com/indexnow?url=[url]&key=[key]
+sitemap_variants: {  }
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/schema/simple_sitemap_engines.schema.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/schema/simple_sitemap_engines.schema.yml
index f8290bd33d7e3fce6ff78d24afde090a9edec475..1ab8a0540016a687f357b52225b533f3a987671b 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/schema/simple_sitemap_engines.schema.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/config/schema/simple_sitemap_engines.schema.yml
@@ -11,6 +11,9 @@ simple_sitemap_engines.simple_sitemap_engine.*:
     url:
       type: string
       label: 'Submission URL'
+    index_now_url:
+      type: string
+      label: 'IndexNow URL'
     sitemap_variants:
       type: sequence
       label: 'Sitemap variants'
@@ -18,6 +21,14 @@ simple_sitemap_engines.simple_sitemap_engine.*:
         type: string
         label: 'Sitemap variant'
 
+simple_sitemap_engines.bundle_settings.*.*:
+  label: 'IndexNow bundle settings'
+  type: config_object
+  mapping:
+    index_now:
+      label: 'IndexNow'
+      type: boolean
+
 simple_sitemap_engines.settings:
   type: config_object
   label: 'Sitemap search engine submission settings'
@@ -28,3 +39,12 @@ simple_sitemap_engines.settings:
     submission_interval:
       type: integer
       label: 'Sitemap submission frequency'
+    index_now_enabled:
+      type: boolean
+      label: 'IndexNow submission enabled'
+    index_now_preferred_engine:
+      type: string
+      label: 'Selected engine for IndexNow submissions'
+    index_now_on_entity_save:
+      type: boolean
+      label: 'IndexNow on entity save'
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.info.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.info.yml
index bd11eb5d695d5c11fea6fd70359cf368e39952e1..284c155361b79ba046a708b9ee22744a897a5f21 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.info.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.info.yml
@@ -1,14 +1,13 @@
 name: 'Simple XML Sitemap (Search engines)'
 type: module
-description: 'Submits sitemaps to search engines.'
+description: 'Submits sitemaps to search engines and notifies IndexNow compatible engines about changes to entities.'
 configure: simple_sitemap_engines.settings
 package: SEO
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9.3 || ^10
 dependencies:
   - simple_sitemap:simple_sitemap
 
-# Information added by Drupal.org packaging script on 2021-10-16
-version: '8.x-3.11'
+# Information added by Drupal.org packaging script on 2023-06-09
+version: '4.1.6'
 project: 'simple_sitemap'
-datestamp: 1634343987
+datestamp: 1686288645
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.install b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.install
index 83448d61d2e923d379e82a99f4c24334fcb95048..6c021d98b56438dbe651125e90758353ee50d582 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.install
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.install
@@ -5,29 +5,68 @@
  * Module install and update procedures.
  */
 
+use Drupal\Core\Url;
+use Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine;
+use Drupal\simple_sitemap_engines\Form\SimplesitemapEnginesForm;
+
+/**
+ * Implements hook_requirements().
+ */
+function simple_sitemap_engines_requirements(string $phase): array {
+  $requirements = [];
+
+  if ($phase === 'runtime'
+    && \Drupal::config('simple_sitemap_engines.settings')->get('index_now_enabled')) {
+    switch (SimplesitemapEnginesForm::getKeyLocation()) {
+      case 'state':
+        $requirements['simple_sitemap_engines_index_now'] = [
+          'title' => t('Simple XML Sitemap IndexNow'),
+          'value' => t('Verification key in Drupal State'),
+          'description' => SimplesitemapEnginesForm::getKeyStatusMessage('state_warning'),
+          'severity' => REQUIREMENT_WARNING,
+        ];
+        break;
+
+      case NULL:
+        $requirements['simple_sitemap_engines_index_now'] = [
+          'title' => t('Simple XML Sitemap IndexNow'),
+          'value' => t('Verification key not available'),
+          'description' => SimplesitemapEnginesForm::getKeyStatusMessage('missing_warning'),
+          'severity' => REQUIREMENT_WARNING,
+        ];
+        break;
+    }
+  }
+
+  return $requirements;
+}
+
+/**
+ * Implements hook_install().
+ */
+function simple_sitemap_engines_install() {
+  \Drupal::messenger()->addWarning(t('In order to generate a verification key for the IndexNow service and choose which sitemaps are to be submitted to search engines, visit <a href="@url">this</a> configuration page.',
+    ['@url' => Url::fromRoute('simple_sitemap.engines.settings')->toString()])
+  );
+}
+
 /**
  * Implements hook_uninstall().
  */
 function simple_sitemap_engines_uninstall() {
   $state = \Drupal::service('state');
-  $state->delete('simple_sitemap_engines_last_submitted');
-
-  $engines = \Drupal::entityTypeManager()
-    ->getStorage('simple_sitemap_engine')
-    ->loadMultiple();
-  foreach ($engines as $engine_id => $engine) {
+  foreach (SimpleSitemapEngine::loadMultiple() as $engine_id => $engine) {
     $state->delete("simple_sitemap_engines.simple_sitemap_engine.{$engine_id}.last_submitted");
   }
+  $state->delete('simple_sitemap_engines.index_now.last');
+  $state->delete('simple_sitemap_engines.index_now.key');
 }
 
 /**
  * Moving last_submitted data from configuration to state.
  */
 function simple_sitemap_engines_update_8301() {
-  $engines = \Drupal::entityTypeManager()
-    ->getStorage('simple_sitemap_engine')
-    ->loadMultiple();
-  foreach ($engines as $engine_id => $engine) {
+  foreach (SimpleSitemapEngine::loadSitemapSubmissionEngines() as $engine_id => $engine) {
     $config = \Drupal::configFactory()
       ->getEditable("simple_sitemap_engines.simple_sitemap_engine.$engine_id");
     $last_submitted = $config->get('last_submitted');
@@ -48,3 +87,74 @@ function simple_sitemap_engines_update_8302() {
     $config->set('submission_interval', 24)->save();
   }
 }
+
+/**
+ * Enabling IndexNow functionality and adding index_now_url property to simple_sitemap_engine entities.
+ */
+function simple_sitemap_engines_update_8401() {
+  \Drupal::configFactory()->getEditable('simple_sitemap_engines.settings')->set('index_now_enabled', TRUE)->save();
+
+  foreach (SimpleSitemapEngine::loadMultiple() as $engine) {
+    $engine->save();
+  }
+
+  return 'For the IndexNow service to be used, a key needs to be generated. Visit admin/config/search/simplesitemap/engines/settings for more info.';
+}
+
+/**
+ * Updating the simple_sitemap_engine Bing entity to use the IndexNow service instead of the sitemap ping service.
+ */
+function simple_sitemap_engines_update_8402() {
+  if ($bing = SimpleSitemapEngine::load('bing')) {
+    $bing->index_now_url = 'https://bing.com/indexnow?url=[url]&key=[key]';
+    $bing->url = NULL;
+    $bing->save();
+
+    /** @var \Drupal\simple_sitemap\Settings $settings */
+    $settings = \Drupal::service('simple_sitemap.settings');
+
+    if (!empty($default_variant = $settings->get('default_variant'))) {
+      $config_factory = \Drupal::configFactory();
+
+      /** @var \Drupal\simple_sitemap\Manager\EntityManager $entity_manager */
+      $entity_manager = \Drupal::service('simple_sitemap.entity_manager');
+
+      $all_bundle_settings = $entity_manager
+        ->setVariants($default_variant)
+        ->getAllBundleSettings();
+      if (!empty($all_bundle_settings[$default_variant])) {
+        foreach ($all_bundle_settings[$default_variant] as $entity_type_name => $bundle_settings) {
+          foreach ($bundle_settings as $bundle_name => $settings) {
+            if (!empty($settings['index'])) {
+              $bundle_settings = $config_factory->getEditable("simple_sitemap_engines.bundle_settings.$entity_type_name.$bundle_name");
+              $bundle_settings->set('index_now', TRUE)->save();
+            }
+          }
+        }
+      }
+    }
+
+    return 'Bing has switched from using the sitemap ping service to using the IndexNow service. If bing was previously set to receive sitemap pings, it will now receive IndexNow notifications directly on entity form submission of indexed entities. Entity inclusion settings can be adjusted on the page admin/config/search/simplesitemap/entities.';
+  }
+}
+
+/**
+ * Creating new simple_sitemap_engine entities.
+ */
+function simple_sitemap_engines_update_8403() {
+  if (NULL === SimpleSitemapEngine::load('yandex')) {
+    SimpleSitemapEngine::create([
+      'id' => 'yandex',
+      'label' => 'Yandex',
+      'index_now_url' => "https://yandex.com/indexnow?url=[url]&key=[key]",
+    ])->save();
+  }
+
+  if (NULL === SimpleSitemapEngine::load('index_now')) {
+    SimpleSitemapEngine::create([
+      'id' => 'index_now',
+      'label' => 'IndexNow',
+      'index_now_url' => "https://api.indexnow.org/indexnow?url=[url]&key=[key]",
+    ])->save();
+  }
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.links.task.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.links.task.yml
index 7653fa58423ffd1912ec592cf5c4473deddbc6e2..d948f5675f08a94aa810f0addcf65f3f5487d3d4 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.links.task.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.links.task.yml
@@ -1,14 +1,14 @@
 simple_sitemap.engines:
   route_name: simple_sitemap.engines.status
   title: 'Search engines'
-  base_route: simple_sitemap.sitemaps
-  weight: 2
+  base_route: entity.simple_sitemap.collection
+  weight: 0
 
 simple_sitemap.engines.status:
   route_name: simple_sitemap.engines.status
   title: 'Status'
   parent_id: simple_sitemap.engines
-  weight: 0
+  weight: -1
 
 simple_sitemap.engines.settings:
   route_name: simple_sitemap.engines.settings
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.module b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.module
index d4a2a74aab9f9e812a21e03a6fea93700babbc44..ff457f5cdfac9d877475c1c1c94dc856233374d0 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.module
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.module
@@ -2,27 +2,29 @@
 
 /**
  * @file
- * Submits sitemaps to search engines.
+ * Main module file containing hooks.
  */
 
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine;
+
 /**
  * Implements hook_cron().
  *
  * If the sitemap submission interval has elapsed, adds each search engine to
  * the submission queue to be processed.
  *
- * @see Drupal\simple_sitemap_engines\Plugin\QueueWorker\SitemapSubmitter
+ * @see Drupal\simple_sitemap_engines\Plugin\QueueWorker\SitemapSubmittingWorker
  */
 function simple_sitemap_engines_cron() {
   $config = \Drupal::config('simple_sitemap_engines.settings');
 
   if ($config->get('enabled')) {
     $interval = (int) $config->get('submission_interval') * 60 * 60;
-    $request_time = \Drupal::service('datetime.time')->getRequestTime();
+    $request_time = \Drupal::time()->getRequestTime();
 
-    foreach (\Drupal::entityTypeManager()
-      ->getStorage('simple_sitemap_engine')
-      ->loadMultiple() as $id => $engine) {
+    foreach (SimpleSitemapEngine::loadSitemapSubmissionEngines() as $id => $engine) {
       $last_submitted = \Drupal::state()->get("simple_sitemap_engines.simple_sitemap_engine.{$id}.last_submitted", -1);
       if ($last_submitted !== -1
         && $last_submitted + $interval > $request_time) {
@@ -34,3 +36,89 @@ function simple_sitemap_engines_cron() {
     }
   }
 }
+
+/**
+ * Implements hook_form_alter().
+ */
+function simple_sitemap_engines_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  /** @var \Drupal\simple_sitemap_engines\Form\FormHelper $form_helper */
+  $form_helper = \Drupal::service('simple_sitemap.engines.form_helper');
+  $form_helper->formAlter($form, $form_state);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function simple_sitemap_engines_form_simple_sitemap_entity_bundles_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  /** @var \Drupal\simple_sitemap_engines\Form\FormHelper $form_helper */
+  $form_helper = \Drupal::service('simple_sitemap.engines.form_helper');
+  $form_helper->entityBundlesFormAlter($form);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function simple_sitemap_engines_form_simple_sitemap_entities_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  /** @var \Drupal\simple_sitemap_engines\Form\FormHelper $form_helper */
+  $form_helper = \Drupal::service('simple_sitemap.engines.form_helper');
+  $form_helper->entitiesFormAlter($form);
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function simple_sitemap_engines_entity_delete(EntityInterface $entity) {
+  /** @var \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter $submitter */
+  $submitter = \Drupal::service('simple_sitemap.engines.index_now_submitter');
+  $submitter->submitIfSubmittable($entity);
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function simple_sitemap_engines_entity_insert(EntityInterface $entity) {
+  /** @var \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter $submitter */
+  $submitter = \Drupal::service('simple_sitemap.engines.index_now_submitter');
+  $submitter->submitIfSubmittable($entity);
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function simple_sitemap_engines_entity_update(EntityInterface $entity) {
+  /** @var \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter $submitter */
+  $submitter = \Drupal::service('simple_sitemap.engines.index_now_submitter');
+  $submitter->submitIfSubmittable($entity);
+}
+
+/**
+ * Implements hook_entity_bundle_delete().
+ *
+ * Removes settings of the removed bundle.
+ */
+function simple_sitemap_engines_entity_bundle_delete($entity_type_id, $bundle) {
+  \Drupal::configFactory()
+    ->getEditable("simple_sitemap_engines.bundle_settings.$entity_type_id.$bundle")->delete();
+}
+
+/**
+ * Implements hook_entity_extra_field_info().
+ */
+function simple_sitemap_engines_entity_extra_field_info() {
+  $extra = [];
+
+  if (\Drupal::config('simple_sitemap_engines.settings')->get('index_now_enabled')) {
+    // Use the hook implementation from the main module as a base.
+    foreach (simple_sitemap_entity_extra_field_info() as $entity_type_id => $entity_type_info) {
+      foreach ($entity_type_info as $bundle_name => $bundle_info) {
+
+        $extra[$entity_type_id][$bundle_name]['form']['simple_sitemap_index_now'] = [
+          'label' => t('IndexNow notification'),
+          'description' => t('IndexNow notification checkbox'),
+          'weight' => 130,
+        ];
+      }
+    }
+  }
+  return $extra;
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.permissions.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.permissions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ebec43e923242c56e48c85cbff952c125e9eebe8
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.permissions.yml
@@ -0,0 +1,4 @@
+index entity on save:
+  title: 'Index entity on save'
+  description: 'Allows optionally sending a change notice to IndexNow compatible search engines on entity form save.'
+  restrict access: false
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.routing.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.routing.yml
index d5ca0dcb1be1b4d42c975a9468011a421b0fe7cc..735ef03bce0a72569f25bc42a09f0503ba3e58f2 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.routing.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.routing.yml
@@ -13,3 +13,12 @@ simple_sitemap.engines.settings:
     _title: 'Simple XML Sitemap'
   requirements:
     _permission: 'administer sitemap settings'
+
+simple_sitemap.engines.index_now.key:
+  path: '/simple_sitemap_engines/index_now_key/{key}'
+  defaults:
+    _controller: '\Drupal\simple_sitemap_engines\Controller\IndexNowController::getKeyFile'
+    _disable_route_normalizer: 'TRUE'
+  requirements:
+    # IndexNow key must be accessible for search engines.
+    _access: 'TRUE'
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.services.yml b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..385c42de99c303ae153fbae3faef424d9e8a29c2
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/simple_sitemap_engines.services.yml
@@ -0,0 +1,27 @@
+services:
+  simple_sitemap.engines.sitemap_submitter:
+    class: Drupal\simple_sitemap_engines\Submitter\SitemapSubmitter
+    public: true
+    arguments:
+      - '@http_client'
+      - '@simple_sitemap.logger'
+      - '@state'
+      - '@datetime.time'
+      - '@config.factory'
+
+  simple_sitemap.engines.index_now_submitter:
+    class: Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter
+    parent: simple_sitemap.engines.sitemap_submitter
+
+  simple_sitemap.engines.form_helper:
+    class: Drupal\simple_sitemap_engines\Form\FormHelper
+    parent: simple_sitemap.form_helper
+    arguments:
+      - '@config.factory'
+
+  simple_sitemap.engines.path_processor:
+    class: Drupal\simple_sitemap_engines\PathProcessor\IndexNowPathProcessor
+    arguments:
+      - '@simple_sitemap.engines.index_now_submitter'
+    tags:
+      - { name: path_processor_inbound, priority: 300 }
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Controller/IndexNowController.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Controller/IndexNowController.php
new file mode 100644
index 0000000000000000000000000000000000000000..3856bddced860b5a90ff698415c8504ad45b5a43
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Controller/IndexNowController.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Controller routines for IndexNow routes.
+ */
+class IndexNowController extends ControllerBase {
+
+  /**
+   * Sitemap submitting service.
+   *
+   * @var \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter
+   */
+  protected $submitter;
+
+  /**
+   * IndexNowController constructor.
+   *
+   * @param \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter $submitter
+   *   Sitemap submitting service.
+   */
+  public function __construct(IndexNowSubmitter $submitter) {
+    $this->submitter = $submitter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container): IndexNowController {
+    return new static(
+      $container->get('simple_sitemap.engines.index_now_submitter')
+    );
+  }
+
+  /**
+   * Return dynamically created text file content.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request object.
+   * @param string|null $key
+   *   The IndexNow key from the request.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   A response object.
+   */
+  public function getKeyFile(Request $request, ?string $key): Response {
+    if ($key
+      && ($saved_key = $this->submitter->getKey())
+      && $key === $saved_key) {
+      $response = new Response($key);
+      $response->headers->set('Content-Type', 'text/plain');
+
+      return $response;
+    }
+
+    throw new NotFoundHttpException();
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Controller/SearchEngineListBuilder.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Controller/SearchEngineListBuilder.php
deleted file mode 100644
index 67e0c2f981167d903212e491ee109016ba7c57f3..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Controller/SearchEngineListBuilder.php
+++ /dev/null
@@ -1,109 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap_engines\Controller;
-
-use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
-use Drupal\Core\Datetime\DateFormatterInterface;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\State\StateInterface;
-use Drupal\simple_sitemap\Form\FormHelper;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Search engine entity list builder.
- */
-class SearchEngineListBuilder extends ConfigEntityListBuilder {
-
-  /**
-   * The date formatter service.
-   *
-   * @var \Drupal\Core\Datetime\DateFormatterInterface
-   */
-  protected $dateFormatter;
-
-  /**
-   * @var \Drupal\Core\State\StateInterface
-   */
-  protected $state;
-
-  /**
-   * SearchEngineListBuilder constructor.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
-   *   The entity type definition.
-   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
-   *   The entity storage class.
-   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
-   *   The date formatter service.
-   * @param \Drupal\Core\State\StateInterface $state
-   *   The state service.
-   */
-  public function __construct(EntityTypeInterface $entity_type,
-                              EntityStorageInterface $storage,
-                              DateFormatterInterface $date_formatter,
-                              StateInterface $state) {
-    parent::__construct($entity_type, $storage);
-    $this->dateFormatter = $date_formatter;
-    $this->state = $state;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
-      $entity_type,
-      $container->get('entity_type.manager')->getStorage($entity_type->id()),
-      $container->get('date.formatter'),
-      $container->get('state')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildHeader() {
-    $header['label'] = $this->t('Name');
-    $header['url'] = $this->t('Submission URL');
-    $header['variants'] = $this->t('Sitemap variants');
-    $header['last_submitted'] = $this->t('Last submitted');
-
-    return $header;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildRow(EntityInterface $entity) {
-    $last_submitted = $this->state->get("simple_sitemap_engines.simple_sitemap_engine.{$entity->id()}.last_submitted", -1);
-
-    /** @var \Drupal\simple_sitemap_engines\Entity\SearchEngine $entity */
-    $row['label'] = $entity->label();
-    $row['url'] = $entity->url;
-    $row['variants'] = implode(', ', $entity->sitemap_variants);
-    $row['last_submitted'] = $last_submitted !== -1
-      ? $this->dateFormatter->format($last_submitted, 'short')
-      : $this->t('Never');
-
-    return $row;
-  }
-
-  /**
-   * Build the render array.
-   */
-  public function render() {
-    return [
-      'simple_sitemap_engines' => [
-        '#type' => 'details',
-        '#open' => TRUE,
-        '#prefix' => FormHelper::getDonationText(),
-        '#title' => $this->t('Submission status'),
-        'table' => parent::render(),
-        '#description' => $this->t('Submission settings can be configured <a href="@url">here</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/engines/settings']),
-      ],
-    ];
-  }
-
-}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SearchEngine.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SearchEngine.php
deleted file mode 100644
index 2628767fdb116a0d7f9dc2fc0b5e455c1b303ee1..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SearchEngine.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap_engines\Entity;
-
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-
-/**
- * Defines the the search engine entity class.
- *
- * @ConfigEntityType(
- *   id = "simple_sitemap_engine",
- *   label = @Translation("Search engine"),
- *   admin_permission = "administer sitemap settings",
- *   entity_keys = {
- *     "id" = "id",
- *     "label" = "label",
- *   },
- *   handlers = {
- *     "list_builder" = "Drupal\simple_sitemap_engines\Controller\SearchEngineListBuilder",
- *   },
- *   links = {
- *     "collection" = "/admin/config/search/simplesitemap/engines/list",
- *   },
- *   config_export = {
- *     "id",
- *     "label",
- *     "url",
- *     "sitemap_variants",
- *   }
- * )
- */
-class SearchEngine extends ConfigEntityBase {
-
-  /**
-   * The search engine ID.
-   *
-   * @var string
-   */
-  public $id;
-
-  /**
-   * The search engine label.
-   *
-   * @var string
-   */
-  public $label;
-
-  /**
-   * The search engine submission URL.
-   *
-   * When submitting to search engines, '[sitemap]' will be replaced with the
-   * full URL to the sitemap.xml.
-   *
-   * @var string
-   */
-  public $url;
-
-  /**
-   * List of sitemap variants to be submitted to this search engine.
-   *
-   * @var array
-   */
-  public $sitemap_variants;
-
-  /**
-   * Implements magic __toString() to simplify checkbox list building.
-   *
-   * @return string
-   *   The search engine label.
-   */
-  public function __toString() {
-    return $this->label();
-  }
-
-}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SimpleSitemapEngine.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SimpleSitemapEngine.php
new file mode 100644
index 0000000000000000000000000000000000000000..2c57bcc25512db947d2ee790d8c11ff7bec264ac
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SimpleSitemapEngine.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+
+/**
+ * Defines the search engine entity class.
+ *
+ * @ConfigEntityType(
+ *   id = "simple_sitemap_engine",
+ *   label = @Translation("Search engine"),
+ *   admin_permission = "administer sitemap settings",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *   },
+ *   handlers = {
+ *     "storage" = "\Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngineStorage",
+ *     "list_builder" = "Drupal\simple_sitemap_engines\SearchEngineListBuilder",
+ *   },
+ *   links = {
+ *     "collection" = "/admin/config/search/simplesitemap/engines/list",
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "url",
+ *     "index_now_url",
+ *     "sitemap_variants",
+ *   }
+ * )
+ */
+class SimpleSitemapEngine extends ConfigEntityBase {
+
+  /**
+   * The search engine ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The search engine label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The sitemap submission URL.
+   *
+   * When submitting to search engines, '[sitemap]' will be replaced with the
+   * full URL to the sitemap.xml.
+   *
+   * @var string
+   */
+  public $url;
+
+  /**
+   * List of sitemaps to be submitted to this search engine.
+   *
+   * @var array
+   */
+  public $sitemap_variants;
+
+  /**
+   * The IndexNow submission URL.
+   *
+   * When submitting to search engines, '[key]' and '[url]' will be replaced
+   * with the respective values.
+   *
+   * @var string
+   */
+  public $index_now_url;
+
+  /**
+   * Implementation of the magic __toString() method.
+   *
+   * @return string
+   *   The search engine label.
+   */
+  public function __toString() {
+    return (string) $this->label();
+  }
+
+  /**
+   * Whether the engine accepts sitemap submissions.
+   *
+   * @return bool
+   *   True if the engine accepts sitemap submissions.
+   */
+  public function hasSitemapSubmission() {
+    return (bool) $this->url;
+  }
+
+  /**
+   * Whether the engine accepts IndexNow submissions.
+   *
+   * @return bool
+   *   True if the engine accepts IndexNow submissions.
+   */
+  public function hasIndexNow() {
+    return (bool) $this->index_now_url;
+  }
+
+  /**
+   * Loads all engines capable of sitemap pinging.
+   *
+   * @return \Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine[]
+   *   Engines capable of sitemap pinging.
+   */
+  public static function loadSitemapSubmissionEngines(): array {
+    $ids = \Drupal::entityQuery('simple_sitemap_engine')
+      ->exists('url')
+      ->execute();
+
+    return static::loadMultiple($ids);
+  }
+
+  /**
+   * Loads all IndexNow capable engines.
+   *
+   * @return \Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine[]
+   *   IndexNow capable engines.
+   */
+  public static function loadIndexNowEngines(): array {
+    $ids = \Drupal::entityQuery('simple_sitemap_engine')
+      ->exists('index_now_url')
+      ->execute();
+
+    return static::loadMultiple($ids);
+  }
+
+  /**
+   * Loads a random IndexNow capable engine.
+   *
+   * @return \Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine|null
+   *   Random IndexNow capable engine or NULL if none given.
+   */
+  public static function loadRandomIndexNowEngine(): ?SimpleSitemapEngine {
+    if ($ids = \Drupal::entityQuery('simple_sitemap_engine')
+      ->exists('index_now_url')
+      ->execute()) {
+      return static::load(array_rand($ids));
+    }
+
+    return NULL;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SimpleSitemapEngineStorage.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SimpleSitemapEngineStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..4275b40878b706e8c4c1a74a3ac71069c6d8943a
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Entity/SimpleSitemapEngineStorage.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Storage handler for simple_sitemap_engine configuration entities.
+ */
+class SimpleSitemapEngineStorage extends ConfigEntityStorage {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doDelete($entities) {
+    $settings = $this->configFactory->getEditable('simple_sitemap_engines.settings');
+    if (!empty($default = $settings->get('index_now_preferred_engine'))) {
+      foreach ($entities as $entity) {
+        if ($default === $entity->id()) {
+          $settings->set('index_now_preferred_engine', NULL)->save();
+          break;
+        }
+      }
+    }
+    parent::doDelete($entities);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSave($id, EntityInterface $entity) {
+    if (empty($entity->index_now_url)) {
+      $settings = $this->configFactory->getEditable('simple_sitemap_engines.settings');
+      if (!empty($default = $settings->get('index_now_preferred_engine'))
+        && $default === $entity->id()) {
+        $settings->set('index_now_preferred_engine', NULL)->save();
+      }
+    }
+
+    return parent::doSave($id, $entity);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/FormHelper.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/FormHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..f062619998f2258d249f66819f3fa602c90fab1d
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/FormHelper.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Form;
+
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\simple_sitemap\Form\FormHelper as BaseFormHelper;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Manager\Generator;
+use Drupal\simple_sitemap\Settings;
+use Drupal\simple_sitemap_engines\Form\Handler\BundleEntityFormHandler;
+use Drupal\simple_sitemap_engines\Form\Handler\EntityFormHandler;
+
+/**
+ * Slightly altered version of the base form helper.
+ */
+class FormHelper extends BaseFormHelper {
+
+  protected const ENTITY_FORM_HANDLER = EntityFormHandler::class;
+  protected const BUNDLE_ENTITY_FORM_HANDLER = BundleEntityFormHandler::class;
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * FormHelper constructor.
+   *
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   Proxy for the current user account.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   */
+  public function __construct(
+    Generator $generator,
+    Settings $settings,
+    EntityHelper $entity_helper,
+    AccountProxyInterface $current_user,
+    ClassResolverInterface $class_resolver,
+    ConfigFactoryInterface $config_factory
+  ) {
+    parent::__construct($generator, $settings, $entity_helper, $current_user, $class_resolver);
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function formAlterAccess(): bool {
+    $access = parent::formAlterAccess();
+
+    return $this->configFactory->get('simple_sitemap_engines.settings')->get('index_now_enabled')
+      && ($access || $this->currentUser->hasPermission('index entity on save'));
+  }
+
+  /**
+   * Alters the specific form (simple_sitemap_entities_form).
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   *
+   * @see \Drupal\simple_sitemap\Form\EntitiesForm
+   * @see simple_sitemap_engines_form_simple_sitemap_entities_form_alter()
+   */
+  public function entitiesFormAlter(array &$form) {
+    if (!$this->formAlterAccess()) {
+      return;
+    }
+
+    $table = &$form['entity_types'];
+    $offset = array_search('bundles', array_keys($table['#header']), TRUE) + 1;
+    array_splice($table['#header'], $offset, 0, ['index_now' => $this->t('IndexNow')]);
+
+    // Add column with IndexNow status.
+    foreach (Element::children($table) as $entity_type_id) {
+      $element = ['#markup' => ''];
+
+      if ($table[$entity_type_id]['enabled']['#default_value']) {
+        $bundles = [];
+
+        foreach ($this->configFactory->listAll("simple_sitemap_engines.bundle_settings.$entity_type_id.") as $name) {
+          if ($this->configFactory->get($name)->get('index_now')) {
+            $name_parts = explode('.', $name);
+
+            $bundles[] = $this->entityHelper
+              ->getBundleLabel($entity_type_id, end($name_parts));
+          }
+        }
+
+        $element['#markup'] = $bundles
+          ? '<em>' . implode(', ', $bundles) . '</em>'
+          : $this->t('Excluded');
+      }
+
+      array_splice($table[$entity_type_id], $offset, 0, ['index_now' => $element]);
+    }
+  }
+
+  /**
+   * Alters the specific form (simple_sitemap_entity_bundles_form).
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   *
+   * @see \Drupal\simple_sitemap\Form\EntityBundlesForm
+   * @see simple_sitemap_engines_form_simple_sitemap_entity_bundles_form_alter()
+   */
+  public function entityBundlesFormAlter(array &$form) {
+    if (!$this->formAlterAccess()) {
+      return;
+    }
+
+    foreach ($form['settings'] as $bundle_name => &$bundle_form) {
+      $bundle_form = $this->bundleSettingsForm($bundle_form, $form['entity_type_id']['#value'], $bundle_name);
+      $bundle_form['simple_sitemap_index_now']['#tree'] = TRUE;
+    }
+
+    $form['#submit'][] = [$this, 'entityBundlesFormSubmit'];
+  }
+
+  /**
+   * Form submission handler (simple_sitemap_entity_bundles_form).
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\simple_sitemap\Form\EntityBundlesForm
+   */
+  public function entityBundlesFormSubmit(array &$form, FormStateInterface $form_state) {
+    $entity_type_id = $form_state->getValue('entity_type_id');
+
+    foreach ($form_state->getValue('bundles') as $bundle_name => $settings) {
+      $this->configFactory
+        ->getEditable("simple_sitemap_engines.bundle_settings.$entity_type_id.$bundle_name")
+        ->set('index_now', $settings['simple_sitemap_index_now'])
+        ->save();
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/BundleEntityFormHandler.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/BundleEntityFormHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..67f5a29eae1e9c5cb0ebd34f7b6717a9c78a4b3b
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/BundleEntityFormHandler.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Form\Handler;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Form\Handler\BundleEntityFormHandlerTrait;
+
+/**
+ * Defines the handler for bundle entity forms.
+ */
+class BundleEntityFormHandler extends EntityFormHandlerBase {
+
+  use BundleEntityFormHandlerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formAlter(array &$form, FormStateInterface $form_state) {
+    if (isset($form['simple_sitemap'])) {
+      parent::formAlter($form, $form_state);
+
+      $form['simple_sitemap'] = $this->settingsForm($form['simple_sitemap']);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $this->configFactory
+      ->getEditable("simple_sitemap_engines.bundle_settings.$this->entityTypeId.$this->bundleName")
+      ->set('index_now', $form_state->getValue('simple_sitemap_index_now'))
+      ->save();
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/EntityFormHandler.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/EntityFormHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..0952bdbb3858a9021f5c081c1b751fd49e1391b8
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/EntityFormHandler.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Form\Handler;
+
+use Drupal\Core\Entity\EntityPublishedInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\simple_sitemap\Form\Handler\EntityFormHandlerTrait;
+use Drupal\simple_sitemap_engines\Form\SimplesitemapEnginesForm;
+
+/**
+ * Defines the handler for entity forms.
+ */
+class EntityFormHandler extends EntityFormHandlerBase {
+
+  use EntityFormHandlerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $operations = ['default', 'edit', 'add', 'register', 'delete'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formAlter(array &$form, FormStateInterface $form_state) {
+    parent::formAlter($form, $form_state);
+    $form = $this->settingsForm($form);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form): array {
+    $form = parent::settingsForm($form);
+
+    $form['simple_sitemap_index_now']['#title'] = $this->t('Notify IndexNow search engines of changes <em>now</em>');
+    $form['simple_sitemap_index_now']['#description'] = $this->t('Send change notice to IndexNow compatible search engines right after submitting this form.');
+
+    // Sensibly place the IndexNow checkbox.
+    $form['simple_sitemap_index_now']['#group'] = 'footer';
+
+    if ($this->entity instanceof EntityPublishedInterface) {
+      $status_key = $this->entity->getEntityType()->getKey('status');
+
+      if ($status_key !== FALSE && isset($form[$status_key])) {
+        $index_now = $form['simple_sitemap_index_now']['#default_value'];
+
+        if (isset($form[$status_key]['#weight'])) {
+          $form['simple_sitemap_index_now']['#weight'] = $form[$status_key]['#weight'] + 10;
+        }
+
+        // If existing form entity is unpublished on load, assume it is a draft
+        // and uncheck IndexNow. Check IndexNow when changing publishing status.
+        if (!$this->entity->isNew() && !$this->entity->isPublished() && $index_now) {
+          $selector = ':input[name="' . $status_key . '[value]"]';
+
+          $form['simple_sitemap_index_now']['#default_value'] = 0;
+          $form['simple_sitemap_index_now']['#states'] = [
+            'checked' => [$selector => ['checked' => TRUE]],
+          ];
+        }
+
+        // If form entity is new, only check IndexNow when publishing status
+        // is checked.
+        if ($this->entity->isNew() && $index_now) {
+          $selector = ':input[name="' . $status_key . '[value]"]';
+
+          $form['simple_sitemap_index_now']['#states'] = [
+            'checked' => [$selector => ['checked' => TRUE]],
+          ];
+        }
+      }
+    }
+
+    // Remove access to IndexNow override checkbox if no verification key has
+    // been added.
+    if (SimplesitemapEnginesForm::getKeyLocation() === NULL) {
+      $form['simple_sitemap_index_now']['#access'] = FALSE;
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $this->entity->_simple_sitemap_index_now = (bool) $form_state->getValue('simple_sitemap_index_now');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function addSubmitHandlers(array &$element, callable ...$handlers) {
+    if (!empty($element['#submit'])) {
+      array_unshift($element['#submit'], ...$handlers);
+    }
+
+    // Process child elements.
+    foreach (Element::children($element) as $key) {
+      $this->addSubmitHandlers($element[$key], ...$handlers);
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/EntityFormHandlerBase.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/EntityFormHandlerBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b72f2a029fac0211ceebd88de58a03754de6c1a
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/Handler/EntityFormHandlerBase.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Form\Handler;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Form\Handler\EntityFormHandlerBase as BaseEntityFormHandlerBase;
+use Drupal\simple_sitemap\Form\FormHelper;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Manager\Generator;
+
+/**
+ * Defines a base class for altering an entity form.
+ */
+abstract class EntityFormHandlerBase extends BaseEntityFormHandlerBase {
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * EntityFormHandlerBase constructor.
+   *
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   */
+  public function __construct(Generator $generator, EntityHelper $entity_helper, FormHelper $form_helper, ConfigFactoryInterface $config_factory) {
+    parent::__construct($generator, $entity_helper, $form_helper);
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.form_helper'),
+      $container->get('config.factory')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formAlter(array &$form, FormStateInterface $form_state) {
+    $this->processFormState($form_state);
+    $this->addSubmitHandlers($form, [$this, 'submitForm']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form): array {
+    $settings = $this->getSettings();
+
+    $form['simple_sitemap_index_now'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Notify IndexNow search engines of changes <em>by default</em>'),
+      '#description' => $this->t('Send change notice to IndexNow compatible search engines right after submitting entity forms of this type.<br/>Changes include creating, deleting and updating of an entity. This setting can be overridden on the entity form.'),
+      '#default_value' => (int) ($settings['index_now'] ?? 0),
+      '#tree' => FALSE,
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSettings(): array {
+    if (!isset($this->settings)) {
+      $this->settings = $this->configFactory
+        ->get("simple_sitemap_engines.bundle_settings.$this->entityTypeId.$this->bundleName")
+        ->get();
+    }
+    return $this->settings;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/SimplesitemapEnginesForm.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/SimplesitemapEnginesForm.php
index 5241ad40a01457c8a4ad22f7d0a8ffc56e4c1302..8f2733a388e32186d3fa281c9c9f794e1349c355 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/SimplesitemapEnginesForm.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Form/SimplesitemapEnginesForm.php
@@ -4,11 +4,17 @@
 
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Datetime\DateFormatter;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\simple_sitemap\Form\FormHelper;
-use Drupal\simple_sitemap\SimplesitemapManager;
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -23,6 +29,13 @@ class SimplesitemapEnginesForm extends ConfigFormBase {
    */
   protected $entityTypeManager;
 
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
   /**
    * The date formatter service.
    *
@@ -31,11 +44,11 @@ class SimplesitemapEnginesForm extends ConfigFormBase {
   protected $dateFormatter;
 
   /**
-   * The sitemap manager service.
+   * The state service.
    *
-   * @var \Drupal\simple_sitemap\SimplesitemapManager
+   * @var \Drupal\Core\State\StateInterface
    */
-  protected $sitemapManager;
+  protected $state;
 
   /**
    * SimplesitemapEnginesForm constructor.
@@ -44,16 +57,23 @@ class SimplesitemapEnginesForm extends ConfigFormBase {
    *   The config factory service.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager service.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
    * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
    *   The date formatter service.
-   * @param \Drupal\simple_sitemap\SimplesitemapManager $sitemap_manager
-   *   The sitemap manager service.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, DateFormatter $date_formatter, SimplesitemapManager $sitemap_manager) {
+  public function __construct(ConfigFactoryInterface $config_factory,
+                              EntityTypeManagerInterface $entity_type_manager,
+                              EntityFieldManagerInterface $entity_field_manager,
+                              DateFormatter $date_formatter,
+                              StateInterface $state) {
     parent::__construct($config_factory);
     $this->entityTypeManager = $entity_type_manager;
+    $this->entityFieldManager = $entity_field_manager;
     $this->dateFormatter = $date_formatter;
-    $this->sitemapManager = $sitemap_manager;
+    $this->state = $state;
   }
 
   /**
@@ -63,8 +83,9 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
       $container->get('entity_type.manager'),
+      $container->get('entity_field.manager'),
       $container->get('date.formatter'),
-      $container->get('simple_sitemap.manager')
+      $container->get('state')
     );
   }
 
@@ -92,14 +113,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     $form['settings'] = [
       '#type' => 'fieldset',
-      '#title' => $this->t('General submission settings'),
-      '#prefix' => FormHelper::getDonationText(),
+      '#title' => $this->t('Sitemap submission settings'),
     ];
 
     $form['settings']['enabled'] = [
       '#type' => 'checkbox',
-      '#title' => $this->t('Submit the sitemap to search engines'),
-      '#description' => $this->t('This enables/disables sitemap submitting; don\'t forget to choose variants below.'),
+      '#title' => $this->t('Submit sitemaps to search engines'),
+      '#description' => $this->t("This enables/disables sitemap submission; don't forget to choose sitemaps below."),
       '#default_value' => $config->get('enabled'),
     ];
 
@@ -113,40 +133,190 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       ],
     ];
 
-    $form['engines'] = [
-      '#type' => 'fieldset',
-      '#title' => $this->t('Variant specific settings'),
-      '#markup' => '<div class="description">' . $this->t('Choose which sitemap variants are to be submitted to which search engines.<br>Variants can be configured <a href="@url">here</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/variants']) . '</div>',
+    $form['settings']['engines'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Engines'),
+      '#markup' => '<div class="description">'
+        . $this->t('Choose which sitemaps are to be submitted to which search engines.<br>Sitemaps can be configured <a href="@url">here</a>.',
+          ['@url' => Url::fromRoute('entity.simple_sitemap.collection')->toString()]
+        )
+        . '</div>',
+      '#open' => TRUE,
+      '#states' => [
+        'visible' => [':input[name="settings[enabled]"]' => ['checked' => TRUE]],
+      ],
     ];
 
-    $engines = $this->entityTypeManager->getStorage('simple_sitemap_engine')->loadMultiple();
-    foreach ($engines as $engine_id => $engine) {
-      $form['engines'][$engine_id] = [
-        '#type' => 'details',
-        '#title' => $engine->label(),
-        '#open' => !empty($engine->sitemap_variants) || count($engines) === 1,
-      ];
-      $form['engines'][$engine_id]['variants'] = [
+    $sitemaps = SimpleSitemap::loadMultiple();
+    foreach (SimpleSitemapEngine::loadSitemapSubmissionEngines() as $engine_id => $engine) {
+      $form['settings']['engines'][$engine_id] = [
         '#type' => 'select',
-        '#title' => $this->t('Sitemap variants'),
+        '#title' => $engine->label(),
         '#options' => array_map(
-          function ($variant) { return $this->t($variant['label']); },
-          $this->sitemapManager->getSitemapVariants(NULL, FALSE)
+          function ($sitemap) {
+            return $sitemap->label();
+          },
+          $sitemaps
         ),
         '#default_value' => $engine->sitemap_variants,
         '#multiple' => TRUE,
       ];
     }
 
+    $form['index_now'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('IndexNow settings'),
+    ];
+
+    $form['index_now']['enabled'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Submit changes to IndexNow capable engines'),
+      '#description' => $this->t('Send change notice to IndexNow compatible search engines right after submitting entity forms. Changes include creating, deleting and updating of an entity.<br/>This behaviour can be overridden on entity forms. Don\'t forget to <a href="@inclusion_url">include entities</a>.',
+        ['@inclusion_url' => Url::fromRoute('simple_sitemap.entities')->toString()]
+      ),
+      '#default_value' => $config->get('index_now_enabled'),
+    ];
+
+    $form['index_now']['preferred_engine'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Preferred IndexNow engine'),
+      '#description' => $this->t('All IndexNow requests will be sent to the engine selected here. Only one engine needs to be notified, as it will notify other IndexNow compatible engines for you.<br/>For the sake of equality of opportunity, <strong>consider leaving this at <em>Random</em></strong>, so a random engine can be picked on each submission.'),
+      '#default_value' => $config->get('index_now_preferred_engine'),
+      '#options' => ['' => '- ' . $this->t('Random') . ' -'] + array_map(function ($engine) {
+        return $engine->label();
+      }, SimpleSitemapEngine::loadIndexNowEngines()),
+      '#states' => [
+        'visible' => [':input[name="index_now[enabled]"]' => ['checked' => TRUE]],
+      ],
+    ];
+
+    $form['index_now']['on_entity_save'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Index on every entity save operation'),
+      '#description' => $this->t('If checked, all entity save operations for <a href="@inclusion_url">included entities</a> will trigger notification of IndexNow search engines.<br/>If unchecked, this will only be possible by adding/altering/deleting an entity through a form.<br/>This should be unchecked if there are mass operations performed on entities that are irrelevant to indexing.',
+        ['@inclusion_url' => Url::fromRoute('simple_sitemap.entities')->toString()]
+      ),
+      '#default_value' => $config->get('index_now_on_entity_save'),
+      '#states' => [
+        'visible' => [':input[name="index_now[enabled]"]' => ['checked' => TRUE]],
+      ],
+    ];
+
+    $key_location = self::getKeyLocation();
+    switch ($key_location) {
+      case 'settings':
+        $text = self::getKeyStatusMessage('settings_info');
+        break;
+
+      case 'settings_state':
+        $text = self::getKeyStatusMessage('settings_info');
+        $this->messenger()->addWarning(self::getKeyStatusMessage('settings_state_warning'));
+        break;
+
+      case 'state':
+        $text = self::getKeyStatusMessage('state_info');
+        $this->messenger()->addWarning(self::getKeyStatusMessage('state_warning'));
+        break;
+
+      default:
+        $text = self::getKeyStatusMessage('missing_warning');
+        $this->messenger()->addWarning($text);
+    }
+
+    $form['index_now']['key'] = [
+      '#type' => 'submit',
+      '#value' => in_array($key_location, ['state', 'settings_state'])
+        ? $this->t('Remove verification key from state')
+        : $this->t('Generate verification key'),
+      '#submit' => in_array($key_location, ['state', 'settings_state'])
+        ? [self::class . '::removeKey']
+        : [self::class . '::generateKey'],
+      '#disabled' => $key_location === 'settings',
+      '#validate' => [],
+      '#prefix' => '<p>' . $text . '</p>',
+    ];
+
     return parent::buildForm($form, $form_state);
   }
 
+  /**
+   * Gets the status message of the IndexNow key.
+   *
+   * @param string $type
+   *   The message's type.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The status message.
+   */
+  public static function getKeyStatusMessage(string $type): TranslatableMarkup {
+    $key = \Drupal::service('simple_sitemap.engines.index_now_submitter')->getKey();
+    switch ($type) {
+      case 'settings_info':
+        return t('The IndexNow verification key is saved in <em>settings.php</em>: @key', ['@key' => $key]);
+
+      case 'state_info':
+        return t('The IndexNow verification key is defined in <em>Drupal state</em>: @key', ['@key' => $key]);
+
+      case 'state_warning':
+        return t('The IndexNow verification key is saved in <em>Drupal state</em>. Consider defining it in <em>settings.php</em> like so:<br/>@code', ['@code' => '$settings[\'simple_sitemap_engines.index_now.key\'] = ' . "'$key';"]);
+
+      case 'settings_state_warning':
+        return t('The IndexNow verification key is saved in <em>settings.php</em> and can be safely removed from <em>Drupal state</em>.');
+
+      case 'missing_warning':
+      default:
+        return t('An IndexNow verification key needs to be generated and optionally added to <em>settings.php</em> in order for IndexNow engines to get notified about changes. This warning only applies to the production environment.');
+    }
+  }
+
+  /**
+   * Gets the location of the IndexNow key.
+   *
+   * @return string|null
+   *   The location of the IndexNow key.
+   */
+  public static function getKeyLocation(): ?string {
+    $settings = (bool) Settings::get('simple_sitemap_engines.index_now.key');
+    $state = (bool) \Drupal::state()->get('simple_sitemap_engines.index_now.key');
+
+    if ($settings && $state) {
+      return 'settings_state';
+    }
+    if ($settings) {
+      return 'settings';
+    }
+    if ($state) {
+      return 'state';
+    }
+
+    return NULL;
+  }
+
+  /**
+   * Generates a new IndexNow key and saves it to state.
+   */
+  public static function generateKey(): void {
+    \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_WARNING);
+
+    /** @var \Drupal\Component\Uuid\UuidInterface $uuid */
+    $uuid = \Drupal::service('uuid');
+    \Drupal::state()->set('simple_sitemap_engines.index_now.key', $uuid->generate());
+  }
+
+  /**
+   * Removes the IndexNow key from state.
+   */
+  public static function removeKey(): void {
+    \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_WARNING);
+    \Drupal::state()->delete('simple_sitemap_engines.index_now.key');
+  }
+
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    foreach ($this->entityTypeManager->getStorage('simple_sitemap_engine')->loadMultiple() as $engine_id => $engine) {
-      if (!empty($values = $form_state->getValue(['engines', $engine_id, 'variants']))) {
+    foreach (SimpleSitemapEngine::loadSitemapSubmissionEngines() as $id => $engine) {
+      if (!empty($values = $form_state->getValue(['settings', 'engines', $id]))) {
         $submit = TRUE;
       }
       $engine->sitemap_variants = $values;
@@ -156,12 +326,32 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $config = $this->config('simple_sitemap_engines.settings');
 
     $enabled = (bool) $form_state->getValue(['settings', 'enabled']);
-    $config->set('enabled', $enabled);
-    $config->set('submission_interval', $form_state->getValue(['settings', 'submission_interval']));
-    $config->save();
+    $index_now_enabled = (bool) $form_state->getValue(['index_now', 'enabled']);
+
+    // Clear necessary caches to apply field definition updates.
+    // @see simple_sitemap_engines_entity_extra_field_info()
+    if ($config->get('index_now_enabled') !== $index_now_enabled) {
+      $this->entityFieldManager->clearCachedFieldDefinitions();
+    }
+
+    $config->set('enabled', $enabled)
+      ->set('submission_interval', $form_state->getValue([
+        'settings',
+        'submission_interval',
+      ]))
+      ->set('index_now_enabled', $index_now_enabled)
+      ->set('index_now_preferred_engine', $form_state->getValue([
+        'index_now',
+        'preferred_engine',
+      ]))
+      ->set('index_now_on_entity_save', $form_state->getValue([
+        'index_now',
+        'on_entity_save',
+      ]))
+      ->save();
 
     if ($enabled && empty($submit)) {
-      $this->messenger()->addWarning($this->t('No sitemap variants have been selected for submission.'));
+      $this->messenger()->addWarning($this->t('No sitemaps have been selected for submission.'));
     }
 
     parent::submitForm($form, $form_state);
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/PathProcessor/IndexNowPathProcessor.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/PathProcessor/IndexNowPathProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..a9e06e48395b5f88c6e39fd8855137664e8e7e95
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/PathProcessor/IndexNowPathProcessor.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\PathProcessor;
+
+use Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter;
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Inbound path processor for IndexNow key requests.
+ *
+ * Make inbound IndexNow key text file requests go to a route that will
+ * return a dynamically created text file with the IndexNow key.
+ */
+class IndexNowPathProcessor implements InboundPathProcessorInterface {
+
+  /**
+   * Sitemap submitting service.
+   *
+   * @var \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter
+   */
+  protected $submitter;
+
+  /**
+   * IndexNowPathProcessor constructor.
+   *
+   * @param \Drupal\simple_sitemap_engines\Submitter\IndexNowSubmitter $submitter
+   *   Sitemap submitting service.
+   */
+  public function __construct(IndexNowSubmitter $submitter) {
+    $this->submitter = $submitter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processInbound($path, Request $request) {
+    $args = explode('/', $path ?? '');
+
+    if (count($args) === 2 && substr($args[1], -4) === '.txt') {
+      $key = $this->submitter->getKey();
+
+      if ($key && $key === substr($args[1], 0, -4)) {
+        return "/simple_sitemap_engines/index_now_key/$key";
+      }
+    }
+
+    return $path;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Plugin/QueueWorker/SitemapSubmitter.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Plugin/QueueWorker/SitemapSubmitter.php
deleted file mode 100644
index 021f3f8de3be541c09e7ca22fa7d107414affcf6..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Plugin/QueueWorker/SitemapSubmitter.php
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap_engines\Plugin\QueueWorker;
-
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Queue\QueueWorkerBase;
-use Drupal\simple_sitemap\Simplesitemap;
-use Drupal\simple_sitemap\Logger;
-use Drupal\Core\State\StateInterface;
-use GuzzleHttp\ClientInterface;
-use GuzzleHttp\Exception\RequestException;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Component\Datetime\TimeInterface;
-
-/**
- * Process a queue of search engines to submit sitemaps.
- *
- * @QueueWorker(
- *   id = "simple_sitemap_engine_submit",
- *   title = @Translation("Sitemap search engine submission"),
- *   cron = {"time" = 30}
- * )
- *
- * @see simple_sitemap_engines_cron()
- */
-class SitemapSubmitter extends QueueWorkerBase implements ContainerFactoryPluginInterface {
-
-  /**
-   * The EntityStorage service.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected $engineStorage;
-
-  /**
-   * The HTTP Client service.
-   *
-   * @var \GuzzleHttp\ClientInterface
-   */
-  protected $httpClient;
-
-  /**
-   * The sitemap generator service.
-   *
-   * @var \Drupal\simple_sitemap\Simplesitemap
-   */
-  protected $generator;
-
-  /**
-   * The Drupal logger service.
-   *
-   * @var \Drupal\simple_sitemap\Logger
-   */
-  protected $logger;
-
-  /**
-   * The Drupal state service.
-   *
-   * @var \Drupal\workflows\StateInterface
-   */
-  protected $state;
-
-  /**
-   * The time service.
-   *
-   * @var \Drupal\Component\Datetime\TimeInterface
-   */
-  protected $time;
-
-  /**
-   * SitemapSubmitter constructor.
-   *
-   * @param array $configuration
-   *   The config.
-   * @param string $plugin_id
-   *   The plugin id.
-   * @param array $plugin_definition
-   *   The plugin definition.
-   * @param \Drupal\Core\Entity\EntityStorageInterface $engine_storage
-   *   The EntityStorageInterface.
-   * @param \GuzzleHttp\ClientInterface $http_client
-   *   The client used to submit to engines.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   *   The generator service.
-   * @param \Drupal\simple_sitemap\Logger $logger
-   *   Standard logger.
-   * @param \Drupal\Core\State\StateInterface $state
-   *   Drupal state service for last submitted.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
-   */
-  public function __construct(array $configuration,
-                              $plugin_id,
-                              array $plugin_definition,
-                              EntityStorageInterface $engine_storage,
-                              ClientInterface $http_client,
-                              Simplesitemap $generator,
-                              Logger $logger,
-                              StateInterface $state,
-                              TimeInterface $time) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->engineStorage = $engine_storage;
-    $this->httpClient = $http_client;
-    $this->generator = $generator;
-    $this->logger = $logger;
-    $this->state = $state;
-    $this->time = $time;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static(
-      $configuration,
-      $plugin_id,
-      $plugin_definition,
-      $container->get('entity_type.manager')->getStorage('simple_sitemap_engine'),
-      $container->get('http_client'),
-      $container->get('simple_sitemap.generator'),
-      $container->get('simple_sitemap.logger'),
-      $container->get('state'),
-      $container->get('datetime.time')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function processItem($engine_id) {
-    /** @var \Drupal\simple_sitemap_engines\Entity\SearchEngine $engine */
-    if ($engine = $this->engineStorage->load($engine_id)) {
-
-      $sitemap_urls = [];
-      $manager = $this->generator->getSitemapManager();
-
-      foreach ($manager->getSitemapTypes() as $type_name => $type_definition) {
-        $sitemap_generator = $manager->getSitemapGenerator($type_definition['sitemapGenerator']);
-
-        // Submit all variants that are enabled for this search engine.
-        foreach ($manager->getSitemapVariants($type_name, FALSE) as $variant_id => $variant_definition) {
-          if (in_array($variant_id, $engine->sitemap_variants)
-            && FALSE !== $this->generator->setVariants($variant_id)->getSitemap()) {
-            $sitemap_urls[$variant_definition['label']] = $sitemap_generator->setSitemapVariant($variant_id)->getSitemapUrl();
-          }
-        }
-      }
-
-      // Submit all URLs.
-      foreach ($sitemap_urls as $variant => $sitemap_url) {
-        $submit_url = str_replace('[sitemap]', $sitemap_url, $engine->url);
-        try {
-          $this->httpClient->request('GET', $submit_url);
-          // Log if submission was successful.
-          $this->logger->m('Sitemap @variant submitted to @url', ['@variant' => $variant, '@url' => $submit_url])->log();
-          // Record last submission time. This is purely informational; the
-          // variable that determines when the next submission should be run is
-          // stored in the global state.
-          $this->state->set("simple_sitemap_engines.simple_sitemap_engine.{$engine_id}.last_submitted", $this->time->getRequestTime());
-        }
-        catch (RequestException $e) {
-          // Catch and log exceptions so this submission gets removed from the
-          // queue whether or not it succeeded.
-          // If the error was caused by network failure, it's fine to just wait
-          // until next time the submission is queued to try again.
-          // If the error was caused by a malformed URL, keeping the submission
-          // in the queue to retry is pointless since it will always fail.
-          watchdog_exception('simple_sitemap', $e);
-        }
-      }
-    }
-  }
-
-}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Plugin/QueueWorker/SitemapSubmittingWorker.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Plugin/QueueWorker/SitemapSubmittingWorker.php
new file mode 100644
index 0000000000000000000000000000000000000000..a480722493bd9a83f9145d8b0a298f27f464c37c
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Plugin/QueueWorker/SitemapSubmittingWorker.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Plugin\QueueWorker;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Queue\QueueWorkerBase;
+use Drupal\simple_sitemap_engines\Submitter\SitemapSubmitter;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Process a queue of search engines to submit sitemaps.
+ *
+ * @QueueWorker(
+ *   id = "simple_sitemap_engine_submit",
+ *   title = @Translation("Sitemap search engine submission"),
+ *   cron = {"time" = 30}
+ * )
+ *
+ * @see simple_sitemap_engines_cron()
+ */
+class SitemapSubmittingWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The sitemap_submitter service.
+   *
+   * @var \Drupal\simple_sitemap_engines\Submitter\SitemapSubmitter
+   */
+  protected $sitemapSubmitter;
+
+  /**
+   * SitemapSubmitter constructor.
+   *
+   * @param array $configuration
+   *   The config.
+   * @param string $plugin_id
+   *   The plugin id.
+   * @param array $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\simple_sitemap_engines\Submitter\SitemapSubmitter $sitemap_submitter
+   *   Sitemap submitter service.
+   */
+  public function __construct(array $configuration,
+                                    $plugin_id,
+                              array $plugin_definition,
+                              SitemapSubmitter $sitemap_submitter) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->sitemapSubmitter = $sitemap_submitter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): SitemapSubmittingWorker {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('simple_sitemap.engines.sitemap_submitter')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processItem($engine_id) {
+    $this->sitemapSubmitter->process($engine_id);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/SearchEngineListBuilder.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/SearchEngineListBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..38b5edba2897dc79540d8784cc4e7e5c85353bea
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/SearchEngineListBuilder.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Datetime\DateFormatterInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Search engine entity list builder.
+ */
+class SearchEngineListBuilder extends ConfigEntityListBuilder {
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
+
+  /**
+   * The state key/value store.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $config;
+
+  /**
+   * SearchEngineListBuilder constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
+   *   The entity storage class.
+   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
+   *   The date formatter service.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   */
+  public function __construct(EntityTypeInterface $entity_type,
+                              EntityStorageInterface $storage,
+                              DateFormatterInterface $date_formatter,
+                              StateInterface $state,
+                              ConfigFactoryInterface $config_factory) {
+    parent::__construct($entity_type, $storage);
+    $this->dateFormatter = $date_formatter;
+    $this->state = $state;
+    $this->config = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity_type.manager')->getStorage($entity_type->id()),
+      $container->get('date.formatter'),
+      $container->get('state'),
+      $container->get('config.factory')
+    );
+  }
+
+  /**
+   * Build the render array.
+   */
+  public function render(): array {
+    return [
+      'sitemap_submission_engines' => $this->renderSitemapSubmissionEngines(),
+      'index_now_engines' => $this->renderIndexNowEngines(),
+    ];
+  }
+
+  /**
+   * Render sitemap submission engines.
+   *
+   * @return array
+   *   The build array.
+   */
+  protected function renderSitemapSubmissionEngines(): array {
+    $enabled = (bool) $this->config->get('simple_sitemap_engines.settings')->get('enabled');
+    $build = [
+      '#type' => 'details',
+      '#open' => $enabled,
+      '#title' => $this->t('Sitemap submission status'),
+      'table' => [
+        '#type' => 'table',
+        '#header' => [
+          'label' => $this->t('Name'),
+          'url' => $this->t('Submission URL'),
+          'variants' => $this->t('Sitemaps'),
+          'last_submitted' => $this->t('Last submitted'),
+        ],
+        '#rows' => [],
+        '#empty' => $this->t('There are no @label yet.', ['@label' => $this->entityType->getPluralLabel()]),
+      ],
+      '#description' => $this->t('Submission settings can be configured <a href="@url">here</a>.',
+        ['@url' => Url::fromRoute('simple_sitemap.engines.settings')->toString()]
+      ),
+    ];
+
+    if ($enabled) {
+      foreach (SimpleSitemapEngine::loadSitemapSubmissionEngines() as $entity) {
+        $last_submitted = $this->state->get("simple_sitemap_engines.simple_sitemap_engine.{$entity->id()}.last_submitted", -1);
+        $build['table']['#rows'][$entity->id()] = [
+          'label' => $entity->label(),
+          'url' => $entity->url,
+          'variants' => implode(', ', $entity->sitemap_variants),
+          'last_submitted' => $last_submitted !== -1
+            ? $this->dateFormatter->format($last_submitted, 'short')
+            : $this->t('Never'),
+        ];
+      }
+    }
+
+    $build['table']['#empty'] = $enabled
+      ? $this->t('No search engines supporting sitemap submission have been found.')
+      : $this->t('Sitemap submission is disabled.');
+
+    return $build;
+  }
+
+  /**
+   * Render IndexNow engines.
+   *
+   * @return array
+   *   The build array.
+   */
+  protected function renderIndexNowEngines(): array {
+    $enabled = (bool) $this->config->get('simple_sitemap_engines.settings')->get('index_now_enabled');
+    $info = $this->state->get('simple_sitemap_engines.index_now.last');
+    $build = [
+      '#type' => 'details',
+      '#open' => $enabled,
+      '#title' => $this->t('IndexNow status'),
+      'table' => [
+        '#type' => 'table',
+        '#suffix' => $enabled && $info ? $this->t("The last IndexNow submission was <em>@entity</em> to @engine_label on @time", [
+          '@entity' => $info['entity_label'] ?: $info['entity'],
+          '@engine_label' => $info['engine_label'],
+          '@time' => $this->dateFormatter->format($info['time'], 'short'),
+        ]) : '',
+        '#header' => [
+          'label' => $this->t('Name'),
+          'url' => $this->t('IndexNow URL'),
+        ],
+        '#rows' => [],
+      ],
+      '#description' => $this->t('IndexNow settings can be configured <a href="@url">here</a>.',
+        ['@url' => Url::fromRoute('simple_sitemap.engines.settings')->toString()]
+      ),
+    ];
+
+    if ($enabled) {
+      foreach (SimpleSitemapEngine::loadIndexNowEngines() as $engine) {
+        $build['table']['#rows'][$engine->id()] = [
+          'label' => $engine->label(),
+          'url' => $engine->index_now_url,
+        ];
+      }
+    }
+
+    $build['table']['#empty'] = $enabled
+      ? $this->t('No search engines supporting IndexNow have been found.')
+      : $this->t('IndexNow submission is disabled.');
+
+    return $build;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/IndexNowSubmitter.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/IndexNowSubmitter.php
new file mode 100644
index 0000000000000000000000000000000000000000..fe67ac866ce049025fae844772e4c0c273f64ac9
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/IndexNowSubmitter.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Submitter;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\SynchronizableInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine;
+
+/**
+ * Sitemap submitting service.
+ */
+class IndexNowSubmitter extends SubmitterBase {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $config;
+
+  /**
+   * The entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * The IndexNow key.
+   *
+   * @var string|false
+   */
+  protected $key;
+
+  /**
+   * The search IndexNow capable search engine entity.
+   *
+   * @var \Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine|false
+   */
+  protected $engine;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function onSuccess(): void {
+    $this->logger->m('Entity <em>@entity</em> was submitted to @engine.', [
+      '@entity' => $this->entity->getEntityTypeId() . ':' . $this->entity->id(),
+      '@engine' => $this->engine->label(),
+    ])->log();
+
+    $this->state->set(
+      'simple_sitemap_engines.index_now.last', [
+        'time' => $this->time->getRequestTime(),
+        'engine' => $this->engine->id(),
+        'engine_label' => $this->engine->label(),
+        'entity' => $this->entity->getEntityTypeId() . ':' . $this->entity->id(),
+        'entity_label' => $this->entity->label() ?: '',
+      ]
+    );
+  }
+
+  /**
+   * Gets the IndexNow key.
+   *
+   * @return string|false
+   *   The IndexNow key or FALSE if the key is not set.
+   */
+  public function getKey() {
+    if ($this->key === NULL
+      && empty($this->key = (string) Settings::get('simple_sitemap_engines.index_now.key'))
+      && empty($this->key = (string) $this->state->get('simple_sitemap_engines.index_now.key'))
+    ) {
+      $this->key = FALSE;
+    }
+
+    return $this->key;
+  }
+
+  /**
+   * Gets search engine entity.
+   *
+   * @return \Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine|false
+   *   The search engine entity.
+   */
+  protected function getEngine() {
+    if ($this->engine === NULL) {
+      if ($id = $this->config->get('simple_sitemap_engines.settings')->get('index_now_preferred_engine')) {
+        $engine = SimpleSitemapEngine::load($id);
+        $this->engine = $engine && $engine->hasIndexNow() ? $engine : FALSE;
+      }
+      elseif ($engine = SimpleSitemapEngine::loadRandomIndexNowEngine()) {
+        $this->engine = $engine;
+      }
+      else {
+        $this->engine = FALSE;
+      }
+    }
+
+    return $this->engine;
+  }
+
+  /**
+   * Submit entity URL if it is set to be submitted.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to submit.
+   *
+   * @throws \Drupal\Core\Entity\EntityMalformedException
+   * @throws \GuzzleHttp\Exception\GuzzleException
+   */
+  public function submitIfSubmittable(EntityInterface $entity) {
+    if ($this->config->get('simple_sitemap_engines.settings')->get('index_now_enabled')) {
+      // Do not act on syncing operations (migration/import/...)
+      if ($entity instanceof SynchronizableInterface && $entity->isSyncing()) {
+        return;
+      }
+
+      // Entity was saved outside its entity form - indexing depending
+      // on module and entity inclusion settings.
+      if (!isset($entity->_simple_sitemap_index_now)) {
+        if ($this->config->get('simple_sitemap_engines.settings')->get('index_now_on_entity_save')
+          && $this->config->get("simple_sitemap_engines.bundle_settings.{$entity->getEntityTypeId()}.{$entity->bundle()}")->get('index_now')) {
+          $this->submit($entity);
+        }
+      }
+
+      // Form submission occurred, so we are indexing the entity depending
+      // on the form settings.
+      else {
+        if ($entity->_simple_sitemap_index_now) {
+          $this->submit($entity);
+        }
+        unset($entity->_simple_sitemap_index_now);
+      }
+    }
+  }
+
+  /**
+   * Submit entity URL.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to submit.
+   *
+   * @throws \Drupal\Core\Entity\EntityMalformedException
+   * @throws \GuzzleHttp\Exception\GuzzleException
+   */
+  public function submit(EntityInterface $entity): void {
+    $this->entity = $entity;
+
+    if (!$this->getEngine() || (!$this->getKey())) {
+      return;
+    }
+
+    $base_url = $this->config->get('simple_sitemap.settings')->get('base_url');
+    $url_options = $base_url ? ['base_url' => $base_url] : [];
+    $this->request(
+      str_replace(
+        ['[url]', '[key]'],
+        // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
+        [urlencode($entity->toUrl('canonical', $url_options)->setAbsolute()->toString()), $this->getKey()],
+        $this->getEngine()->index_now_url
+      )
+    );
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/SitemapSubmitter.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/SitemapSubmitter.php
new file mode 100644
index 0000000000000000000000000000000000000000..46241e3afdf0d4371b68c3f8bd9b854a81305f4e
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/SitemapSubmitter.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Submitter;
+
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap_engines\Entity\SimpleSitemapEngine;
+
+/**
+ * Sitemap submitting service.
+ */
+class SitemapSubmitter extends SubmitterBase {
+
+  /**
+   * ID of search engine to be submitted to.
+   *
+   * @var string
+   */
+  protected $engineId;
+
+  /**
+   * Currently processed sitemap variant.
+   *
+   * @var string
+   */
+  protected $currentVariant;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function onSuccess(): void {
+    $this->logger->m('Sitemap @variant submitted to @url', [
+      '@variant' => $this->currentVariant,
+      '@url' => $this->url,
+    ])->log();
+
+    $this->state->set(
+      "simple_sitemap_engines.simple_sitemap_engine.{$this->engineId}.last_submitted",
+      $this->time->getRequestTime()
+    );
+  }
+
+  /**
+   * Sends sitemap URLs to a specific engine.
+   *
+   * @param string $engine_id
+   *   ID of search engine to be submitted to.
+   *
+   * @throws \Drupal\Core\Entity\EntityMalformedException
+   * @throws \GuzzleHttp\Exception\GuzzleException
+   */
+  public function process(string $engine_id) {
+    $this->engineId = $engine_id;
+    if ($engine = SimpleSitemapEngine::load($this->engineId)) {
+      // Submit all variants that are enabled for this search engine.
+      foreach (SimpleSitemap::loadMultiple($engine->sitemap_variants) as $variant => $sitemap) {
+        if ($sitemap->status()) {
+          $this->currentVariant = $variant;
+          $url = str_replace('[sitemap]', $sitemap->toUrl()->toString(), $engine->url);
+          $this->request($url);
+        }
+      }
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/SubmitterBase.php b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/SubmitterBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ad04744878017053453049b88bea566e8052059
--- /dev/null
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_engines/src/Submitter/SubmitterBase.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Drupal\simple_sitemap_engines\Submitter;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\simple_sitemap\Logger;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Exception\TransferException;
+
+/**
+ * Base class for submitter services.
+ */
+abstract class SubmitterBase {
+
+  /**
+   * The HTTP Client service.
+   *
+   * @var \GuzzleHttp\ClientInterface
+   */
+  protected $httpClient;
+
+  /**
+   * Simple XML Sitemap logger.
+   *
+   * @var \Drupal\simple_sitemap\Logger
+   */
+  protected $logger;
+
+  /**
+   * The Drupal state service.
+   *
+   * @var \Drupal\workflows\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $config;
+
+  /**
+   * URL to be submitted.
+   *
+   * @var string
+   */
+  protected $url;
+
+  /**
+   * SitemapSubmitter constructor.
+   *
+   * @param \GuzzleHttp\ClientInterface $http_client
+   *   The client used to submit to engines.
+   * @param \Drupal\simple_sitemap\Logger $logger
+   *   Sitemap logger.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   Drupal state service for last submitted.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   */
+  public function __construct(ClientInterface $http_client,
+                              Logger $logger,
+                              StateInterface $state,
+                              TimeInterface $time,
+                              ConfigFactoryInterface $config_factory) {
+    $this->httpClient = $http_client;
+    $this->logger = $logger;
+    $this->state = $state;
+    $this->time = $time;
+    $this->config = $config_factory;
+  }
+
+  /**
+   * Visits a URL and logs failures.
+   *
+   * @param string $url
+   *   URL to visit.
+   *
+   * @return bool
+   *   TRUE on success and FALSE on failure.
+   *
+   * @throws \GuzzleHttp\Exception\GuzzleException
+   */
+  protected function request(string $url): bool {
+    $this->url = $url;
+    try {
+      $this->httpClient->request('GET', $url);
+      $this->onSuccess();
+      return TRUE;
+    }
+    catch (TransferException $e) {
+      watchdog_exception('simple_sitemap_engines', $e);
+      $this->onFailure();
+      return FALSE;
+    }
+  }
+
+  /**
+   * Actions to be performed on successful URL request.
+   */
+  protected function onSuccess(): void {
+    $this->logger->m('Submission to @url was successful.', ['@url' => $this->url])->log();
+  }
+
+  /**
+   * Actions to be performed on unsuccessful URL request.
+   */
+  protected function onFailure(): void {}
+
+}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/js/simple_sitemap.viewsUi.js b/web/modules/simple_sitemap/modules/simple_sitemap_views/js/simple_sitemap.viewsUi.js
index a6ca3d83f2712b09dfc17fd4191d3c5b2b833968..393df1a5a3081f660912b2380319a3b098d7332c 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/js/simple_sitemap.viewsUi.js
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/js/simple_sitemap.viewsUi.js
@@ -8,11 +8,11 @@
 
   Drupal.behaviors.simpleSitemapViewsUiArguments = {
     attach: function attach() {
-      var $arguments = $('.indexed-arguments').once('simple-sitemap-views-ui-arguments');
+      let $arguments = $('.indexed-arguments').once('simple-sitemap-views-ui-arguments');
 
       if ($arguments.length) {
         $arguments.each(function () {
-          var $checkboxes = $(this).find('input[type="checkbox"]');
+          let $checkboxes = $(this).find('input[type="checkbox"]');
 
           if ($checkboxes.length) {
             new Drupal.simpleSitemapViewsUi.Arguments($checkboxes);
@@ -28,7 +28,7 @@
   };
 
   Drupal.simpleSitemapViewsUi.Arguments.prototype.changeHandler = function (e) {
-    var $checkbox = $(e.target), index = this.$checkboxes.index($checkbox);
+    let $checkbox = $(e.target), index = this.$checkboxes.index($checkbox);
     $checkbox.prop('checked') ? this.check(index) : this.uncheck(index);
   };
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.info.yml b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.info.yml
index 57e79884eff6bfbf061e233d800600059bd4b529..78ef0bedd2656eb6653cd51f0aee6a0fd5b9a1a7 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.info.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.info.yml
@@ -3,13 +3,12 @@ type: module
 description: 'Provides integration of the Simple XML Sitemap module with the Views module.'
 configure: simple_sitemap.settings_views
 package: SEO
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9.3 || ^10
 dependencies:
   - simple_sitemap:simple_sitemap
   - drupal:views
 
-# Information added by Drupal.org packaging script on 2021-10-16
-version: '8.x-3.11'
+# Information added by Drupal.org packaging script on 2023-06-09
+version: '4.1.6'
 project: 'simple_sitemap'
-datestamp: 1634343987
+datestamp: 1686288645
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.install b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.install
index 4b146b609048ea04abdc53cc611565efee6e14dc..43d7783237446174ea5924eebaff80c2436bf512 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.install
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.install
@@ -5,12 +5,19 @@
  * Install and uninstall hooks for the simple_sitemap_views module.
  */
 
+use Drupal\Core\Url;
+
 /**
  * Implements hook_install().
  */
 function simple_sitemap_views_install() {
   // Enable views display extender plugin.
-  \Drupal::service('simple_sitemap.views')->enable();
+  /** @var \Drupal\simple_sitemap_views\SimpleSitemapViews $simple_sitemap_views */
+  $simple_sitemap_views = \Drupal::service('simple_sitemap.views');
+  $simple_sitemap_views->enable();
+  \Drupal::messenger()->addWarning(t('In order to use the Simple XML Sitemap Views module, the Views URL generator must be <a href="@url">added</a> to a sitemap type.',
+      ['@url' => Url::fromRoute('entity.simple_sitemap_type.collection')->toString()])
+  );
 }
 
 /**
@@ -18,7 +25,9 @@ function simple_sitemap_views_install() {
  */
 function simple_sitemap_views_uninstall() {
   // Disable views display extender plugin.
-  \Drupal::service('simple_sitemap.views')->disable();
+  /** @var \Drupal\simple_sitemap_views\SimpleSitemapViews $simple_sitemap_views */
+  $simple_sitemap_views = \Drupal::service('simple_sitemap.views');
+  $simple_sitemap_views->disable();
 }
 
 /**
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.libraries.yml b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.libraries.yml
index 1e9d81cee5c04c1a17e03d014265a16dfd961b9a..8b042f2d07af374a01521eb9d0f20d88b2538f7d 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.libraries.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.libraries.yml
@@ -5,4 +5,4 @@ viewsUi:
   dependencies:
     - core/jquery
     - core/drupal
-    - core/jquery.once
+    - core/once
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.links.task.yml b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.links.task.yml
index 3ed5f37cb3c4d6c5aaa844e9bd8fb824c0ba90b9..8d3763e69180697d4e00adc0813153f77824ee37 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.links.task.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.links.task.yml
@@ -2,4 +2,4 @@ simple_sitemap.views:
   route_name: simple_sitemap.views
   title: 'Views'
   parent_id: simple_sitemap.inclusion
-  weight: 2
+  weight: 1
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.module b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.module
index cdd757d89e9893d8db5d1c5fa9df0964fe00c375..fed522a4ab394aa2bdb910fb2f75a1acf88a845e 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.module
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.module
@@ -10,15 +10,7 @@
  */
 function simple_sitemap_views_cron() {
   // Create tasks in the garbage collection queue.
-  \Drupal::service('simple_sitemap.views')->executeGarbageCollection();
-}
-
-/**
- * Implements hook_simple_sitemap_sitemap_types_alter().
- */
-function simple_sitemap_views_simple_sitemap_sitemap_types_alter(array &$sitemap_types) {
-  // Add a 'views' UrlGenerator plugin to the default hreflang sitemap type.
-  if (isset($sitemap_types['default_hreflang'])) {
-    $sitemap_types['default_hreflang']['urlGenerators'][] = 'views';
-  }
+  /** @var \Drupal\simple_sitemap_views\SimpleSitemapViews $simple_sitemap_views */
+  $simple_sitemap_views = \Drupal::service('simple_sitemap.views');
+  $simple_sitemap_views->executeGarbageCollection();
 }
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.services.yml b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.services.yml
index e9402ce2ce0dc43dffbdcaa9070c799e93283085..94f8d26c01165cfb29b8fcc49aa18a04233996ef 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.services.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/simple_sitemap_views.services.yml
@@ -1,9 +1,17 @@
 services:
   simple_sitemap.views:
     class: Drupal\simple_sitemap_views\SimpleSitemapViews
-    arguments: ['@simple_sitemap.manager', '@entity_type.manager', '@config.factory', '@queue', '@database']
+    arguments:
+      - '@entity_type.manager'
+      - '@config.factory'
+      - '@queue'
+      - '@database'
+
   simple_sitemap.views.argument_collector:
     class: Drupal\simple_sitemap_views\EventSubscriber\ArgumentCollector
-    arguments: ['@entity_type.manager', '@simple_sitemap.views', '@current_route_match']
+    arguments:
+      - '@entity_type.manager'
+      - '@simple_sitemap.views'
+      - '@current_route_match'
     tags:
       - {name: 'event_subscriber'}
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Controller/SimpleSitemapViewsController.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Controller/SimpleSitemapViewsController.php
index 508e865c4e5be5e3854d57c4dcd9bce0b359a5af..9cdb0812023f11b6ed16a2cce557bae9c6844b58 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Controller/SimpleSitemapViewsController.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Controller/SimpleSitemapViewsController.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\simple_sitemap_views\Controller;
 
-use Drupal\simple_sitemap\Form\FormHelper;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\simple_sitemap_views\SimpleSitemapViews;
 use Drupal\Core\Controller\ControllerBase;
@@ -33,7 +32,7 @@ public function __construct(SimpleSitemapViews $sitemap_views) {
   /**
    * {@inheritdoc}
    */
-  public static function create(ContainerInterface $container) {
+  public static function create(ContainerInterface $container): SimpleSitemapViewsController {
     return new static(
       $container->get('simple_sitemap.views')
     );
@@ -45,24 +44,33 @@ public static function create(ContainerInterface $container) {
    * @return array
    *   A render array.
    */
-  public function content() {
+  public function content(): array {
+    $table = &$build['simple_sitemap_views'];
+
     $table = [
       '#type' => 'table',
       '#header' => [
         $this->t('View'),
         $this->t('Display'),
-        $this->t('Variants'),
+        $this->t('Sitemaps'),
         $this->t('Operations'),
       ],
-      '#empty' => $this->t('No view displays are set to be indexed yet. <a href="@url">Edit a view.</a>', ['@url' => $GLOBALS['base_url'] . '/admin/structure/views']),
+      '#empty' => $this->t('No view displays are set to be indexed yet. <a href="@url">Edit a view.</a>', ['@url' => Url::fromRoute('entity.view.collection')->toString()]),
     ];
 
+    if (empty($this->sitemapViews->getSitemaps())) {
+      $table['#empty'] = $this->t('Please configure at least one <a href="@sitemaps_url">sitemap</a> to be of a <a href="@types_url">type</a> that implements the views URL generator.', [
+        '@sitemaps_url' => Url::fromRoute('entity.simple_sitemap.collection')->toString(),
+        '@types_url' => Url::fromRoute('entity.simple_sitemap_type.collection')->toString(),
+      ]);
+    }
+
     foreach ($this->sitemapViews->getIndexableViews() as $index => $view) {
       $table[$index]['view'] = ['#markup' => $view->storage->label()];
       $table[$index]['display'] = ['#markup' => $view->display_handler->display['display_title']];
 
-      $variants = $this->sitemapViews->getIndexableVariants($view);
-      $variants = implode(', ', array_keys($variants));
+      $sitemaps = $this->sitemapViews->getIndexableSitemaps($view);
+      $variants = implode(', ', array_keys($sitemaps));
       $table[$index]['variants'] = ['#markup' => $variants];
 
       // Link to view display edit form.
@@ -82,14 +90,6 @@ public function content() {
       ];
     }
 
-    // Show information about indexed displays.
-    $build['simple_sitemap_views'] = [
-      '#prefix' => FormHelper::getDonationText(),
-      '#title' => $this->t('Indexed view displays'),
-      '#type' => 'fieldset',
-      'table' => $table,
-    ];
-
     return $build;
   }
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/EventSubscriber/ArgumentCollector.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/EventSubscriber/ArgumentCollector.php
index fc63255faea12541171d991a310a996531574549..7a4ef740cf867d7a03023b6c78c2195b73749ada 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/EventSubscriber/ArgumentCollector.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/EventSubscriber/ArgumentCollector.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\simple_sitemap_views\EventSubscriber;
 
+use Symfony\Component\HttpKernel\Event\TerminateEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\HttpKernel\Event\PostResponseEvent;
 use Drupal\simple_sitemap_views\SimpleSitemapViews;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Symfony\Component\HttpKernel\KernelEvents;
@@ -62,10 +62,10 @@ public static function getSubscribedEvents() {
   /**
    * Collect information about views arguments.
    *
-   * @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
+   * @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event
    *   Object of event after a response was sent.
    */
-  public function onTerminate(PostResponseEvent $event) {
+  public function onTerminate(TerminateEvent $event) {
     // Only successful requests are interesting.
     // Collect information about arguments only if views support is enabled.
     if (!$event->getResponse()->isSuccessful() || !$this->sitemapViews->isEnabled()) {
@@ -101,13 +101,14 @@ protected function getViewArgumentsFromRoute() {
 
     $args = [];
     foreach ($map as $attribute => $parameter_name) {
-      $parameter_name = isset($parameter_name) ? $parameter_name : $attribute;
+      $parameter_name = $parameter_name ?? $attribute;
       $arg = $this->routeMatch->getRawParameter($parameter_name);
 
       if ($arg !== NULL) {
         $args[] = $arg;
       }
     }
+
     return $args;
   }
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/QueueWorker/GarbageCollector.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/QueueWorker/GarbageCollector.php
index 3314ec2bbb9cb7e00c6fa8b31195bcc6bc1ff1c3..59c3f32e8289ea5585a9179dca39999954f12627 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/QueueWorker/GarbageCollector.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/QueueWorker/GarbageCollector.php
@@ -47,6 +47,9 @@ class GarbageCollector extends QueueWorkerBase implements ContainerFactoryPlugin
    *   Entity type manager.
    * @param \Drupal\simple_sitemap_views\SimpleSitemapViews $sitemap_views
    *   Views sitemap data.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, SimpleSitemapViews $sitemap_views) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
@@ -87,15 +90,15 @@ public function processItem($data) {
           continue;
         }
 
-        $variants = $this->sitemapViews->getIndexableVariants($view);
-        $variants = array_keys($variants);
+        $sitemaps = $this->sitemapViews->getIndexableSitemaps($view);
+        $sitemaps = array_keys($sitemaps);
 
         $args_ids = [];
-        foreach ($variants as $variant) {
-          $variant_args_ids = $this->sitemapViews->getIndexableArguments($view, $variant);
+        foreach ($sitemaps as $sitemap) {
+          $sitemap_args_ids = $this->sitemapViews->getIndexableArguments($view, $sitemap);
 
-          if (count($variant_args_ids) > count($args_ids)) {
-            $args_ids = $variant_args_ids;
+          if (count($sitemap_args_ids) > count($args_ids)) {
+            $args_ids = $sitemap_args_ids;
           }
         }
 
@@ -115,16 +118,17 @@ public function processItem($data) {
         $this->sitemapViews->removeArgumentsFromIndex($condition);
 
         $max_links = 0;
-        foreach ($variants as $variant) {
-          $settings = $this->sitemapViews->getSitemapSettings($view, $variant);
-          $variant_max_links = is_numeric($settings['max_links']) ? $settings['max_links'] : 0;
+        foreach ($sitemaps as $sitemap) {
+          $settings = $this->sitemapViews->getSitemapSettings($view, $sitemap);
+          $sitemap_max_links = is_numeric($settings['max_links']) ? $settings['max_links'] : 0;
 
-          if ($variant_max_links == 0) {
+          if ($sitemap_max_links == 0) {
             $max_links = 0;
             break;
           }
-          elseif ($variant_max_links > $max_links) {
-            $max_links = $variant_max_links;
+
+          if ($sitemap_max_links > $max_links) {
+            $max_links = $sitemap_max_links;
           }
         }
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/simple_sitemap/UrlGenerator/ViewsUrlGenerator.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/simple_sitemap/UrlGenerator/ViewsUrlGenerator.php
index f04a094a6bda9fd7530ce66c8a6e51acf93f6359..46fc1608d60dd884c1a6ba5c34a5cadf26ef7616 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/simple_sitemap/UrlGenerator/ViewsUrlGenerator.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/simple_sitemap/UrlGenerator/ViewsUrlGenerator.php
@@ -2,15 +2,17 @@
 
 namespace Drupal\simple_sitemap_views\Plugin\simple_sitemap\UrlGenerator;
 
+use Drupal\simple_sitemap\Exception\SkipElementException;
 use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\EntityUrlGeneratorBase;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\simple_sitemap_views\SimpleSitemapViews;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Database\Database;
-use Drupal\simple_sitemap\Simplesitemap;
-use Drupal\simple_sitemap\EntityHelper;
+use Drupal\simple_sitemap\Entity\EntityHelper;
 use Drupal\simple_sitemap\Logger;
 use Drupal\views\Views;
 use Drupal\Core\Url;
@@ -49,15 +51,15 @@ class ViewsUrlGenerator extends EntityUrlGeneratorBase {
    *   The plugin_id for the plugin instance.
    * @param mixed $plugin_definition
    *   The plugin implementation definition.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   *   The simple_sitemap.generator service.
    * @param \Drupal\simple_sitemap\Logger $logger
    *   The simple_sitemap.logger service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
-   * @param \Drupal\simple_sitemap\EntityHelper $entity_helper
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
    *   The simple_sitemap.entity_helper service.
    * @param \Drupal\simple_sitemap_views\SimpleSitemapViews $sitemap_views
    *   Views sitemap data.
@@ -68,8 +70,8 @@ public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
     Logger $logger,
+    Settings $settings,
     LanguageManagerInterface $language_manager,
     EntityTypeManagerInterface $entity_type_manager,
     EntityHelper $entity_helper,
@@ -80,8 +82,8 @@ public function __construct(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $generator,
       $logger,
+      $settings,
       $language_manager,
       $entity_type_manager,
       $entity_helper
@@ -93,13 +95,13 @@ public function __construct(
   /**
    * {@inheritdoc}
    */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
       $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings'),
       $container->get('language_manager'),
       $container->get('entity_type.manager'),
       $container->get('simple_sitemap.entity_helper'),
@@ -111,12 +113,12 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function getDataSets() {
+  public function getDataSets(): array {
     $data_sets = [];
 
     // Get data sets.
     foreach ($this->sitemapViews->getIndexableViews() as $view) {
-      $settings = $this->sitemapViews->getSitemapSettings($view, $this->sitemapVariant);
+      $settings = $this->sitemapViews->getSitemapSettings($view, $this->sitemap->id());
 
       if (empty($settings)) {
         $view->destroy();
@@ -136,7 +138,7 @@ public function getDataSets() {
       }
 
       // Process indexed arguments.
-      if ($args_ids = $this->sitemapViews->getIndexableArguments($view, $this->sitemapVariant)) {
+      if ($args_ids = $this->sitemapViews->getIndexableArguments($view, $this->sitemap->id())) {
         $args_ids = $this->sitemapViews->getArgumentsStringVariations($args_ids);
 
         // Form the condition according to the variants of the
@@ -162,13 +164,14 @@ public function getDataSets() {
       // Destroy a view instance.
       $view->destroy();
     }
+
     return $data_sets;
   }
 
   /**
    * {@inheritdoc}
    */
-  protected function processDataSet($data_set) {
+  protected function processDataSet($data_set): array {
     // Get information from data set.
     $view_id = $data_set['view_id'];
     $display_id = $data_set['display_id'];
@@ -177,7 +180,7 @@ protected function processDataSet($data_set) {
     try {
       // Trying to get an instance of the view.
       $view = Views::getView($view_id);
-      if (empty($view)) {
+      if ($view === NULL) {
         throw new \UnexpectedValueException('Failed to get an instance of the view.');
       }
 
@@ -188,7 +191,7 @@ protected function processDataSet($data_set) {
       }
 
       // Trying to get the sitemap settings.
-      $settings = $this->sitemapViews->getSitemapSettings($view, $this->sitemapVariant);
+      $settings = $this->sitemapViews->getSitemapSettings($view, $this->sitemap->id());
       if (empty($settings)) {
         throw new \UnexpectedValueException('Failed to get the sitemap settings.');
       }
@@ -221,13 +224,13 @@ protected function processDataSet($data_set) {
         $condition->condition('id', $data_set['index_id']);
         $this->sitemapViews->removeArgumentsFromIndex($condition);
       }
-      return FALSE;
+      throw new SkipElementException($e->getMessage());
     }
 
     return [
       'url' => $url,
       'lastmod' => NULL,
-      'priority' => isset($settings['priority']) ? $settings['priority'] : NULL,
+      'priority' => $settings['priority'] ?? NULL,
       'changefreq' => !empty($settings['changefreq']) ? $settings['changefreq'] : NULL,
       'images' => [],
       // Additional info useful in hooks.
@@ -253,11 +256,11 @@ protected function processDataSet($data_set) {
    * @throws \UnexpectedValueException.
    *   If this is a URI with no corresponding route.
    */
-  protected function cleanRouteParameters(Url $url, array $args) {
+  protected function cleanRouteParameters(Url $url, array $args): void {
     $parameters = $url->getRouteParameters();
 
     // Check that the number of params does not match the number of arguments.
-    if (count($parameters) != count($args)) {
+    if (count($parameters) !== count($args)) {
       $route_name = $url->getRouteName();
       $route = $this->routeProvider->getRouteByName($route_name);
       $variables = $route->compile()->getVariables();
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/views/display_extender/SimpleSitemapDisplayExtender.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/views/display_extender/SimpleSitemapDisplayExtender.php
index ff735503d565f740b8d5c88c8a1223ac5dab7017..586e226c83348827c346a9da105e3d8d34ec8b08 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/views/display_extender/SimpleSitemapDisplayExtender.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/Plugin/views/display_extender/SimpleSitemapDisplayExtender.php
@@ -6,7 +6,7 @@
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\views\Plugin\views\display\DisplayRouterInterface;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
-use Drupal\simple_sitemap\SimplesitemapManager;
+use Drupal\simple_sitemap_views\SimpleSitemapViews;
 use Drupal\simple_sitemap\Form\FormHelper;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\ViewExecutable;
@@ -26,25 +26,18 @@
 class SimpleSitemapDisplayExtender extends DisplayExtenderPluginBase {
 
   /**
-   * Simple XML Sitemap form helper.
+   * Helper class for working with forms.
    *
    * @var \Drupal\simple_sitemap\Form\FormHelper
    */
   protected $formHelper;
 
   /**
-   * Simple XML Sitemap manager.
+   * The sitemaps.
    *
-   * @var \Drupal\simple_sitemap\SimplesitemapManager
+   * @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface[]
    */
-  protected $sitemapManager;
-
-  /**
-   * The sitemap variants.
-   *
-   * @var array
-   */
-  protected $variants = [];
+  protected $sitemaps = [];
 
   /**
    * Constructs the plugin.
@@ -56,15 +49,14 @@ class SimpleSitemapDisplayExtender extends DisplayExtenderPluginBase {
    * @param mixed $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
-   *   Simple XML Sitemap form helper.
-   * @param \Drupal\simple_sitemap\SimplesitemapManager $sitemap_manager
-   *   Simple XML Sitemap manager.
+   *   Helper class for working with forms.
+   * @param \Drupal\simple_sitemap_views\SimpleSitemapViews $sitemap_views
+   *   Views sitemap data.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, FormHelper $form_helper, SimplesitemapManager $sitemap_manager) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, FormHelper $form_helper, SimpleSitemapViews $sitemap_views) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->formHelper = $form_helper;
-    $this->sitemapManager = $sitemap_manager;
-    $this->variants = $sitemap_manager->getSitemapVariants(NULL, FALSE);
+    $this->sitemaps = $sitemap_views->getSitemaps();
   }
 
   /**
@@ -76,14 +68,14 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_id,
       $plugin_definition,
       $container->get('simple_sitemap.form_helper'),
-      $container->get('simple_sitemap.manager')
+      $container->get('simple_sitemap.views')
     );
   }
 
   /**
    * {@inheritdoc}
    */
-  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
+  public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
     parent::init($view, $display, $options);
     if (!$this->hasSitemapSettings()) {
       $this->options = [];
@@ -112,49 +104,25 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
         '#tree' => TRUE,
       ];
 
-      foreach ($this->variants as $variant => $definition) {
+      foreach ($this->sitemaps as $variant => $sitemap) {
         $settings = $this->getSitemapSettings($variant);
         $variant_form = &$form['variants'][$variant];
 
         $variant_form = [
           '#type' => 'details',
-          '#title' => '<em>' . $definition['label'] . '</em>',
+          '#title' => '<em>' . $sitemap->label() . '</em>',
           '#open' => (bool) $settings['index'],
         ];
 
-        $variant_form['index'] = [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Index this display in variant <em>@variant_label</em>', [
-            '@variant_label' => $definition['label'],
-          ]),
-          '#default_value' => $settings['index'],
-        ];
-
-        $states = [
-          'visible' => [
-            ':input[name="variants[' . $variant . '][index]"]' => ['checked' => TRUE],
-          ],
-        ];
+        $variant_form = $this->formHelper
+          ->settingsForm($variant_form, $settings);
 
-        // The sitemap priority.
-        $variant_form['priority'] = [
-          '#type' => 'select',
-          '#title' => $this->t('Priority'),
-          '#description' => $this->t('The priority this display will have in the eyes of search engine bots.'),
-          '#default_value' => $settings['priority'],
-          '#options' => $this->formHelper->getPrioritySelectValues(),
-          '#states' => $states,
-        ];
+        $variant_form['index']['#title'] = $this->t('Index this display in sitemap <em>@variant_label</em>', ['@variant_label' => $sitemap->label()]);
+        $variant_form['priority']['#description'] = $this->t('The priority this display will have in the eyes of search engine bots.');
+        $variant_form['changefreq']['#description'] = $this->t('The frequency with which this display changes. Search engine bots may take this as an indication of how often to index it.');
 
-        // The sitemap change frequency.
-        $variant_form['changefreq'] = [
-          '#type' => 'select',
-          '#title' => $this->t('Change frequency'),
-          '#description' => $this->t('The frequency with which this display changes. Search engine bots may take this as an indication of how often to index it.'),
-          '#default_value' => $settings['changefreq'],
-          '#options' => $this->formHelper->getChangefreqSelectValues(),
-          '#states' => $states,
-        ];
+        // Images are not supported.
+        unset($variant_form['include_images']);
 
         // Arguments to index.
         $variant_form['arguments'] = [
@@ -164,7 +132,6 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
           '#default_value' => $settings['arguments'],
           '#attributes' => ['class' => ['indexed-arguments']],
           '#access' => !empty($arguments_options),
-          '#states' => $states,
         ];
 
         // Required arguments are always indexed.
@@ -180,7 +147,6 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
           '#default_value' => $settings['max_links'],
           '#min' => 1,
           '#access' => !empty($arguments_options) || $has_required_arguments,
-          '#states' => $states,
         ];
       }
 
@@ -193,11 +159,12 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
-    if ($this->hasSitemapSettings() && $form_state->get('section') == 'simple_sitemap') {
+    if ($this->hasSitemapSettings() && $form_state->get('section') === 'simple_sitemap') {
       $required_arguments = $this->getRequiredArguments();
 
-      foreach (array_keys($this->variants) as $variant) {
-        $arguments = &$form_state->getValue(['variants', $variant, 'arguments'], []);
+      foreach ($this->sitemaps as $variant => $sitemap) {
+        $key = ['variants', $variant, 'arguments'];
+        $arguments = &$form_state->getValue($key, []);
         $arguments = array_merge($arguments, $required_arguments);
         $errors = $this->validateIndexedArguments($arguments);
 
@@ -212,12 +179,12 @@ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
-    if ($this->hasSitemapSettings() && $form_state->get('section') == 'simple_sitemap') {
+    if ($this->hasSitemapSettings() && $form_state->get('section') === 'simple_sitemap') {
       $variants = $form_state->getValue('variants');
       $this->options['variants'] = [];
 
-      // Save settings for each variant.
-      foreach (array_keys($this->variants) as $variant) {
+      // Save settings for each sitemap.
+      foreach ($this->sitemaps as $variant => $sitemap) {
         $settings = $variants[$variant] + $this->getSitemapSettings($variant);
 
         if ($settings['index']) {
@@ -231,13 +198,13 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
   /**
    * {@inheritdoc}
    */
-  public function validate() {
+  public function validate(): array {
     $errors = [parent::validate()];
 
     // Validate the argument options relative to the
     // current state of the view argument handlers.
     if ($this->hasSitemapSettings()) {
-      foreach (array_keys($this->variants) as $variant) {
+      foreach ($this->sitemaps as $variant => $sitemap) {
         $settings = $this->getSitemapSettings($variant);
         $errors[] = $this->validateIndexedArguments($settings['arguments']);
       }
@@ -257,7 +224,7 @@ public function optionsSummary(&$categories, &$options) {
       ];
 
       $included_variants = [];
-      foreach (array_keys($this->variants) as $variant) {
+      foreach ($this->sitemaps as $variant => $sitemap) {
         $settings = $this->getSitemapSettings($variant);
 
         if ($settings['index']) {
@@ -268,9 +235,9 @@ public function optionsSummary(&$categories, &$options) {
       $options['simple_sitemap'] = [
         'title' => NULL,
         'category' => 'simple_sitemap',
-        'value' => $included_variants ? $this->t('Included in sitemap variants: @variants', [
+        'value' => $included_variants ? $this->t('Included in sitemaps: @variants', [
           '@variants' => implode(', ', $included_variants),
-        ]) : $this->t('Excluded from all sitemap variants'),
+        ]) : $this->t('Excluded from all sitemaps'),
       ];
     }
   }
@@ -279,12 +246,12 @@ public function optionsSummary(&$categories, &$options) {
    * Gets the sitemap settings.
    *
    * @param string $variant
-   *   The name of the sitemap variant.
+   *   The ID of the sitemap.
    *
    * @return array
    *   The sitemap settings.
    */
-  public function getSitemapSettings($variant) {
+  public function getSitemapSettings(string $variant): array {
     $settings = [
       'index' => 0,
       'priority' => 0.5,
@@ -304,6 +271,7 @@ public function getSitemapSettings($variant) {
       $required_arguments = $this->getRequiredArguments();
       $settings['arguments'] = array_merge($settings['arguments'], $required_arguments);
     }
+
     return $settings;
   }
 
@@ -313,8 +281,8 @@ public function getSitemapSettings($variant) {
    * @return bool
    *   Has sitemap settings (TRUE) or not (FALSE).
    */
-  public function hasSitemapSettings() {
-    return $this->displayHandler instanceof DisplayRouterInterface;
+  public function hasSitemapSettings(): bool {
+    return $this->displayHandler instanceof DisplayRouterInterface && !empty($this->sitemaps);
   }
 
   /**
@@ -323,7 +291,7 @@ public function hasSitemapSettings() {
    * @return array
    *   View arguments IDs.
    */
-  public function getRequiredArguments() {
+  public function getRequiredArguments(): array {
     $arguments = $this->displayHandler->getHandlers('argument');
 
     if (!empty($arguments)) {
@@ -331,7 +299,7 @@ public function getRequiredArguments() {
       $arg_counter = 0;
 
       foreach ($bits as $bit) {
-        if ($bit == '%' || strpos($bit, '%') === 0) {
+        if ($bit === '%' || strpos($bit, '%') === 0) {
           $arg_counter++;
         }
       }
@@ -341,6 +309,7 @@ public function getRequiredArguments() {
         return array_combine($arguments, $arguments);
       }
     }
+
     return [];
   }
 
@@ -350,14 +319,15 @@ public function getRequiredArguments() {
    * @return bool
    *   TRUE if the path contains required arguments, FALSE if not.
    */
-  public function hasRequiredArguments() {
+  public function hasRequiredArguments(): bool {
     $bits = explode('/', $this->displayHandler->getPath());
 
     foreach ($bits as $bit) {
-      if ($bit == '%' || strpos($bit, '%') === 0) {
+      if ($bit === '%' || strpos($bit, '%') === 0) {
         return TRUE;
       }
     }
+
     return FALSE;
   }
 
@@ -367,7 +337,7 @@ public function hasRequiredArguments() {
    * @return array
    *   View arguments labels keyed by argument ID.
    */
-  protected function getArgumentsOptions() {
+  protected function getArgumentsOptions(): array {
     $arguments = $this->displayHandler->getHandlers('argument');
     $arguments_options = [];
 
@@ -375,6 +345,7 @@ protected function getArgumentsOptions() {
     foreach ($arguments as $id => $argument) {
       $arguments_options[$id] = $argument->adminLabel();
     }
+
     return $arguments_options;
   }
 
@@ -388,7 +359,7 @@ protected function getArgumentsOptions() {
    *   An array of error strings. This will be empty if there are no validation
    *   errors.
    */
-  protected function validateIndexedArguments(array $indexed_arguments) {
+  protected function validateIndexedArguments(array $indexed_arguments): array {
     $arguments = $this->displayHandler->getHandlers('argument');
     $arguments = array_fill_keys(array_keys($arguments), 0);
     $arguments = array_merge($arguments, $indexed_arguments);
@@ -402,6 +373,7 @@ protected function validateIndexedArguments(array $indexed_arguments) {
         break;
       }
     }
+
     return $errors;
   }
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/SimpleSitemapViews.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/SimpleSitemapViews.php
index 4f5839302ffdef4c4000ceaaadf241222a2180f8..efd028decb90b9cda1df29e8dff4170ce8c8a3e7 100755
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/src/SimpleSitemapViews.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/src/SimpleSitemapViews.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\simple_sitemap_views;
 
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
 use Drupal\simple_sitemap_views\Plugin\views\display_extender\SimpleSitemapDisplayExtender;
-use Drupal\simple_sitemap\SimplesitemapManager;
 use Drupal\Core\Database\Query\ConditionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -22,19 +22,12 @@ class SimpleSitemapViews {
   /**
    * Separator between arguments.
    */
-  const ARGUMENT_SEPARATOR = '/';
+  public const ARGUMENT_SEPARATOR = '/';
 
   /**
    * Views display extender plugin ID.
    */
-  const PLUGIN_ID = 'simple_sitemap_display_extender';
-
-  /**
-   * Simple XML Sitemap manager.
-   *
-   * @var \Drupal\simple_sitemap\SimplesitemapManager
-   */
-  protected $sitemapManager;
+  protected const PLUGIN_ID = 'simple_sitemap_display_extender';
 
   /**
    * View entities storage.
@@ -67,8 +60,6 @@ class SimpleSitemapViews {
   /**
    * SimpleSitemapViews constructor.
    *
-   * @param \Drupal\simple_sitemap\SimplesitemapManager $sitemap_manager
-   *   Simple XML Sitemap manager.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
@@ -77,15 +68,16 @@ class SimpleSitemapViews {
    *   The queue factory.
    * @param \Drupal\Core\Database\Connection $database
    *   The current active database's master connection.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
   public function __construct(
-    SimplesitemapManager $sitemap_manager,
     EntityTypeManagerInterface $entity_type_manager,
     ConfigFactoryInterface $config_factory,
     QueueFactory $queue_factory,
     Connection $database
   ) {
-    $this->sitemapManager = $sitemap_manager;
     $this->viewStorage = $entity_type_manager->getStorage('view');
     $this->configFactory = $config_factory;
     $this->queueFactory = $queue_factory;
@@ -98,7 +90,7 @@ public function __construct(
    * @return bool
    *   Returns TRUE if support is enabled, and FALSE otherwise.
    */
-  public function isEnabled() {
+  public function isEnabled(): bool {
     // Support enabled when views display extender is enabled.
     $enabled = Views::getEnabledDisplayExtenders();
     return isset($enabled[self::PLUGIN_ID]);
@@ -107,7 +99,7 @@ public function isEnabled() {
   /**
    * Enables sitemap support for views.
    */
-  public function enable() {
+  public function enable(): void {
     $config = $this->configFactory->getEditable('views.settings');
     $display_extenders = $config->get('display_extenders') ?: [];
 
@@ -120,7 +112,7 @@ public function enable() {
   /**
    * Disables sitemap support for views.
    */
-  public function disable() {
+  public function disable(): void {
     $config = $this->configFactory->getEditable('views.settings');
     $display_extenders = $config->get('display_extenders') ?: [];
 
@@ -147,18 +139,19 @@ public function disable() {
    * @return \Drupal\simple_sitemap_views\Plugin\views\display_extender\SimpleSitemapDisplayExtender|null
    *   The display extender.
    */
-  public function getDisplayExtender(ViewExecutable $view, $display_id = NULL) {
+  public function getDisplayExtender(ViewExecutable $view, ?string $display_id = NULL): ?SimpleSitemapDisplayExtender {
     // Ensure the display was correctly set.
     if (!$view->setDisplay($display_id)) {
       return NULL;
     }
 
     $extenders = $view->display_handler->getExtenders();
-    $extender = isset($extenders[self::PLUGIN_ID]) ? $extenders[self::PLUGIN_ID] : NULL;
+    $extender = $extenders[self::PLUGIN_ID] ?? NULL;
 
     if ($extender instanceof SimpleSitemapDisplayExtender) {
       return $extender;
     }
+
     return NULL;
   }
 
@@ -168,14 +161,14 @@ public function getDisplayExtender(ViewExecutable $view, $display_id = NULL) {
    * @param \Drupal\views\ViewExecutable $view
    *   A view executable instance.
    * @param string $variant
-   *   The name of the sitemap variant.
+   *   The ID of the sitemap.
    * @param string|null $display_id
    *   The display id. If empty uses the current display.
    *
    * @return array|null
    *   The sitemap settings if the display is indexed, NULL otherwise.
    */
-  public function getSitemapSettings(ViewExecutable $view, $variant, $display_id = NULL) {
+  public function getSitemapSettings(ViewExecutable $view, string $variant, ?string $display_id = NULL): ?array {
     $extender = $this->getDisplayExtender($view, $display_id);
 
     // Retrieve the sitemap settings from the extender.
@@ -186,6 +179,7 @@ public function getSitemapSettings(ViewExecutable $view, $variant, $display_id =
         return $settings;
       }
     }
+
     return NULL;
   }
 
@@ -195,14 +189,14 @@ public function getSitemapSettings(ViewExecutable $view, $variant, $display_id =
    * @param \Drupal\views\ViewExecutable $view
    *   A view executable instance.
    * @param string $variant
-   *   The name of the sitemap variant.
+   *   The ID of the sitemap.
    * @param string|null $display_id
    *   The display id. If empty uses the current display.
    *
    * @return array
    *   Indexable arguments identifiers.
    */
-  public function getIndexableArguments(ViewExecutable $view, $variant, $display_id = NULL) {
+  public function getIndexableArguments(ViewExecutable $view, string $variant, ?string $display_id = NULL): array {
     $settings = $this->getSitemapSettings($view, $variant, $display_id);
     $indexable_arguments = [];
 
@@ -215,7 +209,7 @@ public function getIndexableArguments(ViewExecutable $view, $variant, $display_i
       // Required arguments.
       foreach ($bits as $bit) {
         if ($bit == '%' || strpos($bit, '%') === 0) {
-          $indexable_arguments[] = isset($arguments[$arg_index]) ? $arguments[$arg_index] : $bit;
+          $indexable_arguments[] = $arguments[$arg_index] ?? $bit;
           $arg_index++;
         }
       }
@@ -234,6 +228,7 @@ public function getIndexableArguments(ViewExecutable $view, $variant, $display_i
         }
       }
     }
+
     return $indexable_arguments;
   }
 
@@ -249,17 +244,16 @@ public function getIndexableArguments(ViewExecutable $view, $variant, $display_i
    *
    * @return bool
    *   TRUE if the arguments are added to the index, FALSE otherwise.
+   *
+   * @throws \Exception
    */
-  public function addArgumentsToIndex(ViewExecutable $view, array $args, $display_id = NULL) {
-    $variants = $this->sitemapManager->getSitemapVariants(NULL, FALSE);
-
-    foreach (array_keys($variants) as $variant) {
-      $result = $this->addArgumentsToIndexByVariant($view, $variant, $args, $display_id);
-
-      if ($result) {
-        return $result;
+  public function addArgumentsToIndex(ViewExecutable $view, array $args, ?string $display_id = NULL): bool {
+    foreach ($this->getSitemaps() as $sitemap) {
+      if ($this->addArgumentsToIndexByVariant($view, $sitemap->id(), $args, $display_id)) {
+        return TRUE;
       }
     }
+
     return FALSE;
   }
 
@@ -269,7 +263,7 @@ public function addArgumentsToIndex(ViewExecutable $view, array $args, $display_
    * @param \Drupal\views\ViewExecutable $view
    *   A view executable instance.
    * @param string $variant
-   *   The name of the sitemap variant.
+   *   The ID of the sitemap.
    * @param array $args
    *   Array of arguments to add to the index.
    * @param string|null $display_id
@@ -277,8 +271,10 @@ public function addArgumentsToIndex(ViewExecutable $view, array $args, $display_
    *
    * @return bool
    *   TRUE if the arguments are added to the index, FALSE otherwise.
+   *
+   * @throws \Exception
    */
-  public function addArgumentsToIndexByVariant(ViewExecutable $view, $variant, array $args, $display_id = NULL) {
+  public function addArgumentsToIndexByVariant(ViewExecutable $view, string $variant, array $args, ?string $display_id = NULL): bool {
     // An array of arguments to be added to the index can not be empty.
     // Also ensure the display was correctly set.
     if (empty($args) || !$view->setDisplay($display_id)) {
@@ -293,7 +289,7 @@ public function addArgumentsToIndexByVariant(ViewExecutable $view, $variant, arr
 
     // Check that the number of identifiers is equal to the number of values.
     $args_ids = array_slice($indexable_arguments, 0, count($args));
-    if (count($args_ids) != count($args)) {
+    if (count($args_ids) !== count($args)) {
       return FALSE;
     }
 
@@ -326,14 +322,14 @@ public function addArgumentsToIndexByVariant(ViewExecutable $view, $variant, arr
     }
 
     // Add a set of arguments to the index.
-    $options = ['return' => Database::RETURN_AFFECTED];
-    $query = $this->database->insert('simple_sitemap_views', $options);
+    $query = $this->database->insert('simple_sitemap_views');
     $query->fields([
       'view_id' => $view->id(),
       'display_id' => $view->current_display,
       'arguments_ids' => $args_ids,
       'arguments_values' => $args_values,
     ]);
+
     return (bool) $query->execute();
   }
 
@@ -352,17 +348,17 @@ public function addArgumentsToIndexByVariant(ViewExecutable $view, $variant, arr
    * @return array
    *   An array with information about the indexed arguments.
    */
-  public function getArgumentsFromIndex(ConditionInterface $condition = NULL, $limit = NULL, $convert = FALSE) {
+  public function getArgumentsFromIndex(?ConditionInterface $condition = NULL, ?int $limit = NULL, bool $convert = FALSE): array {
     $query = $this->database->select('simple_sitemap_views', 'ssv');
     $query->addField('ssv', 'id');
     $query->addField('ssv', 'view_id');
     $query->addField('ssv', 'display_id');
     $query->addField('ssv', 'arguments_values', 'arguments');
 
-    if (!empty($condition)) {
+    if ($condition !== NULL) {
       $query->condition($condition);
     }
-    if (!empty($limit)) {
+    if ($limit !== NULL) {
       $query->range(0, $limit);
     }
 
@@ -376,6 +372,7 @@ public function getArgumentsFromIndex(ConditionInterface $condition = NULL, $lim
         'arguments' => $convert ? $this->convertArgumentsStringToArray($row->arguments) : $row->arguments,
       ];
     }
+
     return $arguments;
   }
 
@@ -388,12 +385,13 @@ public function getArgumentsFromIndex(ConditionInterface $condition = NULL, $lim
    * @return int
    *   The number of rows.
    */
-  public function getArgumentsFromIndexCount(ConditionInterface $condition = NULL) {
+  public function getArgumentsFromIndexCount(?ConditionInterface $condition = NULL): int {
     $query = $this->database->select('simple_sitemap_views', 'ssv');
 
-    if (!empty($condition)) {
+    if ($condition !== NULL) {
       $query->condition($condition);
     }
+
     return $query->countQuery()->execute()->fetchField();
   }
 
@@ -408,16 +406,17 @@ public function getArgumentsFromIndexCount(ConditionInterface $condition = NULL)
    * @return int|bool
    *   The ID of the record, or FALSE if there is no specified position.
    */
-  public function getIndexIdByPosition($position, ConditionInterface $condition = NULL) {
+  public function getIndexIdByPosition(int $position, ?ConditionInterface $condition = NULL) {
     $query = $this->database->select('simple_sitemap_views', 'ssv');
     $query->addField('ssv', 'id');
 
-    if (!empty($condition)) {
+    if ($condition !== NULL) {
       $query->condition($condition);
     }
 
-    $query->orderBy('id', 'ASC');
+    $query->orderBy('id');
     $query->range($position - 1, 1);
+
     return $query->execute()->fetchField();
   }
 
@@ -427,8 +426,8 @@ public function getIndexIdByPosition($position, ConditionInterface $condition =
    * @param \Drupal\Core\Database\Query\ConditionInterface|null $condition
    *   The query conditions.
    */
-  public function removeArgumentsFromIndex(ConditionInterface $condition = NULL) {
-    if (empty($condition)) {
+  public function removeArgumentsFromIndex(?ConditionInterface $condition = NULL): void {
+    if ($condition === NULL) {
       // If there are no conditions, use the TRUNCATE query.
       $query = $this->database->truncate('simple_sitemap_views');
     }
@@ -449,15 +448,14 @@ public function removeArgumentsFromIndex(ConditionInterface $condition = NULL) {
    * @return array
    *   Array of display identifiers.
    */
-  public function getRouterDisplayIds(ViewEntityInterface $view_entity) {
+  public function getRouterDisplayIds(ViewEntityInterface $view_entity): array {
     $display_plugins = $this->getRouterDisplayPluginIds();
 
     $filter_callback = function (array $display) use ($display_plugins) {
-      return !empty($display['display_plugin']) && in_array($display['display_plugin'], $display_plugins);
+      return !empty($display['display_plugin']) && in_array($display['display_plugin'], $display_plugins, TRUE);
     };
 
-    $displays = array_filter($view_entity->get('display'), $filter_callback);
-    return array_keys($displays);
+    return array_keys(array_filter($view_entity->get('display'), $filter_callback));
   }
 
   /**
@@ -466,7 +464,7 @@ public function getRouterDisplayIds(ViewEntityInterface $view_entity) {
    * @return \Drupal\views\ViewExecutable[]
    *   An array of ViewExecutable instances.
    */
-  public function getIndexableViews() {
+  public function getIndexableViews(): array {
     // Check that views support is enabled.
     if (!$this->isEnabled()) {
       return [];
@@ -497,38 +495,59 @@ public function getIndexableViews() {
         }
 
         // Check that the display is enabled and indexed.
-        if ($view->display_handler->isEnabled() && $this->getIndexableVariants($view)) {
+        if ($view->display_handler->isEnabled() && $this->getIndexableSitemaps($view)) {
           $indexable_views[] = $view;
         }
       }
     }
+
     return $indexable_views;
   }
 
   /**
-   * Returns an array of indexable sitemap variants for view display.
+   * Returns an array of indexable sitemaps for view display.
    *
    * @param \Drupal\views\ViewExecutable $view
    *   A view executable instance.
    * @param string|null $display_id
    *   The display id. If empty uses the current display.
    *
-   * @return array
-   *   An array of sitemap variants.
+   * @return \Drupal\simple_sitemap\Entity\SimpleSitemapInterface[]
+   *   An array of sitemap entities.
    */
-  public function getIndexableVariants(ViewExecutable $view, $display_id = NULL) {
+  public function getIndexableSitemaps(ViewExecutable $view, ?string $display_id = NULL): array {
     // Ensure the display was correctly set.
     if (!$view->setDisplay($display_id)) {
       return [];
     }
 
-    $variants = $this->sitemapManager->getSitemapVariants(NULL, FALSE);
-    foreach (array_keys($variants) as $variant) {
+    $sitemaps = $this->getSitemaps();
+    foreach ($sitemaps as $variant => $sitemap) {
       if (!$this->getSitemapSettings($view, $variant)) {
-        unset($variants[$variant]);
+        unset($sitemaps[$variant]);
       }
     }
-    return $variants;
+
+    return $sitemaps;
+  }
+
+  /**
+   * Returns an array of correctly configured sitemaps.
+   *
+   * @return \Drupal\simple_sitemap\Entity\SimpleSitemapInterface[]
+   *   An array of sitemap entities.
+   */
+  public function getSitemaps(): array {
+    $sitemaps = SimpleSitemap::loadMultiple();
+
+    /** @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap */
+    foreach ($sitemaps as $variant => $sitemap) {
+      if (!$sitemap->getType()->hasUrlGenerator('views')) {
+        unset($sitemaps[$variant]);
+      }
+    }
+
+    return $sitemaps;
   }
 
   /**
@@ -564,13 +583,14 @@ public function executeGarbageCollection() {
    * @return array
    *   Array of variations of the string representation of arguments.
    */
-  public function getArgumentsStringVariations(array $args) {
+  public function getArgumentsStringVariations(array $args): array {
     $variations = [];
 
     for ($length = 1; $length <= count($args); $length++) {
       $args_slice = array_slice($args, 0, $length);
       $variations[] = $this->convertArgumentsArrayToString($args_slice);
     }
+
     return $variations;
   }
 
@@ -583,7 +603,7 @@ public function getArgumentsStringVariations(array $args) {
    * @return string
    *   A string representation of the arguments.
    */
-  protected function convertArgumentsArrayToString(array $args) {
+  protected function convertArgumentsArrayToString(array $args): string {
     return implode(self::ARGUMENT_SEPARATOR, $args);
   }
 
@@ -596,7 +616,7 @@ protected function convertArgumentsArrayToString(array $args) {
    * @return array
    *   Array of arguments.
    */
-  protected function convertArgumentsStringToArray($args) {
+  protected function convertArgumentsStringToArray($args): array {
     return explode(self::ARGUMENT_SEPARATOR, $args);
   }
 
@@ -606,7 +626,7 @@ protected function convertArgumentsStringToArray($args) {
    * @return array
    *   An array with plugin identifiers.
    */
-  protected function getRouterDisplayPluginIds() {
+  protected function getRouterDisplayPluginIds(): array {
     static $plugin_ids = [];
 
     if (empty($plugin_ids)) {
@@ -619,6 +639,7 @@ protected function getRouterDisplayPluginIds() {
         }
       }
     }
+
     return $plugin_ids;
   }
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/modules/simple_sitemap_views_test/simple_sitemap_views_test.info.yml b/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/modules/simple_sitemap_views_test/simple_sitemap_views_test.info.yml
index 56cb4490c7a88a585253c447e0f041f6aaa73c5f..837cd2a3e45d359c6bef662b167a6113072af61d 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/modules/simple_sitemap_views_test/simple_sitemap_views_test.info.yml
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/modules/simple_sitemap_views_test/simple_sitemap_views_test.info.yml
@@ -2,12 +2,10 @@ name: 'Simple XML Sitemap (Views) Test'
 type: module
 description: 'Test module for Simple XML Sitemap (Views).'
 package: Testing
-core: 8.x
-core_version_requirement: ^8 || ^9
 dependencies:
   - simple_sitemap:simple_sitemap_views
 
-# Information added by Drupal.org packaging script on 2021-10-16
-version: '8.x-3.11'
+# Information added by Drupal.org packaging script on 2023-06-09
+version: '4.1.6'
 project: 'simple_sitemap'
-datestamp: 1634343987
+datestamp: 1686288645
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTest.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTest.php
index bc2e27c6b185da881afd8446d113fbfcab5016ee..b06048bc222aec17e21e42e2ff0487eb69ae277f 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTest.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\simple_sitemap_views\Functional;
 
+use Drupal\simple_sitemap\Entity\SimpleSitemapType;
+
 /**
  * Tests Simple XML Sitemap (Views) functional integration.
  *
@@ -32,7 +34,7 @@ public function testIndexableViews() {
     $this->assertNotEmpty($indexable_views);
 
     $test_view_exists = FALSE;
-    foreach ($indexable_views as &$view) {
+    foreach ($indexable_views as $view) {
       if ($view->id() == $this->testView->id() && $view->current_display == $this->testView->current_display) {
         $test_view_exists = TRUE;
         break;
@@ -92,14 +94,13 @@ public function testAddArgumentsToIndex() {
    * Tests the process of generating view display URLs.
    */
   public function testViewsUrlGenerator() {
-    $sitemap_types = $this->generator->getSitemapManager()->getSitemapTypes();
-    $this->assertContains('views', $sitemap_types['default_hreflang']['urlGenerators']);
+    $this->assertArrayHasKey('views', SimpleSitemapType::load('default_hreflang')->getUrlGenerators());
 
     $title = $this->node->getTitle();
     $this->sitemapViews->addArgumentsToIndex($this->testView, ['page']);
     $this->sitemapViews->addArgumentsToIndex($this->testView, ['page', $title]);
     $this->sitemapViews->addArgumentsToIndex($this->testView2, ['page', 1]);
-    $this->generator->generateSitemap('backend');
+    $this->generator->generate('backend');
 
     $url1 = $this->testView->getUrl()->toString();
     $url2 = $this->testView->getUrl(['page', NULL, NULL])->toString();
@@ -167,7 +168,7 @@ public function testGarbageCollector() {
     $this->assertIndexSize(2);
 
     // Records about pages with empty result must be removed during generation.
-    $this->generator->generateSitemap('backend');
+    $this->generator->generate('backend');
     $this->assertIndexSize(0);
   }
 
diff --git a/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTestBase.php b/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTestBase.php
index 9bfb92308ed58d4d2cbaa947ea23a0d4f0e60c4f..c29cabf0ba65d4232b3e1598fda0c6e3ed923113 100644
--- a/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTestBase.php
+++ b/web/modules/simple_sitemap/modules/simple_sitemap_views/tests/src/Functional/SimpleSitemapViewsTestBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\simple_sitemap_views\Functional;
 
+use Drupal\simple_sitemap\Entity\SimpleSitemapType;
 use Drupal\Tests\simple_sitemap\Functional\SimplesitemapTestBase;
 use Drupal\simple_sitemap_views\SimpleSitemapViews;
 use Drupal\views\Views;
@@ -57,13 +58,16 @@ abstract class SimpleSitemapViewsTestBase extends SimplesitemapTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $this->sitemapViews = $this->container->get('simple_sitemap.views');
     $this->cron = $this->container->get('cron');
     $this->sitemapVariant = 'default';
 
+    $sitemap_type = SimpleSitemapType::load('default_hreflang');
+    $sitemap_type->set('url_generators', array_merge($sitemap_type->get('url_generators'), ['views']))->save();
+
     $this->testView = Views::getView('simple_sitemap_views_test_view');
     $this->testView->setDisplay('page_1');
 
@@ -92,6 +96,8 @@ protected function assertIndexSize($size) {
    *   A set of argument IDs.
    * @param array $args_values
    *   A set of argument values.
+   *
+   * @throws \Exception
    */
   protected function addRecordToIndex($view_id, $display_id, array $args_ids, array $args_values) {
     $args_ids = implode(SimpleSitemapViews::ARGUMENT_SEPARATOR, $args_ids);
diff --git a/web/modules/simple_sitemap/phpcs.xml b/web/modules/simple_sitemap/phpcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f576f0c4586b2b53e0eab5b0d71b3552346d5a8f
--- /dev/null
+++ b/web/modules/simple_sitemap/phpcs.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ruleset name="simple_sitemap">
+  <description>Default PHP CodeSniffer configuration for the Simple XML Sitemap module.</description>
+  <arg name="extensions" value="php,module,inc,install,yml"/>
+  <config name="drupal_core_version" value="8"/>
+  <file>.</file>
+
+  <rule ref="Drupal"/>
+  <rule ref="DrupalPractice">
+    <!-- unserialize() is already used in many places. -->
+    <exclude name="DrupalPractice.FunctionCalls.InsecureUnserialize"/>
+  </rule>
+  <!-- Update hooks may have long descriptions. -->
+  <rule ref="Drupal.Files.LineLength.TooLong">
+    <exclude-pattern>\.install</exclude-pattern>
+    <exclude-pattern>\.post_update\.php</exclude-pattern>
+  </rule>
+  <!-- Update hooks may have multiline descriptions. -->
+  <rule ref="Drupal.Commenting.DocComment.ShortSingleLine">
+    <exclude-pattern>\.install</exclude-pattern>
+    <exclude-pattern>\.post_update\.php</exclude-pattern>
+  </rule>
+</ruleset>
diff --git a/web/modules/simple_sitemap/simple_sitemap.api.php b/web/modules/simple_sitemap/simple_sitemap.api.php
index 4a8f55e4f18c4b0dc6baf77891329fce56774aa6..504ec0091511db84462677a02cac00d621464b6c 100644
--- a/web/modules/simple_sitemap/simple_sitemap.api.php
+++ b/web/modules/simple_sitemap/simple_sitemap.api.php
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ * Hooks provided by the Simple XML Sitemap module.
+ */
+
+use Drupal\simple_sitemap\Entity\SimpleSitemapInterface;
+
 /**
  * @file
  * Hooks provided by the Simple XML Sitemap module.
@@ -11,15 +18,16 @@
  */
 
 /**
- * Alter the generated link data before the sitemap is saved.
+ * Alter the generated link data before a sitemap is saved.
+ *
  * This hook gets invoked for every sitemap chunk generated.
  *
  * @param array &$links
- *   Array containing multilingual links generated for each path to be indexed
- *
- * @param string $sitemap_variant
+ *   Array containing multilingual links generated for each path to be indexed.
+ * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap
+ *   Sitemap entity.
  */
-function hook_simple_sitemap_links_alter(array &$links, $sitemap_variant) {
+function hook_simple_sitemap_links_alter(array &$links, SimpleSitemapInterface $sitemap) {
 
   // Remove German URL for a certain path in the hreflang sitemap.
   foreach ($links as $key => $link) {
@@ -40,16 +48,23 @@ function hook_simple_sitemap_links_alter(array &$links, $sitemap_variant) {
 }
 
 /**
- * Add arbitrary links to the sitemap.
+ * Add arbitrary links to a sitemap.
+ *
+ * This hook gets invoked for sitemaps that are of a type that implements the
+ * 'arbitrary' URL generator plugin. E.g. if wanting to alter the sitemap index,
+ * add the 'arbitrary' URL generator to the 'Sitemap index' sitemap type first.
  *
  * @param array &$arbitrary_links
- * @param string $sitemap_variant
+ *   An array of arbitrary links. Their structure depends on what the sitemap
+ *   type's sitemap generator expects.
+ * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap
+ *   Sitemap entity.
  */
-function hook_simple_sitemap_arbitrary_links_alter(array &$arbitrary_links, $sitemap_variant) {
+function hook_simple_sitemap_arbitrary_links_alter(array &$arbitrary_links, SimpleSitemapInterface $sitemap) {
 
-  // Add an arbitrary link to all sitemap variants.
+  // Add an arbitrary link to all sitemaps.
   $arbitrary_links[] = [
-    'url' => 'http://some-arbitrary-link/',
+    'url' => 'https://some-arbitrary-link/',
     'priority' => '0.5',
 
     // An ISO8601 formatted date.
@@ -57,48 +72,54 @@ function hook_simple_sitemap_arbitrary_links_alter(array &$arbitrary_links, $sit
 
     'changefreq' => 'weekly',
     'images' => [
-      ['path' => 'http://path-to-image.png']
+      ['path' => 'https://path-to-image.png'],
     ],
 
     // Add alternate URLs for every language of a multilingual site.
     // Not necessary for monolingual sites.
     'alternate_urls' => [
-      'en' => 'http://this-is-your-life.net/de/tyler',
-      'de' => 'http://this-is-your-life.net/en/tyler',
-    ]
+      'en' => 'https://this-is-your-life.net/de/tyler',
+      'de' => 'https://this-is-your-life.net/en/tyler',
+    ],
   ];
 
   // Add an arbitrary link to the 'fight_club' sitemap variant only.
-  switch ($sitemap_variant) {
+  switch ($sitemap->id()) {
     case 'fight_club':
       $arbitrary_links[] = [
-        'url' => 'http://this-is-your-life.net/tyler',
+        'url' => 'https://this-is-your-life.net/tyler',
       ];
       break;
   }
 }
 
 /**
- * Alters the sitemap attributes shortly before XML document generation.
+ * Alters a sitemap's attributes shortly before XML document generation.
+ *
  * Attributes can be added, changed and removed.
  *
  * @param array &$attributes
- * @param string $sitemap_variant
+ *   An array of attributes.
+ * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap
+ *   Sitemap entity.
  */
-function hook_simple_sitemap_attributes_alter(array &$attributes, $sitemap_variant) {
+function hook_simple_sitemap_attributes_alter(array &$attributes, SimpleSitemapInterface $sitemap) {
 
   // Remove the xhtml attribute e.g. if no xhtml sitemap elements are present.
   unset($attributes['xmlns:xhtml']);
 }
 
 /**
- * Alters attributes of the sitemap index shortly before XML document generation.
+ * Alters attributes of the sitemap index before XML document generation.
+ *
  * Attributes can be added, changed and removed.
  *
  * @param array &$index_attributes
- * @param string $sitemap_variant
+ *   An array of attributes.
+ * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap
+ *   Sitemap entity.
  */
-function hook_simple_sitemap_index_attributes_alter(array &$index_attributes, $sitemap_variant) {
+function hook_simple_sitemap_index_attributes_alter(array &$index_attributes, SimpleSitemapInterface $sitemap) {
 
   // Add some attribute to the sitemap index.
   $index_attributes['name'] = 'value';
@@ -108,6 +129,7 @@ function hook_simple_sitemap_index_attributes_alter(array &$index_attributes, $s
  * Alter properties of and remove URL generator plugins.
  *
  * @param array $url_generators
+ *   Array of URL generators.
  */
 function hook_simple_sitemap_url_generators_alter(array &$url_generators) {
 
@@ -119,6 +141,7 @@ function hook_simple_sitemap_url_generators_alter(array &$url_generators) {
  * Alter properties of and remove sitemap generator plugins.
  *
  * @param array $sitemap_generators
+ *   Array of sitemap generators.
  */
 function hook_simple_sitemap_sitemap_generators_alter(array &$sitemap_generators) {
 
@@ -126,15 +149,6 @@ function hook_simple_sitemap_sitemap_generators_alter(array &$sitemap_generators
   unset($sitemap_generators['default']);
 }
 
-/**
- * Alter properties of and remove sitemap type plugins.
- *
- * @param array $sitemap_types
- */
-function hook_simple_sitemap_sitemap_types_alter(array &$sitemap_types) {
-
-}
-
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/web/modules/simple_sitemap/simple_sitemap.drush.inc b/web/modules/simple_sitemap/simple_sitemap.drush.inc
index 709f3ff30cd1303eeb602df34ab99be9c28d9307..24e9979d41c4f9c642ab4ee4a8a772ffb62f6d07 100644
--- a/web/modules/simple_sitemap/simple_sitemap.drush.inc
+++ b/web/modules/simple_sitemap/simple_sitemap.drush.inc
@@ -12,14 +12,14 @@
  */
 function simple_sitemap_drush_command() {
   $items['simple-sitemap-generate'] = [
-    'description' => 'Regenerate all XML sitemap variants or continue generation.',
+    'description' => 'Regenerate all sitemaps or continue generation.',
     'callback' => 'drush_simple_sitemap_generate',
     'drupal dependencies' => ['simple_sitemap'],
     'aliases' => ['ssg'],
   ];
 
   $items['simple-sitemap-rebuild-queue'] = [
-    'description' => 'Queue all sitemap variants for regeneration.',
+    'description' => 'Queue all sitemaps for regeneration.',
     'callback' => 'drush_simple_sitemap_rebuild_queue',
     'drupal dependencies' => ['simple_sitemap'],
     'aliases' => ['ssr'],
@@ -34,14 +34,18 @@ function simple_sitemap_drush_command() {
  * Regenerate the XML sitemaps according to the module settings.
  */
 function drush_simple_sitemap_generate() {
-  \Drupal::service('simple_sitemap.generator')->generateSitemap(QueueWorker::GENERATE_TYPE_DRUSH);
+  /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
+  $generator = \Drupal::service('simple_sitemap.generator');
+  $generator->generate(QueueWorker::GENERATE_TYPE_DRUSH);
 }
 
 /**
  * Callback function for hook_drush_command().
  *
- * Rebuild the sitemap queue for all sitemap variants.
+ * Rebuild the sitemap queue for all sitemaps.
  */
 function drush_simple_sitemap_rebuild_queue() {
-  \Drupal::service('simple_sitemap.generator')->rebuildQueue();
+  /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
+  $generator = \Drupal::service('simple_sitemap.generator');
+  $generator->rebuildQueue();
 }
diff --git a/web/modules/simple_sitemap/simple_sitemap.info.yml b/web/modules/simple_sitemap/simple_sitemap.info.yml
index 21929697774f5dcd0d4737c84d61aa5b6b97d791..d67d04c7f944f46c6a87e4bec229f4006f9fb247 100644
--- a/web/modules/simple_sitemap/simple_sitemap.info.yml
+++ b/web/modules/simple_sitemap/simple_sitemap.info.yml
@@ -1,12 +1,11 @@
 name: 'Simple XML Sitemap'
 type: module
 description: 'Generates standard conform hreflang XML sitemaps of the site content and provides a framework for developing other sitemap types.'
-configure: simple_sitemap.sitemaps
+configure: entity.simple_sitemap.collection
 package: SEO
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9.3 || ^10
 
-# Information added by Drupal.org packaging script on 2021-10-16
-version: '8.x-3.11'
+# Information added by Drupal.org packaging script on 2023-06-09
+version: '4.1.6'
 project: 'simple_sitemap'
-datestamp: 1634343987
+datestamp: 1686288645
diff --git a/web/modules/simple_sitemap/simple_sitemap.install b/web/modules/simple_sitemap/simple_sitemap.install
index b5627656a807c2085d1976d2922d6764fe859873..b23c1a1f5625759c39e55860e1d663beb585db28 100644
--- a/web/modules/simple_sitemap/simple_sitemap.install
+++ b/web/modules/simple_sitemap/simple_sitemap.install
@@ -5,13 +5,12 @@
  * Module install and update procedures.
  */
 
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Database\Database;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
 
 /**
  * Implements hook_requirements().
- *
- * @param $phase
- * @return array
  */
 function simple_sitemap_requirements($phase) {
   $requirements = [];
@@ -29,7 +28,8 @@ function simple_sitemap_requirements($phase) {
 
     case 'runtime':
 
-      // todo Implement for 3.x
+      // @todo Implement for 4.x
+      // phpcs:disable
 //      /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
 //      $generator = \Drupal::service('simple_sitemap.generator');
 //      $generated_ago = $generator->getGeneratedAgo();
@@ -64,6 +64,7 @@ function simple_sitemap_requirements($phase) {
 //        'description' => $description,
 //        'severity' => $severity,
 //      ];
+      // phpcs:enable
       break;
   }
   return $requirements;
@@ -73,7 +74,7 @@ function simple_sitemap_requirements($phase) {
  * Implements hook_uninstall().
  */
 function simple_sitemap_uninstall() {
-  \Drupal::service('state')->deleteMultiple([
+  \Drupal::state()->deleteMultiple([
     'simple_sitemap.last_cron_generate',
     'simple_sitemap.queue_items_initial_amount',
     'simple_sitemap.queue_stashed_results',
@@ -93,14 +94,14 @@ function simple_sitemap_schema() {
     'fields' => [
       'id' => [
         'description' => 'Sitemap chunk unique identifier.',
-        'type' => 'int',
+        'type' => 'serial',
         'not null' => TRUE,
         'unsigned' => TRUE,
       ],
       'type' => [
         'description' => 'Type of sitemap this chunk belongs to.',
         'type' => 'varchar',
-        'length' => 50,
+        'length' => EntityTypeInterface::ID_MAX_LENGTH,
         'not null' => TRUE,
         'default' => '',
       ],
@@ -163,13 +164,13 @@ function simple_sitemap_schema() {
       'entity_type' => [
         'description' => 'Entity type of the overriding entity.',
         'type' => 'varchar',
-        'length' => 32,
+        'length' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
         'not null' => TRUE,
       ],
       'entity_id' => [
         'description' => 'ID of the overriding entity.',
         'type' => 'varchar',
-        'length' => 32,
+        'length' => EntityTypeInterface::ID_MAX_LENGTH,
         'not null' => TRUE,
       ],
       'inclusion_settings' => [
@@ -186,6 +187,9 @@ function simple_sitemap_schema() {
   return $schema;
 }
 
+/**
+ * Setting the default variant.
+ */
 function _simple_sitemap_update_8216_get_default_variant() {
   $config_factory = \Drupal::service('config.factory');
   $default_variant = $config_factory->get('simple_sitemap.settings')->get('default_variant');
@@ -196,13 +200,6 @@ function _simple_sitemap_update_8216_get_default_variant() {
       ->save();
   }
 
-  /** @var \Drupal\simple_sitemap\SimplesitemapManager $manager */
-  $manager = \Drupal::service('simple_sitemap.manager');
-  $variants = $manager->getSitemapVariants();
-  if (!isset($variants[$default_variant])) {
-    $manager->addSitemapVariant($default_variant);
-  }
-
   return $default_variant;
 }
 
@@ -221,7 +218,7 @@ function simple_sitemap_update_8201() {
   ];
   foreach ($entity_types as $entity_type_name => $settings) {
     if (isset($naming_changes[$entity_type_name])) {
-      $entity_types[$naming_changes[$entity_type_name]] = $entity_types[$entity_type_name];
+      $entity_types[$naming_changes[$entity_type_name]] = $settings;
       unset($entity_types[$entity_type_name]);
     }
   }
@@ -273,7 +270,7 @@ function simple_sitemap_update_8202() {
 
   foreach ($entity_types as $entity_type_name => &$entity_type) {
     if (is_array($entity_type)) {
-      foreach ($entity_type as $bundle_name => &$bundle) {
+      foreach ($entity_type as &$bundle) {
         if (isset($bundle['entities'])) {
           foreach ($bundle['entities'] as $entity_id => $entity_settings) {
             $database->insert('simple_sitemap_entity_overrides')
@@ -300,17 +297,17 @@ function simple_sitemap_update_8202() {
  * simple_sitemap.entity_types and simple_sitemap.custom.
  */
 function simple_sitemap_update_8203() {
-  $old_config = $config = \Drupal::config('simple_sitemap.settings');
+  $old_config = \Drupal::config('simple_sitemap.settings');
   foreach (['entity_types', 'custom'] as $config_name) {
-    if (!$config = $old_config->get($config_name)) {
-      continue;
+    if ($config = $old_config->get($config_name)) {
+      \Drupal::service('config.factory')->getEditable("simple_sitemap.$config_name")
+        ->setData($config)->save();
     }
-    \Drupal::service('config.factory')->getEditable("simple_sitemap.$config_name")
-      ->setData($config)->save();
   }
-  $settings = $old_config->get('settings');
-  \Drupal::service('config.factory')->getEditable("simple_sitemap.settings")
-    ->setData($settings)->save();
+  if (NULL !== ($settings = $old_config->get('settings'))) {
+    \Drupal::service('config.factory')->getEditable("simple_sitemap.settings")
+      ->setData($settings)->save();
+  }
 }
 
 /**
@@ -424,7 +421,7 @@ function simple_sitemap_update_8208() {
     $settings = unserialize($row->inclusion_settings);
     if (!isset($settings['changefreq'])) {
       \Drupal::database()->update('simple_sitemap_entity_overrides')
-        ->fields(['inclusion_settings' => serialize($settings + ['changefreq' => '']),])
+        ->fields(['inclusion_settings' => serialize($settings + ['changefreq' => ''])])
         ->condition('id', $row->id)
         ->execute();
     }
@@ -460,7 +457,7 @@ function simple_sitemap_update_8209() {
     $settings = unserialize($row->inclusion_settings);
     if (!isset($settings['include_images'])) {
       \Drupal::database()->update('simple_sitemap_entity_overrides')
-        ->fields(['inclusion_settings' => serialize($settings + ['include_images' => 0]),])
+        ->fields(['inclusion_settings' => serialize($settings + ['include_images' => 0])])
         ->condition('id', $row->id)
         ->execute();
     }
@@ -531,7 +528,7 @@ function simple_sitemap_update_8211() {
       'default' => [
         'label' => 'Default',
         'type' => 'default_hreflang',
-      ]
+      ],
     ])->save();
 }
 
@@ -547,13 +544,9 @@ function simple_sitemap_update_8212() {
   foreach ($all_bundle_settings as $bundle_settings) {
     $config = $config_factory->get($bundle_settings)->get();
 
-    $config['include_images'] = isset($config['include_images'])
-      ? (bool) $config['include_images']
-      : FALSE;
+    $config['include_images'] = isset($config['include_images']) && $config['include_images'];
 
-    $config['index'] = isset($config['index'])
-      ? (bool) $config['index']
-      : FALSE;
+    $config['index'] = isset($config['index']) && $config['index'];
 
     $config_factory->getEditable($bundle_settings)->setData($config)->save();
   }
@@ -641,7 +634,7 @@ function simple_sitemap_update_8215() {
   foreach ($config_factory->listAll('simple_sitemap.variants.') as $type) {
     $type = $config_factory->getEditable($type);
     $variants = $type->get('variants');
-    foreach($variants as $i => $variant) {
+    foreach ($variants as $i => $variant) {
       $variants[$i]['weight'] = 0;
     }
     $type->set('variants', $variants)->save();
@@ -764,7 +757,8 @@ function simple_sitemap_update_8303() {
       ],
     ],
   ];
-  $schema->addIndex('simple_sitemap', 'type_status_delta', ['type', 'status', 'delta'], $spec);
+  $fields = ['type', 'status', 'delta'];
+  $schema->addIndex('simple_sitemap', 'type_status_delta', $fields, $spec);
 
   $spec = [
     'fields' => [
@@ -788,7 +782,8 @@ function simple_sitemap_update_8303() {
       ],
     ],
   ];
-  $schema->addIndex('simple_sitemap_entity_overrides', 'entity_key', ['type', 'entity_type', 'entity_id'], $spec);
+  $fields = ['type', 'entity_type', 'entity_id'];
+  $schema->addIndex('simple_sitemap_entity_overrides', 'entity_key', $fields, $spec);
 }
 
 /**
@@ -817,3 +812,117 @@ function simple_sitemap_update_8305() {
     ]
   );
 }
+
+/**
+ * Change the simple_sitemap ID column to serial.
+ */
+function simple_sitemap_update_8401() {
+  \Drupal::database()->schema()->changeField(
+    'simple_sitemap',
+    'id',
+    'id', [
+      'description' => 'Sitemap chunk unique identifier.',
+      'type' => 'serial',
+      'not null' => TRUE,
+      'unsigned' => TRUE,
+    ]
+  );
+}
+
+/**
+ * Install new simple_sitemap_type and simple_sitemap configuration entities.
+ */
+function simple_sitemap_update_8402() {
+  foreach (['simple_sitemap_type', 'simple_sitemap'] as $entity_type) {
+    \Drupal::entityDefinitionUpdateManager()->installEntityType(\Drupal::entityTypeManager()->getDefinition($entity_type));
+  }
+}
+
+/**
+ * Migrate the default_hreflang sitemap type and its variants to new configuration entities.
+ */
+function simple_sitemap_update_8403() {
+
+  // Create the default_hreflang sitemap type.
+  $type_storage = \Drupal::entityTypeManager()->getStorage('simple_sitemap_type');
+  if ($type_storage->load('default_hreflang') === NULL) {
+    $type_storage->create([
+      'id' => 'default_hreflang',
+      'label' => 'Default hreflang',
+      'description' => 'The default hreflang sitemap type.',
+      'sitemap_generator' => 'default',
+      'url_generators' => [
+        'custom',
+        'entity',
+        'entity_menu_link_content',
+        'arbitrary',
+      ],
+    ])->save();
+  }
+
+  // Migrate variants of default_hreflang sitemap type.
+  $config_factory = \Drupal::configFactory();
+  $sitemap_storage = \Drupal::entityTypeManager()->getStorage('simple_sitemap');
+  $old_variants_config = $config_factory->get('simple_sitemap.variants.default_hreflang');
+  foreach ($old_variants_config->get('variants') as $variant_id => $variant_definition) {
+    if ($sitemap_storage->load(substr($variant_id, 0, 32)) === NULL) {
+      $sitemap_storage->create([
+        'id' => substr($variant_id, 0, 32),
+        'label' => $variant_definition['label'] ?? $variant_id,
+        'type' => 'default_hreflang',
+        'weight' => $variant_definition['weight'] ?? 0,
+      ])->save();
+    }
+  }
+  foreach ($config_factory->listAll('simple_sitemap.variants.') as $config) {
+    $config_factory->getEditable($config)->delete();
+  }
+  \Drupal::service('simple_sitemap.queue_worker')->deleteQueue();
+  SimpleSitemap::purgeContent();
+
+  return t('All variants belonging to the built-in "Default hreflang" sitemap type have been converted to entities. Custom sitemap types added via plugins will have to be recreated manually. See simple_sitemap.type.default_hreflang.yml. The sitemaps need to be regenerated now.');
+}
+
+/**
+ * Add dependencies to sitemap entities.
+ */
+function simple_sitemap_update_8404() {
+  foreach (SimpleSitemap::loadMultiple() as $sitemap) {
+    $sitemap->save();
+  }
+}
+
+/**
+ * Create the index sitemap type.
+ */
+function simple_sitemap_update_8405() {
+  $type_storage = \Drupal::entityTypeManager()->getStorage('simple_sitemap_type');
+  if ($type_storage->load('index') === NULL) {
+    $type_storage->create([
+      'id' => 'index',
+      'label' => 'Sitemap Index',
+      'description' => 'The sitemap index sitemap type. A sitemap of this type lists sitemaps of all other types.',
+      'sitemap_generator' => 'index',
+      'url_generators' => ['index'],
+    ])->save();
+  }
+}
+
+/**
+ * Create the index sitemap.
+ */
+function simple_sitemap_update_8406() {
+  $sitemap_storage = \Drupal::entityTypeManager()->getStorage('simple_sitemap');
+  if ($sitemap_storage->load('index') === NULL) {
+    $sitemap_storage->create([
+      'id' => 'index',
+      'label' => 'Sitemap Index',
+      'description' => 'The sitemap index listing all other sitemaps - useful if there are at least two other sitemaps. In most cases this sitemap should be last in the generation queue and set as the default sitemap.',
+      'type' => 'index',
+      'weight' => 1000,
+      'status' => FALSE,
+    ])->save();
+  }
+
+  return t('A sitemap index which lists all other sitemaps is now available and can be enabled.');
+}
diff --git a/web/modules/simple_sitemap/simple_sitemap.libraries.yml b/web/modules/simple_sitemap/simple_sitemap.libraries.yml
index 272381459d259af48ebe08cb3044e34239730d4f..c3c4b9c79c4b0ce6b850d7793dff31b218bbad8e 100644
--- a/web/modules/simple_sitemap/simple_sitemap.libraries.yml
+++ b/web/modules/simple_sitemap/simple_sitemap.libraries.yml
@@ -4,13 +4,17 @@ fieldsetSummaries:
     js/simple_sitemap.fieldsetSummaries.js: {}
   dependencies:
     - core/jquery
+    - core/drupal
+
 sitemapEntities:
   version: VERSION
   js:
     js/simple_sitemap.sitemapEntities.js: {}
   dependencies:
     - core/jquery
-    - core/drupalSettings
+    - core/drupal
+    - core/once
+
 sitemaps:
   version: VERSION
   css:
diff --git a/web/modules/simple_sitemap/simple_sitemap.links.action.yml b/web/modules/simple_sitemap/simple_sitemap.links.action.yml
new file mode 100644
index 0000000000000000000000000000000000000000..02b4f6d40e0b42915fef7a7b442b63f166e3f5e5
--- /dev/null
+++ b/web/modules/simple_sitemap/simple_sitemap.links.action.yml
@@ -0,0 +1,11 @@
+simple_sitemap.sitemap.add:
+  route_name: simple_sitemap.add
+  title: 'Add sitemap'
+  appears_on:
+    - entity.simple_sitemap.collection
+
+simple_sitemap.type.add:
+  route_name: simple_sitemap_type.add
+  title: 'Add sitemap type'
+  appears_on:
+    - entity.simple_sitemap_type.collection
diff --git a/web/modules/simple_sitemap/simple_sitemap.links.menu.yml b/web/modules/simple_sitemap/simple_sitemap.links.menu.yml
index 0a6eb0a74c6f4379bbba2c74a357a947df0e65c3..2948146d1cb02bf4ff4bc656752d3c3af963632c 100644
--- a/web/modules/simple_sitemap/simple_sitemap.links.menu.yml
+++ b/web/modules/simple_sitemap/simple_sitemap.links.menu.yml
@@ -2,12 +2,12 @@ simple_sitemap.sitemaps:
   title: 'Simple XML Sitemap'
   description: 'Configure, add content to and generate XML sitemaps.'
   parent: system.admin_config_search
-  route_name: simple_sitemap.sitemaps
+  route_name: entity.simple_sitemap.collection
 
-simple_sitemap.settings:
-  title: 'Settings'
+simple_sitemap.status:
+  title: 'Sitemaps'
   parent: simple_sitemap.sitemaps
-  route_name: simple_sitemap.settings
+  route_name: entity.simple_sitemap.collection
   weight: 0
 
 simple_sitemap.inclusion:
diff --git a/web/modules/simple_sitemap/simple_sitemap.links.task.yml b/web/modules/simple_sitemap/simple_sitemap.links.task.yml
index 01548962e5e2af2f2a8b29f36731a855da8bda73..9fcd2d9ab5ce12617de904a9a5684ad32eb39af1 100644
--- a/web/modules/simple_sitemap/simple_sitemap.links.task.yml
+++ b/web/modules/simple_sitemap/simple_sitemap.links.task.yml
@@ -1,41 +1,41 @@
 simple_sitemap.sitemaps:
-  route_name: simple_sitemap.sitemaps
+  route_name: entity.simple_sitemap.collection
   title: 'Sitemaps'
-  base_route: simple_sitemap.sitemaps
+  base_route: entity.simple_sitemap.collection
   weight: -1
 
 simple_sitemap.status:
-  route_name: simple_sitemap.sitemaps
+  route_name: entity.simple_sitemap.collection
   title: 'Status'
   parent_id: simple_sitemap.sitemaps
   weight: -1
 
-simple_sitemap.variants:
-  route_name: simple_sitemap.variants
-  title: 'Variants'
+simple_sitemap.types:
+  route_name: entity.simple_sitemap_type.collection
+  title: 'Types'
   parent_id: simple_sitemap.sitemaps
   weight: 0
 
 simple_sitemap.settings:
   route_name: simple_sitemap.settings
   title: 'Settings'
-  base_route: simple_sitemap.sitemaps
-  weight: 0
+  parent_id: simple_sitemap.sitemaps
+  weight: 1
 
 simple_sitemap.inclusion:
   route_name: simple_sitemap.entities
   title: 'Inclusion'
-  base_route: simple_sitemap.sitemaps
+  base_route: entity.simple_sitemap.collection
   weight: 1
 
 simple_sitemap.entities:
   route_name: simple_sitemap.entities
   title: 'Entities'
   parent_id: simple_sitemap.inclusion
-  weight: 0
+  weight: -1
 
 simple_sitemap.custom:
   route_name: simple_sitemap.custom
   title: 'Custom links'
   parent_id: simple_sitemap.inclusion
-  weight: 1
+  weight: 0
diff --git a/web/modules/simple_sitemap/simple_sitemap.module b/web/modules/simple_sitemap/simple_sitemap.module
index ad0098166e9472790310d558585d79e97188c24d..a10b620cd68609029303bf5685ed415db80915b7 100644
--- a/web/modules/simple_sitemap/simple_sitemap.module
+++ b/web/modules/simple_sitemap/simple_sitemap.module
@@ -14,145 +14,53 @@
 
 /**
  * Implements hook_help().
- *
- * @param $route_name
- * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
- * @return \Drupal\Component\Render\MarkupInterface|null
  */
 function simple_sitemap_help($route_name, RouteMatchInterface $route_match) {
-  return $route_name === 'help.page.simple_sitemap'
-    ? check_markup(file_get_contents(__DIR__ . '/README.md'))
-    : NULL;
-}
+  switch ($route_name) {
+    case 'help.page.simple_sitemap':
+      return check_markup(file_get_contents(__DIR__ . '/README.md'));
 
-/**
- * Implements hook_form_alter().
- *
- * Adds sitemap settings to entity types that are supported via plugins.
- *
- * @param $form
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * @param $form_id
- * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
- * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
- */
-function simple_sitemap_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+    case 'simple_sitemap.entities':
+      return '<p>' . t('Simple XML Sitemap settings will be added only to entity forms of entity types enabled here. Settings for specific entity bundles (e.g. <em>page</em>) can be adjusted here or on the bundle pages.') . '</p>';
 
-  /** @var Drupal\simple_sitemap\Form\FormHelper $f */
-  $f = \Drupal::service('simple_sitemap.form_helper');
-  if (!$f->processForm($form_state)) {
-    return;
-  }
+    case 'simple_sitemap.custom':
+      return '<p>' . t('Add custom internal drupal paths to specific sitemaps.') . '</p>'
+        . '<p>' . t("Specify drupal internal (relative) paths, one per line. Do not forget to prepend the paths with a '/'.<br>Optionally link priority <em>(0.0 - 1.0)</em> can be added by appending it after a space.<br> Optionally link change frequency <em>(always / hourly / daily / weekly / monthly / yearly / never)</em> can be added by appending it after a space.") . '</p>'
+        . '<p>' . t('<strong>Examples:</strong><br><em>/ 1.0 daily</em> -> home page with the highest priority and daily change frequency<br><em>/contact</em> -> contact page with the default priority and no change frequency information') . '</p>';
 
-  $form['simple_sitemap'] = [
-    '#type' => 'details',
-    '#group' => isset($form['additional_settings']) ? 'additional_settings' : 'advanced',
-    '#title' => t('Simple XML Sitemap'),
-    '#description' => $f->getEntityCategory() === 'instance' ? t('Settings for this entity can be overridden here.') : '',
-    '#weight' => 10,
-  ];
-
-  // Only attach fieldset summary js to 'additional settings' vertical tabs.
-  if (isset($form['additional_settings'])) {
-    $form['#attached']['library'][] = 'simple_sitemap/fieldsetSummaries';
-  }
-
-  $f->displayEntitySettings($form['simple_sitemap'])
-  // todo: do not show setting when creating new bundle.
-    ->displayRegenerateNow($form['simple_sitemap']);
-
-  // Add submission handler.
-  if (isset($form['actions']['submit']['#submit'])) {
-    foreach (array_keys($form['actions']) as $action) {
-      if ($action !== 'preview'
-        && isset($form['actions'][$action]['#type'])
-        && $form['actions'][$action]['#type'] === 'submit') {
-        $form['actions'][$action]['#submit'][] = 'simple_sitemap_entity_form_submit';
-      }
-    }
-  }
-  // Fix for account page rendering other submit handlers not usable.
-  else {
-    $form['#submit'][] = 'simple_sitemap_entity_form_submit';
+    default:
+      return NULL;
   }
 }
 
 /**
- * Form submission handler called in hook_form_alter.
+ * Implements hook_form_alter().
  *
- * @param $form
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
- * @throws \Drupal\Component\Plugin\Exception\PluginException
- * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * Adds sitemap settings to entity types that are supported via plugins.
  */
-function simple_sitemap_entity_form_submit($form, FormStateInterface &$form_state) {
-
-  /** @var Drupal\simple_sitemap\Form\FormHelper $f */
-  $f = \Drupal::service('simple_sitemap.form_helper');
-  if (!$f->processForm($form_state)) {
-    return;
-  }
-
-  $values = $form_state->getValues();
-
-  // Fix for values appearing in a sub array on a commerce product entity.
-  $values = isset($values['simple_sitemap']) ? $values['simple_sitemap'] : $values;
-
-  if ($f->valuesChanged($form, $values)) {
-
-    /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
-    $generator = \Drupal::service('simple_sitemap.generator');
-
-    foreach ($generator->getSitemapManager()->getSitemapVariants(NULL, FALSE) as $variant => $definition) {
-
-      if (isset($values['index_' . $variant . '_' . $f->getEntityTypeId() . '_settings'])) { // Variants may have changed since form load.
-        $settings = [
-          'index' => (bool) $values['index_' . $variant . '_' . $f->getEntityTypeId() . '_settings'],
-          'priority' => $values['priority_' . $variant . '_' . $f->getEntityTypeId() . '_settings'],
-          'changefreq' => $values['changefreq_' . $variant . '_' . $f->getEntityTypeId() . '_settings'],
-          'include_images' => (bool) $values['include_images_' . $variant . '_' . $f->getEntityTypeId() . '_settings'],
-        ];
-
-        $generator->setVariants($variant);
-
-        switch ($f->getEntityCategory()) {
-          case 'bundle':
-            $generator->setBundleSettings($f->getEntityTypeId(), $f->getBundleName(), $settings);
-            break;
-
-          case 'instance':
-            if (!$f->entityIsNew()) { // Make sure the entity is saved first for multi-step forms, see https://www.drupal.org/project/simple_sitemap/issues/3080510.
-              $generator->setEntityInstanceSettings($f->getEntityTypeId(), $f->getInstanceId(), $settings);
-            }
-            break;
-        }
-      }
-    }
-
-    // Regenerate sitemaps according to user setting.
-    if (!empty($values['simple_sitemap_regenerate_now'])) {
-      $generator->setVariants(TRUE)
-        ->rebuildQueue()
-        ->generateSitemap();
-    }
-  }
+function simple_sitemap_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  /** @var \Drupal\simple_sitemap\Form\FormHelper $form_helper */
+  $form_helper = \Drupal::service('simple_sitemap.form_helper');
+  $form_helper->formAlter($form, $form_state);
 }
 
 /**
  * Implements hook_cron().
  */
 function simple_sitemap_cron() {
+  /** @var \Drupal\simple_sitemap\Settings $settings */
+  $settings = \Drupal::service(('simple_sitemap.settings'));
 
-  /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
-  $generator = \Drupal::service('simple_sitemap.generator');
+  if ($settings->get('cron_generate')) {
 
-  if ($generator->getSetting('cron_generate')) {
-    $interval = (int) $generator->getSetting('cron_generate_interval', 0) * 60 * 60;
+    $interval = (int) $settings->get('cron_generate_interval', 0) * 60 * 60;
     $request_time = \Drupal::service('datetime.time')->getRequestTime();
-    $generation_in_progress = $generator->getQueueWorker()->generationInProgress();
     $state = \Drupal::state();
 
+    /** @var \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker */
+    $queue_worker = \Drupal::service('simple_sitemap.queue_worker');
+    $generation_in_progress = $queue_worker->generationInProgress();
+
     if ($interval === 0
       || $generation_in_progress
       || (($state->get('simple_sitemap.last_cron_generate', 0) + $interval) <= $request_time)) {
@@ -161,7 +69,9 @@ function simple_sitemap_cron() {
         $state->set('simple_sitemap.last_cron_generate', $request_time);
       }
 
-      $generator->generateSitemap(QueueWorker::GENERATE_TYPE_CRON);
+      /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
+      $generator = \Drupal::service('simple_sitemap.generator');
+      $generator->generate(QueueWorker::GENERATE_TYPE_CRON);
     }
   }
 }
@@ -173,13 +83,13 @@ function simple_sitemap_cron() {
  */
 function simple_sitemap_configurable_language_delete(ConfigurableLanguageInterface $language) {
 
-  /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
-  $generator = \Drupal::service('simple_sitemap.generator');
+  /** @var \Drupal\simple_sitemap\Settings $settings */
+  $settings = \Drupal::service('simple_sitemap.settings');
 
-  $excluded_languages = $generator->getSetting('excluded_languages');
+  $excluded_languages = $settings->get('excluded_languages', []);
   if (isset($excluded_languages[$language->id()])) {
     unset($excluded_languages[$language->id()]);
-    $generator->saveSetting('excluded_languages', $excluded_languages);
+    $settings->save('excluded_languages', $excluded_languages);
   }
 }
 
@@ -187,18 +97,16 @@ function simple_sitemap_configurable_language_delete(ConfigurableLanguageInterfa
  * Implements hook_entity_delete().
  *
  * Removes settings of the removed entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
  */
 function simple_sitemap_entity_delete(EntityInterface $entity) {
 
-  /** @var \Drupal\simple_sitemap\EntityHelper $entity_helper */
+  /** @var \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper */
   $entity_helper = \Drupal::service('simple_sitemap.entity_helper');
   if ($entity_helper->supports($entity->getEntityType())) {
 
-    /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
+    /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
     $generator = \Drupal::service('simple_sitemap.generator');
-    $generator->setVariants(TRUE)->removeEntityInstanceSettings(
+    $generator->setVariants()->entityManager()->removeEntityInstanceSettings(
       $entity->getEntityTypeId(), $entity->id()
     );
   }
@@ -209,16 +117,14 @@ function simple_sitemap_entity_delete(EntityInterface $entity) {
  *
  * Removes settings of the removed bundle.
  *
- * @param string $entity_type_id
- * @param string $bundle
  * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
  * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
  */
 function simple_sitemap_entity_bundle_delete($entity_type_id, $bundle) {
 
-  /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
+  /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
   $generator = \Drupal::service('simple_sitemap.generator');
-  $generator->setVariants(TRUE)->removeBundleSettings($entity_type_id, $bundle);
+  $generator->setVariants()->entityManager()->removeBundleSettings($entity_type_id, $bundle);
 }
 
 /**
@@ -226,15 +132,14 @@ function simple_sitemap_entity_bundle_delete($entity_type_id, $bundle) {
  *
  * Removes settings for the removed menu.
  *
- * @param \Drupal\system\MenuInterface $menu
  * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
  * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
  */
 function simple_sitemap_menu_delete(MenuInterface $menu) {
 
-  /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
+  /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
   $generator = \Drupal::service('simple_sitemap.generator');
-  $generator->setVariants(TRUE)->removeBundleSettings('menu_link_content', $menu->id());
+  $generator->setVariants()->entityManager()->removeBundleSettings('menu_link_content', $menu->id());
 }
 
 /**
@@ -243,17 +148,17 @@ function simple_sitemap_menu_delete(MenuInterface $menu) {
 function simple_sitemap_page_attachments_alter(array &$attachments) {
   if (!empty($attachments['#attached']['html_head_link'])) {
 
-    /** @var \Drupal\simple_sitemap\Simplesitemap $generator */
-    $generator = \Drupal::service('simple_sitemap.generator');
+    /** @var \Drupal\simple_sitemap\Settings $settings */
+    $settings = \Drupal::service('simple_sitemap.settings');
 
-    if ($generator->getSetting('disable_language_hreflang')) {
+    if ($settings->get('disable_language_hreflang')) {
       // @fixme https://www.drupal.org/project/drupal/issues/1255092
       // Content Translation module normally adds identical hreflang tags, so
       // executing its hook_page_attachments() implementation would be harmless,
       // but if an entity page is configured as the front page, it attaches
       // extraneous hreflang tags using the entity URL.
       foreach ($attachments['#attached']['html_head_link'] as $key => $list) {
-        foreach ($list as $k => $element) {
+        foreach ($list as $element) {
           if (!empty($element['hreflang']) && !empty($element['rel'])) {
             unset($attachments['#attached']['html_head_link'][$key]);
           }
@@ -262,3 +167,30 @@ function simple_sitemap_page_attachments_alter(array &$attachments) {
     }
   }
 }
+
+/**
+ * Implements hook_entity_extra_field_info().
+ */
+function simple_sitemap_entity_extra_field_info() {
+  $extra = [];
+
+  /** @var \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper */
+  $entity_helper = \Drupal::service('simple_sitemap.entity_helper');
+
+  /** @var \Drupal\simple_sitemap\Manager\EntityManager $entity_manager */
+  $entity_manager = \Drupal::service('simple_sitemap.entity_manager');
+
+  foreach ($entity_helper->getSupportedEntityTypes() as $entity_type_id => $entity_type) {
+    if ($entity_type->get('field_ui_base_route') && $entity_manager->entityTypeIsEnabled($entity_type_id)) {
+
+      foreach ($entity_helper->getBundleInfo($entity_type_id) as $bundle_name => $bundle_info) {
+        $extra[$entity_type_id][$bundle_name]['form']['simple_sitemap'] = [
+          'label' => t('Simple XML Sitemap'),
+          'description' => t('Simple XML Sitemap settings'),
+          'weight' => 10,
+        ];
+      }
+    }
+  }
+  return $extra;
+}
diff --git a/web/modules/simple_sitemap/simple_sitemap.post_update.php b/web/modules/simple_sitemap/simple_sitemap.post_update.php
new file mode 100644
index 0000000000000000000000000000000000000000..a258d9358b784021615648b12ca309352606f10a
--- /dev/null
+++ b/web/modules/simple_sitemap/simple_sitemap.post_update.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for the Simple XML Sitemap module.
+ */
+
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Prevent config:import from deleting and recreating configs created through update hooks.
+ *
+ * @see https://www.drupal.org/project/simple_sitemap/issues/3236623
+ */
+function simple_sitemap_post_update_8403(&$sandbox) {
+  $config_factory = \Drupal::configFactory();
+  $settings = Drupal::service('settings');
+
+  foreach (['simple_sitemap.sitemap.', 'simple_sitemap.type.'] as $config_prefix) {
+    foreach ($config_factory->listAll($config_prefix) as $config) {
+      $data = Yaml::parse(@file_get_contents($settings->get('config_sync_directory') . '/' . $config . '.yml'));
+      if ($data && $data['uuid']) {
+        $config_factory->getEditable($config)->set('uuid', $data['uuid'])->save(TRUE);
+      }
+    }
+  }
+}
diff --git a/web/modules/simple_sitemap/simple_sitemap.routing.yml b/web/modules/simple_sitemap/simple_sitemap.routing.yml
index 89d5ec6ab4ef37e00fd5adefaef9fefd02b59957..e03cd8b7779098df577739b4231c1b39759d5910 100644
--- a/web/modules/simple_sitemap/simple_sitemap.routing.yml
+++ b/web/modules/simple_sitemap/simple_sitemap.routing.yml
@@ -1,9 +1,10 @@
 simple_sitemap.sitemap_default:
   path: '/sitemap.xml'
   defaults:
-    _controller: '\Drupal\simple_sitemap\Controller\SimplesitemapController::getSitemap'
+    _controller: '\Drupal\simple_sitemap\Controller\SimpleSitemapController::getSitemap'
     _disable_route_normalizer: 'TRUE'
   requirements:
+    # Sitemaps are accessible for everyone.
     _access: 'TRUE'
 
 # The actual path to a variant is '/{variant}/sitemap.xml'. Because Drupal 8
@@ -13,40 +14,41 @@ simple_sitemap.sitemap_default:
 simple_sitemap.sitemap_variant:
   path: '/sitemaps/{variant}/sitemap.xml'
   defaults:
-    _controller: '\Drupal\simple_sitemap\Controller\SimplesitemapController::getSitemap'
+    _controller: '\Drupal\simple_sitemap\Controller\SimpleSitemapController::getSitemap'
     _disable_route_normalizer: 'TRUE'
   requirements:
+    # Sitemaps are accessible for everyone.
     _access: 'TRUE'
 
 simple_sitemap.sitemap_xsl:
-  path: '/sitemap.xsl'
+  path: '/sitemap_generator/{sitemap_generator}/sitemap.xsl'
   defaults:
-    _controller: '\Drupal\simple_sitemap\Controller\SimplesitemapController::getSitemapXsl'
-    _title: 'Sitemap XSL'
+    _controller: '\Drupal\simple_sitemap\Controller\SimpleSitemapController::getSitemapXsl'
     _disable_route_normalizer: 'TRUE'
   requirements:
+    # Sitemaps are accessible for everyone.
     _access: 'TRUE'
 
-simple_sitemap.sitemaps:
-  path: '/admin/config/search/simplesitemap'
+simple_sitemap.settings:
+  path: '/admin/config/search/simplesitemap/settings'
   defaults:
-    _form: '\Drupal\simple_sitemap\Form\SimplesitemapSitemapsForm'
+    _form: '\Drupal\simple_sitemap\Form\SettingsForm'
     _title: 'Simple XML Sitemap'
   requirements:
     _permission: 'administer sitemap settings'
 
-simple_sitemap.settings:
-  path: '/admin/config/search/simplesitemap/settings'
+simple_sitemap.entities:
+  path: '/admin/config/search/simplesitemap/entities'
   defaults:
-    _form: '\Drupal\simple_sitemap\Form\SimplesitemapSettingsForm'
+    _form: '\Drupal\simple_sitemap\Form\EntitiesForm'
     _title: 'Simple XML Sitemap'
   requirements:
     _permission: 'administer sitemap settings'
 
-simple_sitemap.entities:
-  path: '/admin/config/search/simplesitemap/entities'
+simple_sitemap.entity_bundles:
+  path: '/admin/config/search/simplesitemap/entities/{entity_type_id}'
   defaults:
-    _form: '\Drupal\simple_sitemap\Form\SimplesitemapEntitiesForm'
+    _form: '\Drupal\simple_sitemap\Form\EntityBundlesForm'
     _title: 'Simple XML Sitemap'
   requirements:
     _permission: 'administer sitemap settings'
@@ -54,15 +56,71 @@ simple_sitemap.entities:
 simple_sitemap.custom:
   path: '/admin/config/search/simplesitemap/custom'
   defaults:
-    _form: '\Drupal\simple_sitemap\Form\SimplesitemapCustomLinksForm'
+    _form: '\Drupal\simple_sitemap\Form\CustomLinksForm'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+entity.simple_sitemap.collection:
+  path: '/admin/config/search/simplesitemap'
+  defaults:
+    _entity_list: 'simple_sitemap'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+simple_sitemap.add:
+  path: '/admin/config/search/simplesitemap/variants/add'
+  defaults:
+    _entity_form: 'simple_sitemap.add'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+entity.simple_sitemap.edit_form:
+  path: '/admin/config/search/simplesitemap/variants/{simple_sitemap}'
+  defaults:
+    _entity_form: 'simple_sitemap.edit'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+entity.simple_sitemap.delete_form:
+  path: '/admin/config/search/simplesitemap/variants/{simple_sitemap}/delete'
+  defaults:
+    _entity_form: 'simple_sitemap.delete'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+entity.simple_sitemap_type.collection:
+  path: '/admin/config/search/simplesitemap/types'
+  defaults:
+    _entity_list: 'simple_sitemap_type'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+simple_sitemap_type.add:
+  path: '/admin/config/search/simplesitemap/types/add'
+  defaults:
+    _entity_form: 'simple_sitemap_type.add'
+    _title: 'Simple XML Sitemap'
+  requirements:
+    _permission: 'administer sitemap settings'
+
+entity.simple_sitemap_type.edit_form:
+  path: '/admin/config/search/simplesitemap/types/{simple_sitemap_type}'
+  defaults:
+    _entity_form: 'simple_sitemap_type.edit'
     _title: 'Simple XML Sitemap'
   requirements:
     _permission: 'administer sitemap settings'
 
-simple_sitemap.variants:
-  path: '/admin/config/search/simplesitemap/variants'
+entity.simple_sitemap_type.delete_form:
+  path: '/admin/config/search/simplesitemap/types/{simple_sitemap_type}/delete'
   defaults:
-    _form: '\Drupal\simple_sitemap\Form\SimplesitemapVariantsForm'
+    _entity_form: 'simple_sitemap_type.delete'
     _title: 'Simple XML Sitemap'
   requirements:
     _permission: 'administer sitemap settings'
diff --git a/web/modules/simple_sitemap/simple_sitemap.services.yml b/web/modules/simple_sitemap/simple_sitemap.services.yml
index c156b30a2d2d9f4b8e503960790f94f14fd69c6f..bfe3e53d062d64cf143c077e5754cc02d605500c 100644
--- a/web/modules/simple_sitemap/simple_sitemap.services.yml
+++ b/web/modules/simple_sitemap/simple_sitemap.services.yml
@@ -1,35 +1,34 @@
 services:
   simple_sitemap.generator:
-    class: Drupal\simple_sitemap\Simplesitemap
+    class: Drupal\simple_sitemap\Manager\Generator
+    public: true
+    arguments:
+      - '@simple_sitemap.settings'
+      - '@simple_sitemap.queue_worker'
+      - '@lock'
+      - '@simple_sitemap.logger'
+
+  simple_sitemap.entity_manager:
+    class: Drupal\simple_sitemap\Manager\EntityManager
     public: true
     arguments:
       - '@simple_sitemap.entity_helper'
       - '@simple_sitemap.settings'
-      - '@simple_sitemap.manager'
       - '@config.factory'
       - '@database'
       - '@entity_type.manager'
-      - '@path.validator'
-      - '@date.formatter'
-      - '@datetime.time'
-      - '@simple_sitemap.queue_worker'
-      - '@lock'
-      - '@simple_sitemap.logger'
+      - '@entity_field.manager'
 
-  simple_sitemap.manager:
-    class: Drupal\simple_sitemap\SimplesitemapManager
+  simple_sitemap.custom_link_manager:
+    class: Drupal\simple_sitemap\Manager\CustomLinkManager
     public: true
     arguments:
-    - '@config.factory'
-    - '@database'
-    - '@plugin.manager.simple_sitemap.sitemap_type'
-    - '@plugin.manager.simple_sitemap.url_generator'
-    - '@plugin.manager.simple_sitemap.sitemap_generator'
-    - '@simple_sitemap.settings'
+      - '@config.factory'
+      - '@path.validator'
 
   simple_sitemap.settings:
-    class: Drupal\simple_sitemap\SimplesitemapSettings
-    public: false
+    class: Drupal\simple_sitemap\Settings
+    public: true
     arguments:
     - '@config.factory'
 
@@ -38,16 +37,16 @@ services:
     public: true
     arguments:
     - '@simple_sitemap.settings'
-    - '@simple_sitemap.manager'
     - '@state'
     - '@simple_sitemap.queue'
     - '@simple_sitemap.logger'
     - '@module_handler'
+    - '@entity_type.manager'
     - '@lock'
 
   simple_sitemap.queue:
-    class: Drupal\simple_sitemap\Queue\SimplesitemapQueue
-    public: false
+    class: Drupal\simple_sitemap\Queue\SimpleSitemapQueue
+    public: true
     arguments:
     - 'simple_sitemap_elements'
     - '@database'
@@ -60,7 +59,7 @@ services:
       - '@router.route_provider'
 
   simple_sitemap.entity_helper:
-    class: Drupal\simple_sitemap\EntityHelper
+    class: Drupal\simple_sitemap\Entity\EntityHelper
     public: true
     arguments:
       - '@entity_type.manager'
@@ -72,8 +71,10 @@ services:
     public: true
     arguments:
       - '@simple_sitemap.generator'
+      - '@simple_sitemap.settings'
       - '@simple_sitemap.entity_helper'
       - '@current_user'
+      - '@class_resolver'
 
   simple_sitemap.logger:
     class: Drupal\simple_sitemap\Logger
@@ -83,19 +84,15 @@ services:
       - '@messenger'
       - '@current_user'
 
-  simple_sitemap.path_processor.variant.in:
-    class: Drupal\simple_sitemap\PathProcessor\PathProcessorSitemapVariantIn
+  simple_sitemap.path_processor:
+    class: Drupal\simple_sitemap\PathProcessor\SitemapPathProcessor
     tags:
       - { name: path_processor_inbound, priority: 300 }
-
-  simple_sitemap.path_processor.variant.out:
-    class: Drupal\simple_sitemap\PathProcessor\PathProcessorSitemapVariantOut
-    tags:
       - { name: path_processor_outbound, priority: 300 }
 
   logger.channel.simple_sitemap:
     parent: logger.channel_base
-    public: false
+    public: true
     arguments:
       - simple_sitemap
 
@@ -106,7 +103,3 @@ services:
   plugin.manager.simple_sitemap.sitemap_generator:
     class: Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
     parent: default_plugin_manager
-
-  plugin.manager.simple_sitemap.sitemap_type:
-    class: Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeManager
-    parent: default_plugin_manager
diff --git a/web/modules/simple_sitemap/src/Annotation/SitemapGenerator.php b/web/modules/simple_sitemap/src/Annotation/SitemapGenerator.php
index 1b71536f4337fb3dce71947b91664856b22dd132..645b3431632511c0fc5980a1524008e4e68fc7b3 100644
--- a/web/modules/simple_sitemap/src/Annotation/SitemapGenerator.php
+++ b/web/modules/simple_sitemap/src/Annotation/SitemapGenerator.php
@@ -7,8 +7,6 @@
 /**
  * Defines a SitemapGenerator item annotation object.
  *
- * @package Drupal\simple_sitemap\Annotation
- *
  * @see \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
  * @see plugin_api
  *
@@ -26,18 +24,18 @@ class SitemapGenerator extends Plugin {
   /**
    * The human-readable name of the generator.
    *
-   * @ingroup plugin_translatable
-   *
    * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
    */
   public $label;
 
   /**
    * A short description of the generator.
    *
-   * @ingroup plugin_translatable
-   *
    * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
    */
   public $description;
 
@@ -47,4 +45,5 @@ class SitemapGenerator extends Plugin {
    * @var array
    */
   public $settings = [];
+
 }
diff --git a/web/modules/simple_sitemap/src/Annotation/SitemapType.php b/web/modules/simple_sitemap/src/Annotation/SitemapType.php
deleted file mode 100644
index 0d3670e7753a2304759246d6078bb19524bad20b..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Annotation/SitemapType.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Annotation;
-
-use Drupal\Component\Annotation\Plugin;
-
-/**
- * Defines a SitemapType item annotation object.
- *
- * @package Drupal\simple_sitemap\Annotation
- *
- * @see \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeManager
- * @see plugin_api
- *
- * @Annotation
- */
-class SitemapType extends Plugin {
-
-  /**
-   * The sitemap type ID.
-   *
-   * @var string
-   */
-  public $id;
-
-  /**
-   * The human-readable name of the sitemap type.
-   *
-   * @ingroup plugin_translatable
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   */
-  public $label;
-
-  /**
-   * A short description of the sitemap type.
-   *
-   * @ingroup plugin_translatable
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   */
-  public $description;
-
-  /**
-   * The ID of the sitemap generator.
-   *
-   * @var string
-   */
-  public $sitemapGenerator;
-
-  /**
-   * The IDs of the URL generators.
-   *
-   * @var[] string
-   */
-  public $urlGenerators = [];
-}
diff --git a/web/modules/simple_sitemap/src/Annotation/UrlGenerator.php b/web/modules/simple_sitemap/src/Annotation/UrlGenerator.php
index ffd094fb8b746ed021c4d7709547d1f74fe476f2..6ad44a0b034c09d79ee3b8ee70fdd2af10f996b3 100644
--- a/web/modules/simple_sitemap/src/Annotation/UrlGenerator.php
+++ b/web/modules/simple_sitemap/src/Annotation/UrlGenerator.php
@@ -7,8 +7,6 @@
 /**
  * Defines a UrlGenerator item annotation object.
  *
- * @package Drupal\simple_sitemap\Annotation
- *
  * @see \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
  * @see plugin_api
  *
@@ -26,18 +24,18 @@ class UrlGenerator extends Plugin {
   /**
    * The human-readable name of the generator.
    *
-   * @ingroup plugin_translatable
-   *
    * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
    */
   public $label;
 
   /**
    * A short description of the generator.
    *
-   * @ingroup plugin_translatable
-   *
    * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
    */
   public $description;
 
@@ -47,4 +45,5 @@ class UrlGenerator extends Plugin {
    * @var array
    */
   public $settings = [];
+
 }
diff --git a/web/modules/simple_sitemap/src/Commands/SimplesitemapCommands.php b/web/modules/simple_sitemap/src/Commands/SimpleSitemapCommands.php
similarity index 57%
rename from web/modules/simple_sitemap/src/Commands/SimplesitemapCommands.php
rename to web/modules/simple_sitemap/src/Commands/SimpleSitemapCommands.php
index db5b9ee347882452b42c31e2ca887ddcbd450b26..241a343db4b481f93297e698ce876ea35f7eb4fb 100644
--- a/web/modules/simple_sitemap/src/Commands/SimplesitemapCommands.php
+++ b/web/modules/simple_sitemap/src/Commands/SimpleSitemapCommands.php
@@ -2,70 +2,78 @@
 
 namespace Drupal\simple_sitemap\Commands;
 
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
 use Drupal\simple_sitemap\Queue\QueueWorker;
-use Drupal\simple_sitemap\Simplesitemap;
+use Drupal\simple_sitemap\Manager\Generator;
 use Drush\Commands\DrushCommands;
 
 /**
- * Class SimplesitemapCommands
- * @package Drupal\simple_sitemap\Commands
+ * Provides Drush commands for managing sitemaps.
  */
-class SimplesitemapCommands extends DrushCommands {
+class SimpleSitemapCommands extends DrushCommands {
 
   /**
-   * @var \Drupal\simple_sitemap\Simplesitemap
+   * The simple_sitemap.generator service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\Generator
    */
   protected $generator;
 
   /**
    * SimplesitemapCommands constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The simple_sitemap.generator service.
    */
-  public function __construct(Simplesitemap $generator) {
+  public function __construct(Generator $generator) {
     $this->generator = $generator;
+
+    parent::__construct();
   }
 
   /**
-   * Regenerate all XML sitemap variants or continue generation.
+   * Regenerate all sitemaps or continue generation.
    *
    * @command simple-sitemap:generate
    *
    * @usage drush simple-sitemap:generate
-   *   Regenerate all XML sitemap variants or continue generation.
+   *   Regenerate all sitemaps or continue generation.
    *
    * @validate-module-enabled simple_sitemap
    *
    * @aliases ssg, simple-sitemap-generate
    */
-  public function generate() {
-    $this->generator->generateSitemap(QueueWorker::GENERATE_TYPE_DRUSH);
+  public function generate(): void {
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_DRUSH);
   }
 
   /**
-   * Queue all or specific sitemap variants for regeneration.
+   * Queue all or specific sitemaps for regeneration.
+   *
+   * @param array $options
+   *   The command options.
    *
    * @command simple-sitemap:rebuild-queue
    *
    * @option variants
-   *   Queue all or specific sitemap variants for regeneration.
+   *   Queue all or specific sitemaps for regeneration.
    *
    * @usage drush simple-sitemap:rebuild-queue
-   *   Rebuild the sitemap queue for all sitemap variants.
+   *   Rebuild the sitemap queue for all sitemaps.
    * @usage drush simple-sitemap:rebuild-queue --variants=default,test
-   *   Rebuild the sitemap queue queuing only variants 'default' and 'test'.
+   *   Rebuild the sitemap queue queuing only sitemaps 'default' and 'test'.
    *
    * @validate-module-enabled simple_sitemap
    *
    * @aliases ssr, simple-sitemap-rebuild-queue
    *
-   * @param array $options
-   *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
-  public function rebuildQueue(array $options = ['variants' => '']) {
-    $variants = array_keys($this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE));
-    if (strlen($options['variants']) > 0) {
-      $chosen_variants = array_map('trim', array_filter(explode(',', $options['variants'])));
+  public function rebuildQueue(array $options = ['variants' => '']): void {
+    // @todo No need to load all sitemaps here.
+    $variants = array_keys(SimpleSitemap::loadMultiple());
+    if (isset($options['variants']) && (string) $options['variants'] !== '') {
+      $chosen_variants = array_map('trim', array_filter(explode(',', (string) $options['variants'])));
       if (!empty($erroneous_variants = array_diff($chosen_variants, $variants))) {
         $message = 'The following variants do not exist: ' . implode(', ', $erroneous_variants)
           . '. Available variants are: ' . implode(', ', $variants) . '.';
@@ -79,4 +87,5 @@ public function rebuildQueue(array $options = ['variants' => '']) {
 
     $this->logger()->log('notice', 'The following variants have been queued for regeneration: ' . implode(', ', $variants) . '.');
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Controller/SimpleSitemapController.php b/web/modules/simple_sitemap/src/Controller/SimpleSitemapController.php
new file mode 100644
index 0000000000000000000000000000000000000000..418c26c8c3a3b97362c984f965d51fd646cd75ba
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Controller/SimpleSitemapController.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\simple_sitemap\Controller;
+
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableResponse;
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Drupal\simple_sitemap\Manager\Generator;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Controller routines for sitemap routes.
+ */
+class SimpleSitemapController extends ControllerBase {
+
+  /**
+   * The simple_sitemap.generator service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\Generator
+   */
+  protected $generator;
+
+  /**
+   * SimpleSitemapController constructor.
+   *
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The simple_sitemap.generator service.
+   */
+  public function __construct(Generator $generator) {
+    $this->generator = $generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container): SimpleSitemapController {
+    return new static(
+      $container->get('simple_sitemap.generator')
+    );
+  }
+
+  /**
+   * Returns a specific sitemap, its chunk, or its index.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param string|null $variant
+   *   Optional name of sitemap variant.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   Returns an XML response.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   */
+  public function getSitemap(Request $request, ?string $variant = NULL): Response {
+    $variant = $variant ?? $this->generator->getDefaultVariant();
+    $page = $request->query->get('page') ? (int) $request->query->get('page') : NULL;
+    $output = $this->generator->setVariants($variant)->getContent($page);
+    if ($output === NULL) {
+      throw new NotFoundHttpException();
+    }
+
+    $response = new CacheableResponse($output, Response::HTTP_OK, [
+      'Content-type' => 'application/xml; charset=utf-8',
+      'X-Robots-Tag' => 'noindex, follow',
+    ]);
+    $response->getCacheableMetadata()
+      ->addCacheTags(Cache::buildTags('simple_sitemap', (array) $variant))
+      ->addCacheContexts(['url.query_args']);
+    return $response;
+  }
+
+  /**
+   * Returns the XML stylesheet for a sitemap.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   Returns an XSL response.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function getSitemapXsl(string $sitemap_generator): Response {
+    /** @var \Drupal\Component\Plugin\PluginManagerInterface $manager */
+    // @phpcs:ignore DrupalPractice.Objects.GlobalDrupal.GlobalDrupal
+    $manager = \Drupal::service('plugin.manager.simple_sitemap.sitemap_generator');
+    try {
+      $sitemap_generator = $manager->createInstance($sitemap_generator);
+    }
+    catch (PluginNotFoundException $ex) {
+      throw new NotFoundHttpException();
+    }
+
+    /** @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorInterface $sitemap_generator */
+    if (NULL === ($xsl = $sitemap_generator->getXslContent())) {
+      throw new NotFoundHttpException();
+    }
+
+    return new Response($xsl, Response::HTTP_OK, [
+      'Content-type' => 'application/xml; charset=utf-8',
+      'X-Robots-Tag' => 'noindex, nofollow',
+    ]);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Controller/SimplesitemapController.php b/web/modules/simple_sitemap/src/Controller/SimplesitemapController.php
deleted file mode 100644
index 524287f7c744530c51613947820bafdba77521fa..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Controller/SimplesitemapController.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Controller;
-
-use Drupal\Core\Controller\ControllerBase;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-use Drupal\simple_sitemap\Simplesitemap;
-use Symfony\Component\HttpFoundation\Request;
-use Drupal\simple_sitemap\SimplesitemapManager;
-
-/**
- * Class SimplesitemapController
- * @package Drupal\simple_sitemap\Controller
- */
-class SimplesitemapController extends ControllerBase {
-
-  /**
-   * @var \Drupal\simple_sitemap\Simplesitemap
-   */
-  protected $generator;
-
-  /**
-   * SimplesitemapController constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   */
-  public function __construct(Simplesitemap $generator) {
-    $this->generator = $generator;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('simple_sitemap.generator')
-    );
-  }
-
-  /**
-   * Returns the whole sitemap variant, its requested chunk,
-   * or its sitemap index file.
-   * Caches the response in case of expected output, prevents caching otherwise.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *  The request object.
-   *
-   * @param string $variant
-   *  Optional name of sitemap variant.
-   *  @see SimplesitemapManager::getSitemapVariants()
-   *
-   * @throws NotFoundHttpException
-   *
-   * @return \Symfony\Component\HttpFoundation\Response|false
-   *  Returns an XML response.
-   */
-  public function getSitemap(Request $request, $variant = NULL) {
-    $output = $this->generator->setVariants($variant)->getSitemap($request->query->getInt('page'));
-    if (!$output) {
-      throw new NotFoundHttpException();
-    }
-
-    return new Response($output, Response::HTTP_OK, [
-      'Content-type' => 'application/xml; charset=utf-8',
-      'X-Robots-Tag' => 'noindex, follow',
-    ]);
-  }
-
-  /**
-   * Returns the XML stylesheet for the sitemap.
-   *
-   * @return \Symfony\Component\HttpFoundation\Response
-   */
-  public function getSitemapXsl() {
-
-    // Read the XSL content from the file.
-    $module_path = drupal_get_path('module', 'simple_sitemap');
-    $xsl_content = file_get_contents($module_path . '/xsl/simple_sitemap.xsl');
-
-    // Replace custom tokens in the XSL content with appropriate values.
-    $replacements = [
-      '[title]' => $this->t('Sitemap file'),
-      '[generated-by]' => $this->t('Generated by the <a href="@link">@module_name</a> Drupal module.', ['@link' => 'https://www.drupal.org/project/simple_sitemap', '@module_name' => 'Simple XML Sitemap']),
-      '[number-of-sitemaps]' => $this->t('Number of sitemaps in this index'),
-      '[sitemap-url]' => $this->t('Sitemap URL'),
-      '[number-of-urls]' => $this->t('Number of URLs in this sitemap'),
-      '[url-location]' => $this->t('URL location'),
-      '[lastmod]' => $this->t('Last modification date'),
-      '[changefreq]' => $this->t('Change frequency'),
-      '[priority]' => $this->t('Priority'),
-      '[translation-set]' => $this->t('Translation set'),
-      '[images]' => $this->t('Images'),
-      '[image-title]' => $this->t('Title'),
-      '[image-caption]' => $this->t('Caption'),
-      '[jquery]' => base_path() . 'core/assets/vendor/jquery/jquery.min.js',
-      '[jquery-tablesorter]' => base_path() . $module_path . '/xsl/jquery.tablesorter.min.js',
-      '[parser-date-iso8601]' => base_path() . $module_path . '/xsl/parser-date-iso8601.min.js',
-      '[xsl-js]' => base_path() . $module_path . '/xsl/simple_sitemap.xsl.js',
-      '[xsl-css]' => base_path() . $module_path . '/xsl/simple_sitemap.xsl.css',
-    ];
-
-    // Output the XSL content.
-    return new Response(strtr($xsl_content, $replacements), Response::HTTP_OK, [
-      'Content-type' => 'application/xml; charset=utf-8',
-      'X-Robots-Tag' => 'noindex, nofollow',
-    ]);
-  }
-
-}
diff --git a/web/modules/simple_sitemap/src/EntityHelper.php b/web/modules/simple_sitemap/src/Entity/EntityHelper.php
similarity index 53%
rename from web/modules/simple_sitemap/src/EntityHelper.php
rename to web/modules/simple_sitemap/src/Entity/EntityHelper.php
index b1bc875ecfbff47a116af0f1a18445f42257650c..609f834998d0357357e2171e7e575bfa4544eecc 100644
--- a/web/modules/simple_sitemap/src/EntityHelper.php
+++ b/web/modules/simple_sitemap/src/Entity/EntityHelper.php
@@ -1,7 +1,8 @@
 <?php
 
-namespace Drupal\simple_sitemap;
+namespace Drupal\simple_sitemap\Entity;
 
+use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -9,11 +10,10 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Url;
+use Drupal\system\Entity\Menu;
 
 /**
  * Helper class for working with entities.
- *
- * @package Drupal\simple_sitemap
  */
 class EntityHelper {
 
@@ -25,20 +25,35 @@ class EntityHelper {
   protected $entityTypeManager;
 
   /**
+   * The bundle info service.
+   *
    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
    */
   protected $entityTypeBundleInfo;
 
   /**
+   * The configuration factory.
+   *
    * @var \Drupal\Core\Config\ConfigFactoryInterface
    */
   protected $configFactory;
 
+  /**
+   * Static cache of bundle information.
+   *
+   * @var array
+   */
+  protected $bundleInfo = [];
+
   /**
    * EntityHelper constructor.
+   *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
+   *   The bundle info service.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The configuration factory.
    */
   public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, ConfigFactoryInterface $configFactory) {
     $this->entityTypeManager = $entity_type_manager;
@@ -47,82 +62,109 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ent
   }
 
   /**
+   * Gets the bundle info of an entity type.
+   *
    * @param string $entity_type_id
+   *   The entity type ID.
+   *
    * @return array
+   *   An array of bundle information.
    */
-  public function getBundleInfo($entity_type_id) {
-    return $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
+  public function getBundleInfo(string $entity_type_id): array {
+    if (!isset($this->bundleInfo[$entity_type_id])) {
+      $bundle_info = &$this->bundleInfo[$entity_type_id];
+
+      // Menu fix.
+      if ($entity_type_id === 'menu_link_content') {
+        $bundle_info = [];
+
+        foreach (Menu::loadMultiple() as $menu) {
+          $bundle_info[$menu->id()]['label'] = $menu->label();
+        }
+      }
+      else {
+        $bundle_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
+      }
+
+      // Sort bundles by label.
+      uasort($bundle_info, function ($a, $b) {
+        return SortArray::sortByKeyString($a, $b, 'label');
+      });
+    }
+    return $this->bundleInfo[$entity_type_id];
   }
 
   /**
+   * Gets the label for the bundle.
+   *
    * @param string $entity_type_id
+   *   The entity type ID.
    * @param string $bundle_name
-   * @return mixed
+   *   The entity bundle.
+   *
+   * @return string
+   *   The bundle label.
    */
-  public function getBundleLabel($entity_type_id, $bundle_name) {
-    $entity_info = $this->getBundleInfo($entity_type_id);
-
-    return isset($entity_info[$bundle_name]['label'])
-      ? $entity_info[$bundle_name]['label']
-      : $bundle_name; // Menu fix.
+  public function getBundleLabel(string $entity_type_id, string $bundle_name) {
+    return $this->getBundleInfo($entity_type_id)[$bundle_name]['label'] ?? $bundle_name;
   }
 
   /**
-   * Gets an entity's bundle name.
+   * Gets the bundle of the entity.
+   *
+   * Special handling of 'menu_link_content' entities.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to get the bundle name for.
+   *   The entity to get the bundle for.
    *
    * @return string
    *   The bundle of the entity.
    */
-  public function getEntityInstanceBundleName(EntityInterface $entity) {
-    return $entity->getEntityTypeId() === 'menu_link_content'
-      // Menu fix.
-      ? $entity->getMenuName() : $entity->bundle();
+  public function getEntityBundle(EntityInterface $entity): string {
+    return $entity->getEntityTypeId() === 'menu_link_content' && method_exists($entity, 'getMenuName') ? $entity->getMenuName() : $entity->bundle();
   }
 
   /**
-   * Gets the entity type id for a bundle.
+   * Gets the entity type for which the entity provides bundles.
+   *
+   * Special handling of 'menu' entities.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to get an entity type id for a bundle.
+   *   The entity to get the "bundle of" for.
    *
    * @return null|string
-   *   The entity type for a bundle or NULL on failure.
+   *   The entity type for which the entity provides bundles, or NULL if does
+   *   not provide bundles for another entity type.
    */
-  public function getBundleEntityTypeId(EntityInterface $entity) {
-    return $entity->getEntityTypeId() === 'menu'
-      // Menu fix.
-      ? 'menu_link_content' : $entity->getEntityType()->getBundleOf();
+  public function getEntityBundleOf(EntityInterface $entity): ?string {
+    return $entity->getEntityTypeId() === 'menu' ? 'menu_link_content' : $entity->getEntityType()->getBundleOf();
   }
 
   /**
    * Returns objects of entity types that can be indexed.
    *
-   * @return array
+   * @return \Drupal\Core\Entity\ContentEntityTypeInterface[]
    *   Objects of entity types that can be indexed by the sitemap.
    */
-  public function getSupportedEntityTypes() {
-    return array_filter($this->entityTypeManager->getDefinitions(), [$this, 'supports']);
+  public function getSupportedEntityTypes(): array {
+    return array_filter($this->entityTypeManager->getDefinitions(), [
+      $this,
+      'supports',
+    ]);
   }
 
   /**
    * Determines if an entity type is supported or not.
    *
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   *
    * @return bool
-   *   TRUE if entity type supported by Simple Sitemap, FALSE if not.
+   *   TRUE if entity type is supported, FALSE if not.
    */
-  public function supports(EntityTypeInterface $entity_type) {
-    if (!$entity_type instanceof ContentEntityTypeInterface
-      || !method_exists($entity_type, 'getBundleEntityType')
-      || !$entity_type->hasLinkTemplate('canonical')) {
-      return FALSE;
-    }
-
-    return TRUE;
-   }
+  public function supports(EntityTypeInterface $entity_type): bool {
+    return $entity_type instanceof ContentEntityTypeInterface && $entity_type->hasLinkTemplate('canonical');
+  }
 
   /**
    * Checks whether an entity type does not provide bundles.
@@ -133,7 +175,7 @@ public function supports(EntityTypeInterface $entity_type) {
    * @return bool
    *   TRUE if the entity type is atomic and FALSE otherwise.
    */
-  public function entityTypeIsAtomic($entity_type_id) {
+  public function entityTypeIsAtomic(string $entity_type_id): bool {
 
     // Menu fix.
     if ($entity_type_id === 'menu_link_content') {
@@ -143,10 +185,10 @@ public function entityTypeIsAtomic($entity_type_id) {
     $entity_types = $this->entityTypeManager->getDefinitions();
 
     if (!isset($entity_types[$entity_type_id])) {
-      // todo: Throw exception.
+      throw new \InvalidArgumentException("Entity type $entity_type_id does not exist.");
     }
 
-    return empty($entity_types[$entity_type_id]->getBundleEntityType()) ? TRUE : FALSE;
+    return empty($entity_types[$entity_type_id]->getBundleEntityType());
   }
 
   /**
@@ -161,7 +203,7 @@ public function entityTypeIsAtomic($entity_type_id) {
    * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
-  public function getEntityFromUrlObject(Url $url_object) {
+  public function getEntityFromUrlObject(Url $url_object): ?EntityInterface {
     if ($url_object->isRouted()) {
 
       // Fix for the homepage, see
@@ -171,9 +213,11 @@ public function getEntityFromUrlObject(Url $url_object) {
         $url_object = Url::fromUri('internal:' . $uri);
       }
 
-      if (!empty($route_parameters = $url_object->getRouteParameters())
-        && $this->entityTypeManager->getDefinition($entity_type_id = key($route_parameters), FALSE)) {
-          return $this->entityTypeManager->getStorage($entity_type_id)->load($route_parameters[$entity_type_id]);
+      foreach ($url_object->getRouteParameters() as $entity_type_id => $entity_id) {
+        if ($entity_id && $this->entityTypeManager->hasDefinition($entity_type_id)
+          && $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id)) {
+          return $entity;
+        }
       }
     }
 
@@ -194,14 +238,17 @@ public function getEntityFromUrlObject(Url $url_object) {
    * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
-  public function getEntityInstanceIds($entity_type_id, $bundle_name = NULL) {
+  public function getEntityInstanceIds(string $entity_type_id, ?string $bundle_name = NULL): array {
     $sitemap_entity_types = $this->getSupportedEntityTypes();
     if (!isset($sitemap_entity_types[$entity_type_id])) {
       return [];
     }
 
-    $entity_query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
-    if (!$this->entityTypeIsAtomic($entity_type_id) && NULL !== $bundle_name) {
+    $entity_query = $this->entityTypeManager
+      ->getStorage($entity_type_id)
+      ->getQuery()
+      ->accessCheck(TRUE);
+    if ($bundle_name !== NULL && !$this->entityTypeIsAtomic($entity_type_id)) {
       $keys = $sitemap_entity_types[$entity_type_id]->getKeys();
 
       // Menu fix.
@@ -210,7 +257,7 @@ public function getEntityInstanceIds($entity_type_id, $bundle_name = NULL) {
       $entity_query->condition($keys['bundle'], $bundle_name);
     }
 
-    return $entity_query->accessCheck(TRUE)->execute();
+    return $entity_query->execute();
   }
 
 }
diff --git a/web/modules/simple_sitemap/src/Entity/SimpleSitemap.php b/web/modules/simple_sitemap/src/Entity/SimpleSitemap.php
new file mode 100644
index 0000000000000000000000000000000000000000..8b3ed7bb9e404cbc0264c63da99dc2238194ec4e
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Entity/SimpleSitemap.php
@@ -0,0 +1,365 @@
+<?php
+
+namespace Drupal\simple_sitemap\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap\Exception\SitemapNotExistsException;
+
+/**
+ * Defines the simple_sitemap entity.
+ *
+ * @ConfigEntityType(
+ *   id = "simple_sitemap",
+ *   label = @Translation("Sitemap"),
+ *   label_collection = @Translation("Sitemaps"),
+ *   label_singular = @Translation("sitemap"),
+ *   label_plural = @Translation("sitemaps"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count sitemap",
+ *     plural = "@count sitemaps",
+ *   ),
+ *   handlers = {
+ *     "storage" = "\Drupal\simple_sitemap\Entity\SimpleSitemapStorage",
+ *     "list_builder" = "\Drupal\simple_sitemap\SimpleSitemapListBuilder",
+ *     "form" = {
+ *       "default" = "\Drupal\simple_sitemap\Form\SimpleSitemapEntityForm",
+ *       "add" = "\Drupal\simple_sitemap\Form\SimpleSitemapEntityForm",
+ *       "edit" = "\Drupal\simple_sitemap\Form\SimpleSitemapEntityForm",
+ *       "delete" = "\Drupal\Core\Entity\EntityDeleteForm"
+ *     },
+ *   },
+ *   config_prefix = "sitemap",
+ *   admin_permission = "administer sitemap settings",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "label" = "label",
+ *     "weight" = "weight",
+ *     "status" = "status"
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "description",
+ *     "type",
+ *     "weight",
+ *     "status"
+ *   },
+ *   links = {
+ *     "add-form" = "/admin/config/search/simplesitemap/variants/add",
+ *     "edit-form" = "/admin/config/search/simplesitemap/variants/{simple_sitemap}",
+ *     "delete-form" = "/admin/config/search/simplesitemap/variants/{simple_sitemap}/delete",
+ *     "collection" = "/admin/config/search/simplesitemap",
+ *   },
+ * )
+ */
+class SimpleSitemap extends ConfigEntityBase implements SimpleSitemapInterface {
+
+  public const SITEMAP_UNPUBLISHED = 0;
+  public const SITEMAP_PUBLISHED = 1;
+  public const SITEMAP_PUBLISHED_GENERATING = 2;
+
+  public const FETCH_BY_STATUS_ALL = NULL;
+  public const FETCH_BY_STATUS_UNPUBLISHED = 0;
+  public const FETCH_BY_STATUS_PUBLISHED = 1;
+
+  /**
+   * The fetch status.
+   *
+   * @var int
+   */
+  protected $fetchByStatus;
+
+  /**
+   * The sitemap type entity.
+   *
+   * @var \Drupal\simple_sitemap\Entity\SimpleSitemapTypeInterface
+   */
+  protected $sitemapType;
+
+  /**
+   * Implements the magic __toString() method.
+   */
+  public function __toString(): string {
+    return $this->toString();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    parent::calculateDependencies();
+    $this->addDependency('config', $this->getType()->getConfigDependencyName());
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fromPublished(): SimpleSitemapInterface {
+    $this->fetchByStatus = self::FETCH_BY_STATUS_PUBLISHED;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fromUnpublished(): SimpleSitemapInterface {
+    $this->fetchByStatus = self::FETCH_BY_STATUS_UNPUBLISHED;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fromPublishedAndUnpublished(): SimpleSitemapInterface {
+    $this->fetchByStatus = self::FETCH_BY_STATUS_ALL;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType(): SimpleSitemapTypeInterface {
+    if ($this->sitemapType === NULL) {
+      $this->sitemapType = $this->entityTypeManager()->getStorage('simple_sitemap_type')->load($this->get('type'));
+    }
+
+    return $this->sitemapType;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toString(?int $delta = NULL): string {
+    $status = $this->fetchByStatus ?? self::FETCH_BY_STATUS_PUBLISHED;
+    $storage = $this->entityTypeManager()->getStorage('simple_sitemap');
+
+    if ($delta) {
+      try {
+        return $storage->getChunk($this, $status, $delta);
+      }
+      catch (SitemapNotExistsException $e) {
+      }
+    }
+
+    if ($storage->hasIndex($this, $status)) {
+      return $storage->getIndex($this, $status);
+    }
+
+    try {
+      return $storage->getChunk($this, $status);
+    }
+    catch (SitemapNotExistsException $e) {
+      return '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function publish(): SimpleSitemapInterface {
+    $this->entityTypeManager()->getStorage('simple_sitemap')->publish($this);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteContent(): SimpleSitemapInterface {
+    $this->entityTypeManager()->getStorage('simple_sitemap')->deleteContent($this);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addChunk(array $links): SimpleSitemapInterface {
+    // @todo Automatically set variant.
+    $xml = $this->getType()->getSitemapGenerator()->setSitemap($this)->getChunkContent($links);
+    $this->entityTypeManager()->getStorage('simple_sitemap')->addChunk($this, $xml, count($links));
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function generateIndex(): SimpleSitemapInterface {
+    if ($this->isIndexable()) {
+      // @todo Automatically set variant.
+      $xml = $this->getType()->getSitemapGenerator()->setSitemap($this)->getIndexContent();
+      $this->entityTypeManager()->getStorage('simple_sitemap')->generateIndex($this, $xml);
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChunkCount(): int {
+    return $this->entityTypeManager()->getStorage('simple_sitemap')->getChunkCount($this, $this->fetchByStatus);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasIndex(): bool {
+    return $this->entityTypeManager()->getStorage('simple_sitemap')->hasIndex($this, $this->fetchByStatus);
+  }
+
+  /**
+   * Returns whether the sitemap needs a chunk index.
+   *
+   * This is not about indexing sitemap variants, it's about creating an index
+   * of all sitemap chunks. A sitemap needs a chunk index if it consists of more
+   * than one (unpublished) chunk.
+   *
+   * @return bool
+   *   TRUE if the sitemap is indexable and FALSE otherwise.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  protected function isIndexable(): bool {
+    try {
+      $this->entityTypeManager()->getStorage('simple_sitemap')->getChunk($this, self::FETCH_BY_STATUS_UNPUBLISHED, 2);
+      return TRUE;
+    }
+    catch (SitemapNotExistsException $e) {
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIndex(): string {
+    return $this->entityTypeManager()->getStorage('simple_sitemap')->getIndex($this, $this->fetchByStatus);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEnabled(): bool {
+    return parent::status();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function status(): bool {
+    return $this->isEnabled() && $this->contentStatus();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contentStatus(): int {
+    return $this->entityTypeManager()->getStorage('simple_sitemap')->status($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreated(): ?string {
+    return $this->entityTypeManager()->getStorage('simple_sitemap')->getCreated($this, $this->fetchByStatus);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkCount(): int {
+    return $this->entityTypeManager()->getStorage('simple_sitemap')->getLinkCount($this, $this->fetchByStatus);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toUrl($rel = 'canonical', array $options = []) {
+    if ($rel !== 'canonical') {
+      return parent::toUrl($rel, $options);
+    }
+
+    $parameters = isset($options['delta']) ? ['page' => $options['delta']] : [];
+    unset($options['delta']);
+
+    if (empty($options['base_url'])) {
+      /** @var \Drupal\simple_sitemap\Settings $settings */
+      $settings = \Drupal::service('simple_sitemap.settings');
+      $options['base_url'] = $settings->get('base_url') ?: $GLOBALS['base_url'];
+    }
+
+    $options['language'] = $this->languageManager()->getLanguage(LanguageInterface::LANGCODE_NOT_APPLICABLE);
+
+    return $this->isDefault()
+      ? Url::fromRoute(
+        'simple_sitemap.sitemap_default',
+        $parameters,
+        $options)
+      : Url::fromRoute(
+        'simple_sitemap.sitemap_variant',
+        $parameters + ['variant' => $this->id()],
+        $options);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDefault(): bool {
+    /** @var \Drupal\simple_sitemap\Settings $settings */
+    $settings = \Drupal::service('simple_sitemap.settings');
+    return $this->id() === $settings->get('default_variant');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isMultilingual(): bool {
+    if (!\Drupal::moduleHandler()->moduleExists('language')) {
+      return FALSE;
+    }
+
+    $url_negotiation_method_enabled = FALSE;
+    /** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
+    $language_negotiator = \Drupal::service('language_negotiator');
+    foreach ($language_negotiator->getNegotiationMethods(LanguageInterface::TYPE_URL) as $method) {
+      if ($language_negotiator->isNegotiationMethodEnabled($method['id'])) {
+        $url_negotiation_method_enabled = TRUE;
+        break;
+      }
+    }
+
+    /** @var \Drupal\simple_sitemap\Settings $settings */
+    $settings = \Drupal::service('simple_sitemap.settings');
+    $has_multiple_indexable_languages = count(
+        array_diff_key($this->languageManager()->getLanguages(),
+          $settings->get('excluded_languages', []))
+      ) > 1;
+
+    return $url_negotiation_method_enabled && $has_multiple_indexable_languages;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property_name, $value) {
+    if ($property_name === 'type') {
+      $this->sitemapType = NULL;
+    }
+
+    return parent::set($property_name, $value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function purgeContent(?array $variants = NULL, ?int $status = self::FETCH_BY_STATUS_ALL) {
+    \Drupal::entityTypeManager()->getStorage('simple_sitemap')->purgeContent($variants, $status);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Entity/SimpleSitemapInterface.php b/web/modules/simple_sitemap/src/Entity/SimpleSitemapInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0801558e0aaf85ac732b7058b9c3dcf7df31f621
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Entity/SimpleSitemapInterface.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace Drupal\simple_sitemap\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+
+/**
+ * Provides an interface defining a sitemap entity.
+ */
+interface SimpleSitemapInterface extends ConfigEntityInterface {
+
+  /**
+   * Sets the fetch status to published.
+   *
+   * @return $this
+   */
+  public function fromPublished(): SimpleSitemapInterface;
+
+  /**
+   * Sets the fetch status to unpublished.
+   *
+   * @return $this
+   */
+  public function fromUnpublished(): SimpleSitemapInterface;
+
+  /**
+   * Sets the fetch status to published and unpublished.
+   *
+   * @return $this
+   */
+  public function fromPublishedAndUnpublished(): SimpleSitemapInterface;
+
+  /**
+   * Gets the sitemap type.
+   *
+   * @return \Drupal\simple_sitemap\Entity\SimpleSitemapTypeInterface
+   *   The sitemap type entity.
+   */
+  public function getType(): SimpleSitemapTypeInterface;
+
+  /**
+   * Retrieves the sitemap content as string.
+   *
+   * @param int|null $delta
+   *   Optional delta of the chunk.
+   *
+   * @return string
+   *   The sitemap content.
+   */
+  public function toString(?int $delta = NULL): string;
+
+  /**
+   * Publishes the sitemap's content.
+   *
+   * @return $this
+   */
+  public function publish(): SimpleSitemapInterface;
+
+  /**
+   * Removes the sitemap's content.
+   *
+   * @return $this
+   */
+  public function deleteContent(): SimpleSitemapInterface;
+
+  /**
+   * Adds a new content chunk to the sitemap.
+   *
+   * @param array $links
+   *   An array of links for this chunk.
+   *
+   * @return $this
+   */
+  public function addChunk(array $links): SimpleSitemapInterface;
+
+  /**
+   * Generates the index for this sitemap's content chunks.
+   *
+   * @return $this
+   */
+  public function generateIndex(): SimpleSitemapInterface;
+
+  /**
+   * Returns the number of all sitemap content chunks.
+   *
+   * @return int
+   *   Number of chunks.
+   */
+  public function getChunkCount(): int;
+
+  /**
+   * Determines whether the sitemap has a content index.
+   *
+   * @return bool
+   *   TRUE if the sitemap has an index, FALSE otherwise.
+   */
+  public function hasIndex(): bool;
+
+  /**
+   * Retrieves the sitemap's content index.
+   *
+   * @return string
+   *   The sitemap index content.
+   */
+  public function getIndex(): string;
+
+  /**
+   * Returns the enabled status of the sitemap.
+   *
+   * This is different to ::status(), which returns TRUE
+   * only if the sitemap is enabled AND its content published.
+   *
+   * @return bool
+   *   The enabled status of the sitemap.
+   */
+  public function isEnabled(): bool;
+
+  /**
+   * Returns the status of this sitemap's content.
+   *
+   * @return int
+   *   The content status of this sitemap.
+   */
+  public function contentStatus(): int;
+
+  /**
+   * Returns the timestamp of the sitemap chunk generation.
+   *
+   * @return string|null
+   *   Timestamp of sitemap chunk generation.
+   */
+  public function getCreated(): ?string;
+
+  /**
+   * Returns the number of links indexed in the sitemap content.
+   *
+   * @return int
+   *   Number of links.
+   */
+  public function getLinkCount(): int;
+
+  /**
+   * Determines whether this sitemap is set to be the default one.
+   *
+   * @return bool
+   *   Whether the sitemap is the default sitemap.
+   */
+  public function isDefault(): bool;
+
+  /**
+   * Determines if the sitemap is to be a multilingual based on several factors.
+   *
+   * A hreflang/multilingual sitemap is only wanted if there are indexable
+   * languages available and if there is a language negotiation method enabled
+   * that is based on URL discovery. Any other language negotiation methods
+   * should be irrelevant, as a sitemap can only use URLs to guide to the
+   * correct language.
+   *
+   * @see https://www.drupal.org/project/simple_sitemap/issues/3154570#comment-13730522
+   *
+   * @return bool
+   *   TRUE if the sitemap is multilingual and FALSE otherwise.
+   */
+  public function isMultilingual(): bool;
+
+  /**
+   * Removes the content from all or specified sitemaps.
+   *
+   * A sitemap entity can exist without the sitemap (XML) content which lives
+   * in the DB. This purges the sitemap content.
+   *
+   * @param array|null $variants
+   *   An array of sitemap IDs, or NULL for all sitemaps.
+   * @param int|null $status
+   *   Purge by sitemap status.
+   */
+  public static function purgeContent(?array $variants = NULL, ?int $status = SimpleSitemap::FETCH_BY_STATUS_ALL);
+
+}
diff --git a/web/modules/simple_sitemap/src/Entity/SimpleSitemapStorage.php b/web/modules/simple_sitemap/src/Entity/SimpleSitemapStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..bfe40888f3452f8350867cc4612071c6115a17f4
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Entity/SimpleSitemapStorage.php
@@ -0,0 +1,591 @@
+<?php
+
+namespace Drupal\simple_sitemap\Entity;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\simple_sitemap\Exception\SitemapNotExistsException;
+use Drupal\simple_sitemap\Settings;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Storage handler for sitemap configuration entities.
+ */
+class SimpleSitemapStorage extends ConfigEntityStorage {
+
+  public const SITEMAP_INDEX_DELTA = 0;
+  public const SITEMAP_CHUNK_FIRST_DELTA = 1;
+
+  protected const SITEMAP_PUBLISHED = 1;
+  protected const SITEMAP_UNPUBLISHED = 0;
+
+  /**
+   * The database connection to be used.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
+   */
+  protected $settings;
+
+  /**
+   * SimpleSitemapStorage constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
+   *   The UUID service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
+   *   The memory cache backend.
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection to be used.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   */
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, Connection $database, TimeInterface $time, EntityTypeManagerInterface $entity_type_manager, Settings $settings) {
+    parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $memory_cache);
+    $this->database = $database;
+    $this->time = $time;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->settings = $settings;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('config.factory'),
+      $container->get('uuid'),
+      $container->get('language_manager'),
+      $container->get('entity.memory_cache'),
+      $container->get('database'),
+      $container->get('datetime.time'),
+      $container->get('entity_type.manager'),
+      $container->get('simple_sitemap.settings')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Improve performance of this method.
+   */
+  protected function doDelete($entities) {
+    $default_variant = $this->settings->get('default_variant');
+
+    /** @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface[] $entities */
+    foreach ($entities as $entity) {
+
+      // Remove sitemap content.
+      $this->deleteContent($entity);
+
+      // Unset default variant setting if necessary.
+      if ($default_variant === $entity->id()) {
+        $this->settings->save('default_variant', NULL);
+      }
+
+      // Remove bundle settings.
+      foreach ($this->configFactory->listAll("simple_sitemap.bundle_settings.{$entity->id()}.") as $config_name) {
+        $this->configFactory->getEditable($config_name)->delete();
+      }
+
+      // Remove custom links.
+      foreach ($this->configFactory->listAll("simple_sitemap.custom_links.{$entity->id()}") as $config_name) {
+        $this->configFactory->getEditable($config_name)->delete();
+      }
+
+      // Remove bundle settings entity overrides.
+      $this->database->delete('simple_sitemap_entity_overrides')->condition('type', $entity->id())->execute();
+    }
+
+    parent::doDelete($entities);
+  }
+
+  /**
+   * Loads all sitemaps, sorted by their weight.
+   *
+   * {@inheritdoc}
+   */
+  protected function doLoadMultiple(?array $ids = NULL): array {
+    $sitemaps = parent::doLoadMultiple($ids);
+    uasort($sitemaps, [SimpleSitemap::class, 'sort']);
+
+    return $sitemaps;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadByProperties(array $values = []): array {
+    $sitemaps = parent::loadByProperties($values);
+    uasort($sitemaps, [SimpleSitemap::class, 'sort']);
+
+    return $sitemaps;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function create(array $values = []) {
+    if (isset($values['id']) && ($sitemap = SimpleSitemap::load($values['id'])) !== NULL) {
+      foreach (['type', 'label', 'weight'] as $property) {
+        if (isset($values[$property])) {
+          $sitemap->set('type', $values[$property]);
+        }
+      }
+      return $sitemap;
+    }
+
+    return parent::create($values);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSave($id, EntityInterface $entity) {
+    /** @var SimpleSitemapInterface $entity */
+    if (preg_match('/[^a-z0-9-_]+/', $id)) {
+      throw new \InvalidArgumentException('The sitemap ID can only contain lowercase letters, numbers, dashes and underscores.');
+    }
+
+    if ($entity->get('type') === NULL || $entity->get('type') === '') {
+      throw new \InvalidArgumentException('The sitemap must define its sitemap type information.');
+    }
+
+    if ($this->entityTypeManager->getStorage('simple_sitemap_type')->load($entity->get('type')) === NULL) {
+      throw new \InvalidArgumentException("Sitemap type {$entity->get('type')} does not exist.");
+    }
+
+    if ($entity->label() === NULL || $entity->label() === '') {
+      $entity->set('label', $id);
+    }
+
+    if ($entity->get('weight') === NULL || $entity->get('weight') === '') {
+      $entity->set('weight', 0);
+    }
+
+    // If disabling the entity, delete sitemap content if any.
+    if (!$entity->isEnabled() && $entity->fromPublishedAndUnpublished()->getChunkCount()) {
+      $this->deleteContent($entity);
+    }
+    // We need the else since we don't want to thrash cache invalidation and
+    // deleting content already invalidates cache.
+    else {
+      $this->invalidateCache([$entity->id()]);
+    }
+
+    return parent::doSave($id, $entity);
+  }
+
+  /**
+   * Retrieves the chunk data for the specified sitemap.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $entity
+   *   The sitemap entity.
+   *
+   * @return array
+   *   The chunk data.
+   *
+   * @todo Costs too much.
+   */
+  protected function getChunkData(SimpleSitemapInterface $entity) {
+    return $this->database->select('simple_sitemap', 's')
+      ->fields('s', [
+        'id',
+        'type',
+        'delta',
+        'sitemap_created',
+        'status',
+        'link_count',
+      ])
+      ->condition('s.type', $entity->id())
+      ->execute()
+      ->fetchAllAssoc('id');
+  }
+
+  /**
+   * Publishes the specified sitemap.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity to publish.
+   */
+  public function publish(SimpleSitemap $entity): void {
+    $unpublished_chunk = $this->database->query('SELECT MAX(id) FROM {simple_sitemap} WHERE type = :type AND status = :status', [
+      ':type' => $entity->id(),
+      ':status' => self::SITEMAP_UNPUBLISHED,
+    ])->fetchField();
+
+    // Only allow publishing a sitemap variant if there is an unpublished
+    // sitemap variant, as publishing involves deleting the currently published
+    // variant.
+    if (FALSE !== $unpublished_chunk) {
+      $this->database->delete('simple_sitemap')->condition('type', $entity->id())->condition('status', self::SITEMAP_PUBLISHED)->execute();
+      $this->database->query('UPDATE {simple_sitemap} SET status = :status WHERE type = :type', [
+        ':type' => $entity->id(),
+        ':status' => self::SITEMAP_PUBLISHED,
+      ]);
+      $this->invalidateCache([$entity->id()]);
+    }
+  }
+
+  /**
+   * Removes the content of the specified sitemap.
+   *
+   * A sitemap entity can exist without the sitemap (XML) content which lives
+   * in the DB. This purges the sitemap content.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity to process.
+   */
+  public function deleteContent(SimpleSitemap $entity): void {
+    $this->purgeContent([$entity->id()]);
+  }
+
+  /**
+   * Adds a new content chunk to the specified sitemap.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $entity
+   *   The sitemap entity to process.
+   * @param string $content
+   *   The sitemap chunk content.
+   * @param int $link_count
+   *   Number of links.
+   *
+   * @throws \Exception
+   */
+  public function addChunk(SimpleSitemapInterface $entity, string $content, $link_count): void {
+    $highest_delta = $this->database->query('SELECT MAX(delta) FROM {simple_sitemap} WHERE type = :type AND status = :status', [
+      ':type' => $entity->id(),
+      ':status' => self::SITEMAP_UNPUBLISHED,
+    ])
+      ->fetchField();
+
+    $this->database->insert('simple_sitemap')->fields([
+      'delta' => NULL === $highest_delta ? self::SITEMAP_CHUNK_FIRST_DELTA : $highest_delta + 1,
+      'type' => $entity->id(),
+      'sitemap_string' => $content,
+      'sitemap_created' => $this->time->getRequestTime(),
+      'status' => 0,
+      'link_count' => $link_count,
+    ])->execute();
+    $this->invalidateCache([$entity->id()]);
+  }
+
+  /**
+   * Generates the chunk index of the specified sitemap's content chunks.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $entity
+   *   The sitemap entity to process.
+   * @param string $content
+   *   The sitemap index content.
+   *
+   * @throws \Exception
+   */
+  public function generateIndex(SimpleSitemapInterface $entity, string $content): void {
+    $this->database->merge('simple_sitemap')
+      ->keys([
+        'delta' => self::SITEMAP_INDEX_DELTA,
+        'type' => $entity->id(),
+        'status' => 0,
+      ])
+      ->insertFields([
+        'delta' => self::SITEMAP_INDEX_DELTA,
+        'type' => $entity->id(),
+        'sitemap_string' => $content,
+        'sitemap_created' => $this->time->getRequestTime(),
+        'status' => 0,
+      ])
+      ->updateFields([
+        'sitemap_string' => $content,
+        'sitemap_created' => $this->time->getRequestTime(),
+      ])
+      ->execute();
+    $this->invalidateCache([$entity->id()]);
+  }
+
+  /**
+   * Returns the number of all content chunks of the specified sitemap.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param int|null $status
+   *   Fetch by sitemap status.
+   *
+   * @return int
+   *   Number of chunks.
+   */
+  public function getChunkCount(SimpleSitemap $entity, ?int $status = SimpleSitemap::FETCH_BY_STATUS_ALL): int {
+    $query = $this->database->select('simple_sitemap', 's')
+      ->condition('s.type', $entity->id())
+      ->condition('s.delta', self::SITEMAP_INDEX_DELTA, '<>');
+
+    if ($status !== SimpleSitemap::FETCH_BY_STATUS_ALL) {
+      $query->condition('s.status', $status);
+    }
+
+    return (int) $query->countQuery()->execute()->fetchField();
+  }
+
+  /**
+   * Retrieves the content of a specified sitemap's chunk.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param bool|null $status
+   *   Fetch by sitemap status.
+   * @param int $delta
+   *   Delta of the chunk.
+   *
+   * @return string
+   *   The sitemap chunk content.
+   *
+   * @todo Fix the duplicate query.
+   */
+  public function getChunk(SimpleSitemap $entity, ?bool $status, int $delta = SimpleSitemapStorage::SITEMAP_CHUNK_FIRST_DELTA): string {
+    if ($delta === self::SITEMAP_INDEX_DELTA) {
+      throw new SitemapNotExistsException('The sitemap chunk delta cannot be ' . self::SITEMAP_INDEX_DELTA . '.');
+    }
+
+    return $this->getSitemapString($entity, $this->getIdByDelta($entity, $delta, $status), $status);
+  }
+
+  /**
+   * Determines whether the specified sitemap has a chunk index.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity to check.
+   * @param bool $status
+   *   Fetch by sitemap status.
+   *
+   * @return bool
+   *   TRUE if the sitemap has an index, FALSE otherwise.
+   */
+  public function hasIndex(SimpleSitemap $entity, bool $status): bool {
+    try {
+      $this->getIdByDelta($entity, self::SITEMAP_INDEX_DELTA, $status);
+      return TRUE;
+    }
+    catch (SitemapNotExistsException $e) {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Gets the sitemap chunk index content.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param bool|null $status
+   *   Fetch by sitemap status.
+   *
+   * @return string
+   *   The sitemap index content.
+   *
+   * @todo Fix the duplicate query.
+   */
+  public function getIndex(SimpleSitemap $entity, ?bool $status): string {
+    return $this->getSitemapString($entity, $this->getIdByDelta($entity, self::SITEMAP_INDEX_DELTA, $status), $status);
+  }
+
+  /**
+   * Returns the sitemap chunk ID by delta.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param int $delta
+   *   Delta of the chunk.
+   * @param bool $status
+   *   Fetch by sitemap status.
+   *
+   * @return int
+   *   The sitemap chunk ID.
+   */
+  protected function getIdByDelta(SimpleSitemap $entity, int $delta, bool $status): int {
+    foreach ($this->getChunkData($entity) as $chunk) {
+      if ($chunk->delta == $delta && $chunk->status == $status) {
+        return $chunk->id;
+      }
+    }
+
+    throw new SitemapNotExistsException();
+  }
+
+  /**
+   * Retrieves the sitemap chunk content.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param int $id
+   *   The sitemap chunk ID.
+   * @param bool|null $status
+   *   Fetch by sitemap status.
+   *
+   * @return string
+   *   The sitemap chunk content.
+   */
+  protected function getSitemapString(SimpleSitemap $entity, int $id, ?bool $status): string {
+    $chunk_data = $this->getChunkData($entity);
+    if (!isset($chunk_data[$id])) {
+      throw new SitemapNotExistsException();
+    }
+
+    if (empty($chunk_data[$id]->sitemap_string)) {
+      $query = $this->database->select('simple_sitemap', 's')
+        ->fields('s', ['sitemap_string'])
+        ->condition('status', $status)
+        ->condition('id', $id);
+
+      $chunk_data[$id]->sitemap_string = $query->execute()->fetchField();
+    }
+
+    return $chunk_data[$id]->sitemap_string;
+  }
+
+  /**
+   * Returns the status of the specified sitemap.
+   *
+   * The sitemap can be unpublished (0), published (1), or published and in
+   * regeneration (2).
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   *
+   * @return int
+   *   The sitemap status.
+   */
+  public function status(SimpleSitemap $entity): int {
+    foreach ($this->getChunkData($entity) as $chunk) {
+      $status[$chunk->status] = $chunk->status;
+    }
+
+    if (!isset($status)) {
+      return SimpleSitemap::SITEMAP_UNPUBLISHED;
+    }
+
+    if (count($status) === 1) {
+      return (int) reset($status) === self::SITEMAP_UNPUBLISHED
+        ? SimpleSitemap::SITEMAP_UNPUBLISHED
+        : SimpleSitemap::SITEMAP_PUBLISHED;
+    }
+
+    return SimpleSitemap::SITEMAP_PUBLISHED_GENERATING;
+  }
+
+  /**
+   * Returns the timestamp of the specified sitemap's chunk generation.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param int|null $status
+   *   Fetch by sitemap status.
+   *
+   * @return string|null
+   *   Timestamp of sitemap chunk generation.
+   */
+  public function getCreated(SimpleSitemap $entity, ?int $status = SimpleSitemap::FETCH_BY_STATUS_ALL): ?string {
+    foreach ($this->getChunkData($entity) as $chunk) {
+      if ($status === SimpleSitemap::FETCH_BY_STATUS_ALL || $chunk->status == $status) {
+        return $chunk->sitemap_created;
+      }
+    }
+
+    return NULL;
+  }
+
+  /**
+   * Returns the number of links indexed in the specified sitemap's content.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemap $entity
+   *   The sitemap entity.
+   * @param int|null $status
+   *   Fetch by sitemap status.
+   *
+   * @return int
+   *   Number of links.
+   */
+  public function getLinkCount(SimpleSitemap $entity, ?int $status = SimpleSitemap::FETCH_BY_STATUS_ALL): int {
+    $count = 0;
+    foreach ($this->getChunkData($entity) as $chunk) {
+      if ($chunk->delta != self::SITEMAP_INDEX_DELTA
+        && ($status === SimpleSitemap::FETCH_BY_STATUS_ALL || $chunk->status == $status)) {
+        $count += (int) $chunk->link_count;
+      }
+    }
+
+    return $count;
+  }
+
+  /**
+   * Removes the content from all or specified sitemaps.
+   *
+   * A sitemap entity can exist without the sitemap (XML) content which lives
+   * in the DB. This purges the sitemap content.
+   *
+   * @param array|null $variants
+   *   An array of sitemap IDs, or NULL for all sitemaps.
+   * @param int|null $status
+   *   Purge by sitemap status.
+   */
+  public function purgeContent(?array $variants = NULL, ?int $status = SimpleSitemap::FETCH_BY_STATUS_ALL): void {
+    $query = $this->database->delete('simple_sitemap');
+    if ($status !== SimpleSitemap::FETCH_BY_STATUS_ALL) {
+      $query->condition('status', $status);
+    }
+    if ($variants !== NULL) {
+      $query->condition('type', $variants, 'IN');
+    }
+    $query->execute();
+    $this->invalidateCache($variants);
+  }
+
+  /**
+   * Invalidates cache for all or specified sitemaps.
+   *
+   * @param array|null $variants
+   *   An array of sitemap IDs, or NULL for all sitemaps.
+   */
+  public function invalidateCache(?array $variants = NULL): void {
+    $variants = $variants ?? array_keys(SimpleSitemap::loadMultiple());
+    $tags = Cache::buildTags('simple_sitemap', $variants);
+    Cache::invalidateTags($tags);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Entity/SimpleSitemapType.php b/web/modules/simple_sitemap/src/Entity/SimpleSitemapType.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a086e27c98076b80c9481420f54a1293c6b67d3
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Entity/SimpleSitemapType.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Drupal\simple_sitemap\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorInterface;
+
+/**
+ * Defines the simple_sitemap entity.
+ *
+ * @ConfigEntityType(
+ *   id = "simple_sitemap_type",
+ *   label = @Translation("Simple XML sitemap type"),
+ *   label_collection = @Translation("Sitemap types"),
+ *   label_singular = @Translation("sitemap type"),
+ *   label_plural = @Translation("sitemap types"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count sitemap type",
+ *     plural = "@count sitemap types",
+ *   ),
+ *   handlers = {
+ *     "storage" = "\Drupal\simple_sitemap\Entity\SimpleSitemapTypeStorage",
+ *     "list_builder" = "\Drupal\simple_sitemap\SimpleSitemapTypeListBuilder",
+ *     "form" = {
+ *       "default" = "\Drupal\simple_sitemap\Form\SimpleSitemapTypeEntityForm",
+ *       "add" = "\Drupal\simple_sitemap\Form\SimpleSitemapTypeEntityForm",
+ *       "edit" = "\Drupal\simple_sitemap\Form\SimpleSitemapTypeEntityForm",
+ *       "delete" = "\Drupal\Core\Entity\EntityDeleteForm"
+ *     },
+ *   },
+ *   config_prefix = "type",
+ *   admin_permission = "administer sitemap settings",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "label" = "label",
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "description",
+ *     "sitemap_generator",
+ *     "url_generators",
+ *   },
+ *   links = {
+ *     "add-form" = "/admin/config/search/simplesitemap/types/add",
+ *     "edit-form" = "/admin/config/search/simplesitemap/types/{simple_sitemap_type}",
+ *     "delete-form" = "/admin/config/search/simplesitemap/types/{simple_sitemap_type}/delete",
+ *     "collection" = "/admin/config/search/simplesitemap/types",
+ *   },
+ * )
+ */
+class SimpleSitemapType extends ConfigEntityBase implements SimpleSitemapTypeInterface {
+
+  /**
+   * The sitemap generator.
+   *
+   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorInterface
+   */
+  protected $sitemapGenerator;
+
+  /**
+   * The URL generators.
+   *
+   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorInterface[]
+   */
+  protected $urlGenerators;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSitemapGenerator(): SitemapGeneratorInterface {
+    if ($this->sitemapGenerator === NULL) {
+      /** @var \Drupal\Component\Plugin\PluginManagerInterface $manager */
+      $manager = \Drupal::service('plugin.manager.simple_sitemap.sitemap_generator');
+      $this->sitemapGenerator = $manager
+        ->createInstance($this->get('sitemap_generator'));
+    }
+
+    return $this->sitemapGenerator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrlGenerators(): array {
+    if ($this->urlGenerators === NULL) {
+      $this->urlGenerators = [];
+      /** @var \Drupal\Component\Plugin\PluginManagerInterface $manager */
+      $manager = \Drupal::service('plugin.manager.simple_sitemap.url_generator');
+      foreach ($this->get('url_generators') as $generator_id) {
+        $this->urlGenerators[$generator_id] = $manager->createInstance($generator_id);
+      }
+    }
+
+    return $this->urlGenerators;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasUrlGenerator(string $generator_id): bool {
+    return in_array($generator_id, $this->get('url_generators'), TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property_name, $value) {
+    if ($property_name === 'sitemap_generator') {
+      $this->sitemapGenerator = NULL;
+    }
+    elseif ($property_name === 'url_generators') {
+      $this->urlGenerators = NULL;
+    }
+
+    return parent::set($property_name, $value);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Entity/SimpleSitemapTypeInterface.php b/web/modules/simple_sitemap/src/Entity/SimpleSitemapTypeInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c142055d9898f97c3dcb0421b75b83a23112d69f
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Entity/SimpleSitemapTypeInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\simple_sitemap\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorInterface;
+
+/**
+ * Provides an interface defining a sitemap type entity.
+ */
+interface SimpleSitemapTypeInterface extends ConfigEntityInterface {
+
+  /**
+   * Gets the sitemap generator.
+   *
+   * @return \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorInterface
+   *   The sitemap generator.
+   */
+  public function getSitemapGenerator(): SitemapGeneratorInterface;
+
+  /**
+   * Gets the URL generators.
+   *
+   * @return \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorInterface[]
+   *   Array of URL generators.
+   */
+  public function getUrlGenerators(): array;
+
+  /**
+   * Determines whether the sitemap type has a URL generator with the given ID.
+   *
+   * @param string $generator_id
+   *   ID of the URL generator.
+   *
+   * @return bool
+   *   TRUE if the sitemap type has a URL generator with the given ID.
+   */
+  public function hasUrlGenerator(string $generator_id): bool;
+
+}
diff --git a/web/modules/simple_sitemap/src/Entity/SimpleSitemapTypeStorage.php b/web/modules/simple_sitemap/src/Entity/SimpleSitemapTypeStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7683d6ff9113c007337628ae3b902ac36a7fa0b
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Entity/SimpleSitemapTypeStorage.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\simple_sitemap\Entity;
+
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Storage handler for sitemap type configuration entities.
+ */
+class SimpleSitemapTypeStorage extends ConfigEntityStorage {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * SimpleSitemapTypeStorage constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
+   *   The UUID service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
+   *   The memory cache backend.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $memory_cache);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('config.factory'),
+      $container->get('uuid'),
+      $container->get('language_manager'),
+      $container->get('entity.memory_cache'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSave($id, EntityInterface $entity) {
+    /** @var SimpleSitemapTypeInterface $entity */
+    if ($entity->get('sitemap_generator') === NULL || $entity->get('sitemap_generator') === '') {
+      throw new \InvalidArgumentException("The sitemap type must define its sitemap generator.");
+    }
+
+    if (empty($entity->get('url_generators'))) {
+      throw new \InvalidArgumentException("The sitemap type must define its URL generators");
+    }
+
+    if ($entity->label() === NULL || $entity->label() === '') {
+      $entity->set('label', $id);
+    }
+
+    return parent::doSave($id, $entity);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Exception/SitemapNotExistsException.php b/web/modules/simple_sitemap/src/Exception/SitemapNotExistsException.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6df1563441d6fee9b398852e3328b38f6d2a18c
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Exception/SitemapNotExistsException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Drupal\simple_sitemap\Exception;
+
+/**
+ * Exception class thrown when sitemap data does not exist.
+ */
+class SitemapNotExistsException extends \InvalidArgumentException {}
diff --git a/web/modules/simple_sitemap/src/Exception/SkipElementException.php b/web/modules/simple_sitemap/src/Exception/SkipElementException.php
new file mode 100644
index 0000000000000000000000000000000000000000..239f5ecc5914cd5d1993b307ee760705852ae804
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Exception/SkipElementException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Drupal\simple_sitemap\Exception;
+
+/**
+ * Exception class thrown when an element is skipped during sitemap generation.
+ */
+class SkipElementException extends \InvalidArgumentException {}
diff --git a/web/modules/simple_sitemap/src/Form/CustomLinksForm.php b/web/modules/simple_sitemap/src/Form/CustomLinksForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..ed783eced42093ab0441bb06fa5da93029ad7b5e
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/CustomLinksForm.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Settings;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Manager\Generator;
+use Drupal\Core\Path\PathValidatorInterface;
+
+/**
+ * Provides form to manage custom links.
+ */
+class CustomLinksForm extends SimpleSitemapFormBase {
+
+  /**
+   * The path validator service.
+   *
+   * @var \Drupal\Core\Path\PathValidatorInterface
+   */
+  protected $pathValidator;
+
+  /**
+   * CustomLinksForm constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
+   *   The path validator service.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    Generator $generator,
+    Settings $settings,
+    FormHelper $form_helper,
+    PathValidatorInterface $path_validator
+  ) {
+    parent::__construct(
+      $config_factory,
+      $generator,
+      $settings,
+      $form_helper
+    );
+    $this->pathValidator = $path_validator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.settings'),
+      $container->get('simple_sitemap.form_helper'),
+      $container->get('path.validator')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'simple_sitemap_custom_links_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['variants']['#tree'] = TRUE;
+
+    foreach ($this->getCustomLinkCapableSitemaps() as $variant => $sitemap) {
+      $custom_link_settings = $this->generator->setVariants($variant)->customLinkManager()->get();
+
+      $count = $custom_link_settings ? count($custom_link_settings[$variant]) : 0;
+      $form['variants'][$sitemap->id()] = [
+        '#type' => 'details',
+        '#title' => $sitemap->label() . ($count ? ' (' . $count . ')' : ''),
+        '#open' => (bool) $custom_link_settings,
+      ];
+
+      $form['variants'][$variant]['custom_links'] = [
+        '#type' => 'textarea',
+        '#default_value' => $custom_link_settings ? $this->customLinksToString($custom_link_settings[$variant]) : '',
+      ];
+    }
+
+    $form['include_images'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Include images'),
+      '#description' => $this->t('If a custom link points to an entity, include its referenced images in the sitemap.'),
+      '#default_value' => $this->settings->get('custom_links_include_images', FALSE),
+      '#options' => [0 => $this->t('No'), 1 => $this->t('Yes')],
+    ];
+
+    $form = $this->formHelper->regenerateNowForm($form);
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $sitemaps = $this->getCustomLinkCapableSitemaps();
+    foreach ($form_state->getValue('variants') as $variant => $values) {
+      foreach ($this->stringToCustomLinks($values['custom_links']) as $i => $link_config) {
+        $placeholders = [
+          '@sitemap' => $sitemaps[$variant]->label(),
+          '@line' => ++$i,
+          '@path' => $link_config['path'],
+          '@priority' => $link_config['priority'] ?? '',
+          '@changefreq' => $link_config['changefreq'] ?? '',
+          '@changefreq_options' => implode(', ', array_keys(FormHelper::getChangefreqOptions())),
+        ];
+
+        // Checking if internal path exists.
+        if (!$this->pathValidator->getUrlIfValidWithoutAccessCheck($link_config['path'])
+          // Path validator does not see a double slash as an error. Catching
+          // this to prevent breaking path generation.
+          || strpos($link_config['path'], '//') !== FALSE) {
+          $form_state->setError($form['variants'][$variant]['custom_links'], $this->t('<strong>@sitemap, line @line</strong>: The path <em>@path</em> does not exist.', $placeholders));
+        }
+
+        // Making sure the paths start with a slash.
+        if ($link_config['path'][0] !== '/') {
+          $form_state->setError($form['variants'][$variant]['custom_links'], $this->t("<strong>@sitemap, line @line</strong>: The path <em>@path</em> needs to start with a '/'.", $placeholders));
+        }
+
+        // Making sure the priority is formatted correctly.
+        if (isset($link_config['priority']) && !FormHelper::isValidPriority($link_config['priority'])) {
+          $form_state->setError($form['variants'][$variant]['custom_links'], $this->t('<strong>@sitemap, line @line</strong>: The priority setting <em>@priority</em> for path <em>@path</em> is incorrect. Set the priority from 0.0 to 1.0.', $placeholders));
+        }
+
+        // Making sure changefreq is formatted correctly.
+        if (isset($link_config['changefreq']) && !FormHelper::isValidChangefreq($link_config['changefreq'])) {
+          $form_state->setError($form['variants'][$variant]['custom_links'], $this->t('<strong>@sitemap, line @line</strong>: The changefreq setting <em>@changefreq</em> for path <em>@path</em> is incorrect. The following are the correct values: <em>@changefreq_options</em>.', $placeholders));
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    foreach ($form_state->getValue('variants') as $variant => $values) {
+      $this->generator->setVariants($variant)->customLinkManager()->remove();
+      foreach ($this->stringToCustomLinks($values['custom_links']) as $link_config) {
+        $this->generator->customLinkManager()->add($link_config['path'], $link_config);
+      }
+    }
+    $this->settings->save('custom_links_include_images', (bool) $form_state->getValue('include_images'));
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * Gets sitemaps that are of a type that implements a custom URL generator.
+   *
+   * @return \Drupal\simple_sitemap\Entity\SimpleSitemap[]
+   *   Array of sitemaps of a type that implements a custom URL generator.
+   */
+  protected function getCustomLinkCapableSitemaps(): array {
+    $sitemaps = SimpleSitemap::loadMultiple();
+    foreach ($sitemaps as $variant => $sitemap) {
+      if (!$sitemap->getType()->hasUrlGenerator('custom')) {
+        unset($sitemaps[$variant]);
+      }
+    }
+
+    return $sitemaps;
+  }
+
+  /**
+   * Converts a string with custom links to an array.
+   *
+   * @param string $custom_links_string
+   *   A string representation of the custom links to convert.
+   *
+   * @return array
+   *   Array of custom links.
+   */
+  protected function stringToCustomLinks(string $custom_links_string): array {
+
+    // Unify newline characters and explode into array.
+    $custom_links_string_lines = explode("\n", str_replace("\r\n", "\n", $custom_links_string));
+
+    // Remove empty values and whitespaces from array.
+    $custom_links_string_lines = array_filter(array_map('trim', $custom_links_string_lines));
+
+    $custom_links = [];
+    foreach ($custom_links_string_lines as $i => $line) {
+      $link_settings = explode(' ', $line);
+      $custom_links[$i]['path'] = $link_settings[0];
+
+      // If two arguments are provided for a link, assume the first to be
+      // priority, the second to be changefreq.
+      if (!empty($link_settings[1]) && !empty($link_settings[2])) {
+        $custom_links[$i]['priority'] = $link_settings[1];
+        $custom_links[$i]['changefreq'] = $link_settings[2];
+      }
+      else {
+        // If one argument is provided for a link, guess if it is priority or
+        // changefreq.
+        if (!empty($link_settings[1])) {
+          if (is_numeric($link_settings[1])) {
+            $custom_links[$i]['priority'] = $link_settings[1];
+          }
+          else {
+            $custom_links[$i]['changefreq'] = $link_settings[1];
+          }
+        }
+      }
+    }
+
+    return $custom_links;
+  }
+
+  /**
+   * Converts an array of custom links to a string.
+   *
+   * @param array $links
+   *   Array of custom links to convert.
+   *
+   * @return string
+   *   A string representation of the custom links.
+   */
+  protected function customLinksToString(array $links): string {
+    $setting_string = '';
+    foreach ($links as $custom_link) {
+      $setting_string .= $custom_link['path'];
+      $setting_string .= isset($custom_link['priority'])
+        ? ' ' . FormHelper::formatPriority($custom_link['priority'])
+        : '';
+      $setting_string .= isset($custom_link['changefreq'])
+        ? ' ' . $custom_link['changefreq']
+        : '';
+      $setting_string .= "\r\n";
+    }
+
+    return $setting_string;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/EntitiesForm.php b/web/modules/simple_sitemap/src/Form/EntitiesForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..f151da8fa3568eacf2211ef8273469297a3260d4
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/EntitiesForm.php
@@ -0,0 +1,246 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap\Manager\EntityManager;
+use Drupal\simple_sitemap\Manager\Generator;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Settings;
+
+/**
+ * Provides form to manage entity settings.
+ */
+class EntitiesForm extends SimpleSitemapFormBase {
+
+  /**
+   * Helper class for working with entities.
+   *
+   * @var \Drupal\simple_sitemap\Entity\EntityHelper
+   */
+  protected $entityHelper;
+
+  /**
+   * The simple_sitemap.entity_manager service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * EntitiesForm constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Manager\EntityManager $entity_manager
+   *   The simple_sitemap.entity_manager service.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    Generator $generator,
+    Settings $settings,
+    FormHelper $form_helper,
+    EntityHelper $entity_helper,
+    EntityManager $entity_manager
+  ) {
+    parent::__construct(
+      $config_factory,
+      $generator,
+      $settings,
+      $form_helper
+    );
+    $this->entityHelper = $entity_helper;
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.settings'),
+      $container->get('simple_sitemap.form_helper'),
+      $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.entity_manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'simple_sitemap_entities_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state): array {
+    $table = &$form['entity_types'];
+
+    $table = [
+      '#type' => 'table',
+      '#header' => [
+        'type' => $this->t('Entity type'),
+        'bundles' => $this->t('Indexed bundles'),
+        'enabled' => $this->t('Enabled'),
+        'operations' => $this->t('Operations'),
+      ],
+      '#empty' => $this->t('No supported entity types available.'),
+      '#attached' => ['library' => ['simple_sitemap/sitemapEntities']],
+    ];
+
+    $entity_types = $this->entityHelper->getSupportedEntityTypes();
+    foreach ($entity_types as $entity_type_id => &$entity_type) {
+      $entity_type = $entity_type->getLabel() ?: $entity_type_id;
+    }
+    natcasesort($entity_types);
+
+    foreach ($entity_types as $entity_type_id => $label) {
+      $is_enabled = $this->entityManager->entityTypeIsEnabled($entity_type_id);
+
+      $table[$entity_type_id]['type'] = [
+        '#markup' => '<strong>' . $label . '</strong>',
+      ];
+
+      $table[$entity_type_id]['bundles'] = [
+        '#type' => 'item',
+        '#input' => FALSE,
+        '#markup' => $this->getIndexedBundlesString($entity_type_id),
+      ];
+
+      $table[$entity_type_id]['enabled'] = [
+        '#type' => 'checkbox',
+        '#default_value' => $is_enabled,
+        '#parents' => ['entity_types', $entity_type_id],
+      ];
+
+      $table[$entity_type_id]['operations'] = [
+        '#type' => 'operations',
+        '#access' => $is_enabled,
+        '#links' => [
+          'display_edit' => [
+            'title' => $this->t('Configure'),
+            'url' => Url::fromRoute('simple_sitemap.entity_bundles', ['entity_type_id' => $entity_type_id], [
+              'query' => ['destination' => Url::fromRoute('<current>')->toString()],
+            ]),
+          ],
+        ],
+      ];
+
+      if ($is_enabled) {
+        $table[$entity_type_id]['#attributes']['class'][] = 'color-success';
+
+        if ($this->hasIndexedBundles($entity_type_id)) {
+          $table[$entity_type_id]['#attributes']['class'][] = 'protected';
+        }
+      }
+    }
+
+    $form = $this->formHelper->regenerateNowForm($form);
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity_types = $form_state->getValue('entity_types');
+
+    foreach ($entity_types as $entity_type_id => $enabled) {
+      if ($enabled) {
+        $this->entityManager->enableEntityType($entity_type_id);
+      }
+      else {
+        $this->entityManager->disableEntityType($entity_type_id);
+      }
+    }
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * Gets indexed bundles.
+   *
+   * @return array
+   *   Indexed bundles data.
+   */
+  protected function getIndexedBundles(): array {
+    static $indexed_bundles;
+
+    if ($indexed_bundles === NULL) {
+      $indexed_bundles = [];
+
+      foreach ($this->entityManager->setVariants()->getAllBundleSettings() as $variant => $entity_types) {
+        $sitemap_label = SimpleSitemap::load($variant)->label();
+
+        foreach ($entity_types as $entity_type_id => $bundles) {
+          foreach ($bundles as $bundle_name => $bundle_settings) {
+            if ($bundle_settings['index']) {
+              $indexed_bundles[$entity_type_id][$bundle_name]['sitemaps'][] = $sitemap_label;
+              $indexed_bundles[$entity_type_id][$bundle_name]['bundle_label'] = $this->entityHelper->getBundleLabel($entity_type_id, $bundle_name);
+            }
+          }
+        }
+      }
+    }
+
+    return $indexed_bundles;
+  }
+
+  /**
+   * Determines whether the given entity type has indexed bundles.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return bool
+   *   TRUE if the given entity type has indexed bundles, FALSE otherwise.
+   */
+  protected function hasIndexedBundles(string $entity_type_id): bool {
+    return !empty($this->getIndexedBundles()[$entity_type_id]);
+  }
+
+  /**
+   * Gets a string representation of indexed bundles for the given entity type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup|string
+   *   A string representation of indexed bundles for the given entity type.
+   */
+  protected function getIndexedBundlesString(string $entity_type_id) {
+    if (!$this->entityManager->entityTypeIsEnabled($entity_type_id)) {
+      return '';
+    }
+    if (!$this->hasIndexedBundles($entity_type_id)) {
+      return $this->t('Excluded from all sitemaps');
+    }
+
+    foreach ($this->getIndexedBundles()[$entity_type_id] as $bundle_data) {
+      $pieces[] = $this->t('%bundle_label <span class="description">(sitemaps: %sitemaps)</span>', [
+        '%bundle_label' => $bundle_data['bundle_label'],
+        '%sitemaps' => implode(', ', $bundle_data['sitemaps']),
+      ]);
+    }
+
+    return implode('<br />', $pieces ?? []);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/EntityBundlesForm.php b/web/modules/simple_sitemap/src/Form/EntityBundlesForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2d86f4c88fd97ee73a3ba5b21ac459a273f6dad
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/EntityBundlesForm.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Manager\EntityManager;
+use Drupal\simple_sitemap\Manager\Generator;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Settings;
+
+/**
+ * Provides form to manage entity bundles settings.
+ */
+class EntityBundlesForm extends SimpleSitemapFormBase {
+
+  /**
+   * Helper class for working with entities.
+   *
+   * @var \Drupal\simple_sitemap\Entity\EntityHelper
+   */
+  protected $entityHelper;
+
+  /**
+   * The simple_sitemap.entity_manager service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * EntityBundlesForm constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Manager\EntityManager $entity_manager
+   *   The simple_sitemap.entity_manager service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    Generator $generator,
+    Settings $settings,
+    FormHelper $form_helper,
+    EntityHelper $entity_helper,
+    EntityManager $entity_manager,
+    EntityTypeManagerInterface $entity_type_manager
+  ) {
+    parent::__construct(
+      $config_factory,
+      $generator,
+      $settings,
+      $form_helper
+    );
+    $this->entityHelper = $entity_helper;
+    $this->entityManager = $entity_manager;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.settings'),
+      $container->get('simple_sitemap.form_helper'),
+      $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.entity_manager'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'simple_sitemap_entity_bundles_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL): array {
+    if (!$this->entityTypeManager->hasDefinition($entity_type_id)) {
+      throw new NotFoundHttpException();
+    }
+
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+    if (!$this->entityHelper->supports($entity_type) || !$this->entityManager->entityTypeIsEnabled($entity_type_id)) {
+      throw new NotFoundHttpException();
+    }
+
+    $form['#title'] = $this->t('Configure %label entity type', [
+      '%label' => $entity_type->getLabel() ?: $entity_type_id,
+    ]);
+
+    $form['entity_type_id'] = [
+      '#type' => 'value',
+      '#value' => $entity_type_id,
+    ];
+
+    $form['bundles'] = [
+      '#type' => 'vertical_tabs',
+      '#access' => !$this->entityHelper->entityTypeIsAtomic($entity_type_id),
+      '#attached' => ['library' => ['simple_sitemap/fieldsetSummaries']],
+    ];
+
+    foreach ($this->entityHelper->getBundleInfo($entity_type_id) as $bundle_name => $bundle_info) {
+      $bundle_form = &$form['settings'][$bundle_name];
+
+      $bundle_form = [
+        '#type' => $form['bundles']['#access'] ? 'details' : 'container',
+        '#title' => $this->entityHelper->getBundleLabel($entity_type_id, $bundle_name),
+        '#attributes' => ['class' => ['simple-sitemap-fieldset']],
+        '#group' => 'bundles',
+        '#parents' => ['bundles', $bundle_name],
+        '#tree' => TRUE,
+      ];
+
+      $bundle_form = $this->formHelper
+        ->bundleSettingsForm($bundle_form, $entity_type_id, $bundle_name);
+    }
+
+    $form = $this->formHelper->regenerateNowForm($form);
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity_type_id = $form_state->getValue('entity_type_id');
+    $bundles = $form_state->getValue('bundles');
+
+    // @todo No need to load all sitemaps here.
+    foreach (SimpleSitemap::loadMultiple() as $variant => $sitemap) {
+      $this->entityManager->setVariants($variant);
+
+      foreach ($bundles as $bundle_name => $settings) {
+        if (isset($settings[$variant])) {
+          $this->entityManager->setBundleSettings($entity_type_id, $bundle_name, $settings[$variant]);
+        }
+      }
+    }
+
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/FormHelper.php b/web/modules/simple_sitemap/src/Form/FormHelper.php
index c3e0e958e3b1ced52acfcd700adbc0b8eae66320..f81474763d56de0a3f7c5b1fce7dd8ff96af01b6 100644
--- a/web/modules/simple_sitemap/src/Form/FormHelper.php
+++ b/web/modules/simple_sitemap/src/Form/FormHelper.php
@@ -2,599 +2,410 @@
 
 namespace Drupal\simple_sitemap\Form;
 
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Entity\EntityFormInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\simple_sitemap\EntityHelper;
-use Drupal\simple_sitemap\Simplesitemap;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Form\Handler\EntityFormHandlerInterface;
+use Drupal\simple_sitemap\Form\Handler\BundleEntityFormHandler;
+use Drupal\simple_sitemap\Form\Handler\EntityFormHandler;
+use Drupal\simple_sitemap\Manager\Generator;
 use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\simple_sitemap\Settings;
 
 /**
- * Class FormHelper
- * @package Drupal\simple_sitemap\Form
+ * Helper class for working with forms.
  */
 class FormHelper {
+
+  use DependencySerializationTrait;
   use StringTranslationTrait;
 
-  const PRIORITY_HIGHEST = 10;
-  const PRIORITY_DIVIDER = 10;
+  protected const PRIORITY_HIGHEST = 10;
+  protected const PRIORITY_DIVIDER = 10;
+
+  protected const ENTITY_FORM_HANDLER = EntityFormHandler::class;
+  protected const BUNDLE_ENTITY_FORM_HANDLER = BundleEntityFormHandler::class;
 
   /**
-   * @var \Drupal\simple_sitemap\Simplesitemap
+   * The sitemap generator service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\Generator
    */
   protected $generator;
 
   /**
-   * @var \Drupal\simple_sitemap\EntityHelper
+   * Helper class for working with entities.
+   *
+   * @var \Drupal\simple_sitemap\Entity\EntityHelper
    */
   protected $entityHelper;
 
   /**
+   * Proxy for the current user account.
+   *
    * @var \Drupal\Core\Session\AccountProxyInterface
    */
   protected $currentUser;
 
   /**
-   * @var \Drupal\Core\Form\FormState
-   */
-  protected $formState;
-
-  /**
-   * @var string|null
-   */
-  protected $entityCategory;
-
-  /**
-   * @var string
-   */
-  protected $entityTypeId;
-
-  /**
-   * @var string
-   */
-  protected $bundleName;
-
-  /**
-   * @var string
-   */
-  protected $instanceId;
-
-  /**
-   * @var array
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
    */
   protected $settings;
 
   /**
-   * @var array
+   * The class resolver.
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
    */
-  protected static $allowedFormOperations = [
-    'default',
-    'edit',
-    'add',
-    'register',
-  ];
+  protected $classResolver;
 
   /**
-   * @var array
+   * Cron intervals.
+   *
+   * @var int[]
    */
-  protected static $changefreqValues = [
-    'always',
-    'hourly',
-    'daily',
-    'weekly',
-    'monthly',
-    'yearly',
-    'never',
-  ];
-
-  protected static $cronIntervals = [
-    1,
-    3,
-    6,
-    12,
-    24,
-    48,
-    72,
-    96,
-    120,
-    144,
-    168,
-  ];
+  protected static $cronIntervals = [1, 3, 6, 12, 24, 48, 72, 96, 120, 144, 168];
 
   /**
    * FormHelper constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
+   *
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
    * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   Proxy for the current user account.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
    */
   public function __construct(
-    Simplesitemap $generator,
-    EntityHelper $entityHelper,
-    AccountProxyInterface $current_user
+    Generator $generator,
+    Settings $settings,
+    EntityHelper $entity_helper,
+    AccountProxyInterface $current_user,
+    ClassResolverInterface $class_resolver
   ) {
     $this->generator = $generator;
-    $this->entityHelper = $entityHelper;
+    $this->settings = $settings;
+    $this->entityHelper = $entity_helper;
     $this->currentUser = $current_user;
+    $this->classResolver = $class_resolver;
   }
 
   /**
+   * Alters the specified form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
-   * @return bool
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   *   The current state of the form.
    *
+   * @see simple_sitemap_form_alter()
+   * @see simple_sitemap_engines_form_alter()
    */
-  public function processForm(FormStateInterface $form_state) {
-    $this->formState = $form_state;
-    $this->cleanUpFormInfo();
-
-    if ($this->getEntityDataFromFormEntity()) {
-      $this->negotiateSettings();
+  public function formAlter(array &$form, FormStateInterface $form_state) {
+    if (!$this->formAlterAccess()) {
+      return;
     }
 
-    return $this->supports();
-  }
-
-  /**
-   * @param string $entity_category
-   * @return $this
-   */
-  public function setEntityCategory($entity_category) {
-    $this->entityCategory = $entity_category;
-    return $this;
-  }
-
-  /**
-   * @return null|string
-   */
-  public function getEntityCategory() {
-    return $this->entityCategory;
-  }
+    $form_object = $form_state->getFormObject();
+    if (!$form_object instanceof EntityFormInterface) {
+      return;
+    }
 
-  /**
- * @param string $entity_type_id
- * @return $this
- */
-  public function setEntityTypeId($entity_type_id) {
-    $this->entityTypeId = $entity_type_id;
+    $form_handler = $this->resolveEntityFormHandler($form_object->getEntity());
 
-    return $this;
-  }
+    if ($form_handler instanceof EntityFormHandlerInterface && $form_handler->isSupportedOperation($form_object->getOperation())) {
+      $entity_type_id = $form_handler->getEntityTypeId();
 
-  /**
-   * @return string
-   */
-  public function getEntityTypeId() {
-    return $this->entityTypeId;
-  }
-
-  /**
-   * @param string $bundle_name
-   * @return $this
-   */
-  public function setBundleName($bundle_name) {
-    $this->bundleName = $bundle_name;
-
-    return $this;
+      if ($this->generator->entityManager()->entityTypeIsEnabled($entity_type_id)) {
+        $form_handler->formAlter($form, $form_state);
+      }
+    }
   }
 
   /**
-   * @return string
+   * Determines whether a form can be altered.
+   *
+   * @return bool
+   *   TRUE if a form can be altered, FALSE otherwise.
    */
-  public function getBundleName() {
-    return $this->bundleName;
+  protected function formAlterAccess(): bool {
+    return $this->currentUser->hasPermission('administer sitemap settings');
   }
 
   /**
-   * @param string $instance_id
-   * @return $this
+   * Resolves the entity form handler for the given entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which the form handler should be resolved.
+   *
+   * @return \Drupal\simple_sitemap\Form\Handler\EntityFormHandlerInterface|null
+   *   The instance of the entity form handler or NULL if there is no handler
+   *   for the given entity.
    */
-  public function setInstanceId($instance_id) {
-    $this->instanceId = $instance_id;
-
-    return $this;
-  }
+  public function resolveEntityFormHandler(EntityInterface $entity): ?EntityFormHandlerInterface {
+    $definition = $this->resolveEntityFormHandlerDefinition($entity);
 
-  /**
-   * @return string
-   */
-  public function getInstanceId() {
-    return $this->instanceId;
+    if ($definition) {
+      return $this->classResolver
+        ->getInstanceFromDefinition($definition)
+        ->setEntity($entity);
+    }
+    return NULL;
   }
 
   /**
-   * @return bool
+   * Resolves the definition of the entity form handler for the given entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which the definition should be resolved.
+   *
+   * @return string|null
+   *   A string with definition of the entity form handler or NULL if there is
+   *   no definition for the given entity.
    */
-  protected function supports() {
+  protected function resolveEntityFormHandlerDefinition(EntityInterface $entity): ?string {
+    $entity_type_id = $entity->getEntityTypeId();
 
-    // Do not alter the form if it is irrelevant to sitemap generation.
-    if (empty($this->getEntityCategory())) {
-      return FALSE;
+    if ($this->entityHelper->supports($entity->getEntityType())) {
+      return static::ENTITY_FORM_HANDLER;
     }
-
-    // Do not alter the form if user lacks certain permissions.
-    if (!$this->currentUser->hasPermission('administer sitemap settings')) {
-      return FALSE;
+    // Menu fix.
+    elseif ($entity_type_id === 'menu') {
+      return static::BUNDLE_ENTITY_FORM_HANDLER;
     }
-
-    // Do not alter the form if entity is not enabled in sitemap settings.
-    if (!$this->generator->entityTypeIsEnabled($this->getEntityTypeId())) {
-      return FALSE;
+    else {
+      foreach ($this->entityHelper->getSupportedEntityTypes() as $entity_type) {
+        if ($entity_type->getBundleEntityType() === $entity_type_id) {
+          return static::BUNDLE_ENTITY_FORM_HANDLER;
+        }
+      }
     }
-
-    return TRUE;
+    return NULL;
   }
 
   /**
-   * @return bool
+   * Returns a form to configure the bundle settings.
+   *
+   * @param array $form
+   *   The form where the bundle settings form is being included in.
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $bundle_name
+   *   The bundle name.
+   *
+   * @return array
+   *   The form elements for the bundle settings.
    */
-  public function entityIsNew() {
-    return !empty($entity = $this->getFormEntity()) ? $entity->isNew() : TRUE;
+  public function bundleSettingsForm(array $form, $entity_type_id, $bundle_name): array {
+    /** @var \Drupal\simple_sitemap\Form\Handler\BundleEntityFormHandler $form_handler */
+    $form_handler = $this->classResolver->getInstanceFromDefinition(static::BUNDLE_ENTITY_FORM_HANDLER);
+
+    return $form_handler->setEntityTypeId($entity_type_id)
+      ->setBundleName($bundle_name)
+      ->settingsForm($form);
   }
 
   /**
-   * @param array $form_fragment
-   * @return $this
+   * Adds the 'regenerate all sitemaps' checkbox to the form.
+   *
+   * @param array $form
+   *   The form where the checkbox is being included in.
+   *
+   * @return array
+   *   The form elements with checkbox.
    */
-  public function displayRegenerateNow(&$form_fragment) {
-    $form_fragment['simple_sitemap_regenerate_now'] = [
+  public function regenerateNowForm(array $form): array {
+    $form['simple_sitemap_regenerate_now'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Regenerate all sitemaps after hitting <em>Save</em>'),
       '#description' => $this->t('This setting will regenerate all sitemaps including the above changes.'),
       '#default_value' => FALSE,
+      '#tree' => FALSE,
+      '#weight' => 90,
     ];
-    if ($this->generator->getSetting('cron_generate')) {
-      $form_fragment['simple_sitemap_regenerate_now']['#description'] .= '<br>' . $this->t('Otherwise the sitemaps will be regenerated during a future cron run.');
-    }
 
-    return $this;
-  }
-
-  /**
-   * @return $this
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   */
-  public function negotiateSettings() {
-
-    $this->settings = $this->generator->setVariants(TRUE)
-      ->getBundleSettings($this->getEntityTypeId(), $this->getBundleName(), TRUE, TRUE);
-    if ($this->getEntityCategory() === 'instance') {
-
-      //todo Should spit out variant => settings and not just settings; to do this, alter getEntityInstanceSettings() to include 'multiple variants' option.
-      foreach ($this->settings as $variant_name => $settings) {
-        if (NULL !== $instance_id = $this->getInstanceId()) {
-          $this->settings[$variant_name] = $this->generator
-            ->setVariants($variant_name)
-            ->getEntityInstanceSettings($this->getEntityTypeId(), $instance_id);
-        }
-        $this->settings[$variant_name]['bundle_settings'] = $settings;
-      }
+    if ($this->settings->get('cron_generate')) {
+      $form['simple_sitemap_regenerate_now']['#description'] .= '<br>' . $this->t('Otherwise the sitemaps will be regenerated during a future cron run.');
     }
 
-    return $this;
+    return $form;
   }
 
   /**
-   * @param $form_fragment
-   * @return $this
-   */
-  public function displayEntitySettings(&$form_fragment) {
-    $bundle_name = !empty($this->getBundleName())
-      ? $this->entityHelper->getBundleLabel($this->getEntityTypeId(), $this->getBundleName())
-      : $this->t('undefined');
-
-    $variants = $this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE);
-    $form_fragment['settings']['#markup'] = empty($variants)
-      ? $this->t('At least one sitemap variants needs to be defined for a bundle to be indexable.<br>Variants can be configured <a href="@url">here</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/variants'])
-      : '<strong>' . $this->t('Sitemap variants') . '</strong>';
-
-    foreach ($variants as $variant => $definition) {
-      $form_fragment['settings'][$variant] = [
-        '#type' => 'details',
-        '#title' => '<em>' . $this->t($definition['label']) . '</em>',
-        '#open' => !empty($this->settings[$variant]['index']),
-      ];
-
-      // Disable fields of entity instance whose bundle is not indexed.
-      $form_fragment['settings'][$variant]['#disabled'] = $this->getEntityCategory() === 'instance' && empty($this->settings[$variant]['bundle_settings']['index']);
-
-      // Index
-      $form_fragment['settings'][$variant]['index_' . $variant . '_' . $this->getEntityTypeId() . '_settings'] = [
-        '#type' => 'radios',
-        '#default_value' => (int) $this->settings[$variant]['index'],
-        '#options' => [
-          $this->getEntityCategory() === 'instance'
-            ? $this->t('Do not index this <em>@bundle</em> entity in variant <em>@variant_label</em>', ['@bundle' => $bundle_name, '@variant_label' => $this->t($variants[$variant]['label'])])
-            : $this->t('Do not index entities of type <em>@bundle</em> in variant <em>@variant_label</em>', ['@bundle' => $bundle_name, '@variant_label' => $this->t($variants[$variant]['label'])]),
-          $this->getEntityCategory() === 'instance'
-            ? $this->t('Index this <em>@bundle entity</em> in variant <em>@variant_label</em>', ['@bundle' => $bundle_name, '@variant_label' => $this->t($variants[$variant]['label'])])
-            : $this->t('Index entities of type <em>@bundle</em> in variant <em>@variant_label</em>', ['@bundle' => $bundle_name, '@variant_label' => $this->t($variants[$variant]['label'])]),
-        ],
-        '#attributes' => ['class' => ['enabled-for-variant', $variant]],
-      ];
-
-      if ($this->getEntityCategory() === 'instance' && isset($this->settings[$variant]['bundle_settings']['index'])) {
-        $form_fragment['settings'][$variant]['index_' . $variant . '_' . $this->getEntityTypeId() . '_settings']['#options'][(int) $this->settings[$variant]['bundle_settings']['index']] .= ' <em>(' . $this->t('default') . ')</em>';
-      }
-
-      // Priority
-      $form_fragment['settings'][$variant]['priority_' . $variant . '_' . $this->getEntityTypeId() . '_settings'] = [
-        '#type' => 'select',
-        '#title' => $this->t('Priority'),
-        '#description' => $this->getEntityCategory() === 'instance'
-          ? $this->t('The priority this <em>@bundle</em> entity will have in the eyes of search engine bots.', ['@bundle' => $bundle_name])
-          : $this->t('The priority entities of this type will have in the eyes of search engine bots.'),
-        '#default_value' => $this->settings[$variant]['priority'],
-        '#options' => $this->getPrioritySelectValues(),
-        '#states' => [
-          'visible' => [':input[name="index_' . $variant . '_' . $this->getEntityTypeId() . '_settings"]' => ['value' => 1]],
-        ],
-      ];
-
-      if ($this->getEntityCategory() === 'instance' && isset($this->settings[$variant]['bundle_settings']['priority'])) {
-        $form_fragment['settings'][$variant]['priority_' . $variant . '_' . $this->getEntityTypeId() . '_settings']['#options'][$this->formatPriority($this->settings[$variant]['bundle_settings']['priority'])] .= ' (' . $this->t('default') . ')';
-      }
-
-      // Changefreq
-      $form_fragment['settings'][$variant]['changefreq_' . $variant . '_' . $this->getEntityTypeId() . '_settings'] = [
-        '#type' => 'select',
-        '#title' => $this->t('Change frequency'),
-        '#description' => $this->getEntityCategory() === 'instance'
-          ? $this->t('The frequency with which this <em>@bundle</em> entity changes. Search engine bots may take this as an indication of how often to index it.', ['@bundle' => $bundle_name])
-          : $this->t('The frequency with which entities of this type change. Search engine bots may take this as an indication of how often to index them.'),
-        '#default_value' => isset($this->settings[$variant]['changefreq']) ? $this->settings[$variant]['changefreq'] : NULL,
-        '#options' => $this->getChangefreqSelectValues(),
-        '#states' => [
-          'visible' => [':input[name="index_' . $variant . '_' . $this->getEntityTypeId() . '_settings"]' => ['value' => 1]],
-        ],
-      ];
-
-      if ($this->getEntityCategory() === 'instance' && isset($this->settings[$variant]['bundle_settings']['changefreq'])) {
-        $form_fragment['settings'][$variant]['changefreq_' . $variant . '_' . $this->getEntityTypeId() . '_settings']['#options'][$this->settings[$variant]['bundle_settings']['changefreq']] .= ' (' . $this->t('default') . ')';
-      }
-
-      // Images
-      $form_fragment['settings'][$variant]['include_images_' . $variant . '_' . $this->getEntityTypeId() . '_settings'] = [
-        '#type' => 'select',
-        '#title' => $this->t('Include images'),
-        '#description' => $this->getEntityCategory() === 'instance'
-          ? $this->t('Determines if images referenced by this <em>@bundle</em> entity should be included in the sitemap.', ['@bundle' => $bundle_name])
-          : $this->t('Determines if images referenced by entities of this type should be included in the sitemap.'),
-        '#default_value' => isset($this->settings[$variant]['include_images']) ? (int) $this->settings[$variant]['include_images'] : 0,
-        '#options' => [$this->t('No'), $this->t('Yes')],
-        '#states' => [
-          'visible' => [':input[name="index_' . $variant . '_' . $this->getEntityTypeId() . '_settings"]' => ['value' => 1]],
-        ],
-      ];
-
-      if ($this->getEntityCategory() === 'instance' && isset($this->settings[$variant]['bundle_settings']['include_images'])) {
-        $form_fragment['settings'][$variant]['include_images_' . $variant . '_' . $this->getEntityTypeId() . '_settings']['#options'][(int) $this->settings[$variant]['bundle_settings']['include_images']] .= ' (' . $this->t('default') . ')';
-      }
-    }
-
-    return $this;
-  }
-
-  /**
-   * Checks if this particular form is a bundle form, or a bundle instance form
-   * and gathers sitemap settings from the database.
+   * Form submission handler.
    *
-   * @return bool
-   *   TRUE if this is a bundle or bundle instance form, FALSE otherwise.
-   */
-  protected function getEntityDataFromFormEntity() {
-    if (!$form_entity = $this->getFormEntity()) {
-      return FALSE;
-    }
-
-    $entity_type_id = $form_entity->getEntityTypeId();
-    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
-    if (isset($sitemap_entity_types[$entity_type_id])) {
-      $this->setEntityCategory('instance');
-    }
-    else {
-      /** @var \Drupal\Core\Entity\EntityType $sitemap_entity_type */
-      foreach ($sitemap_entity_types as $sitemap_entity_type) {
-        if ($sitemap_entity_type->getBundleEntityType() === $entity_type_id) {
-          $this->setEntityCategory('bundle');
-          break;
-        }
-      }
-    }
-
-    // Menu fix.
-    $this->setEntityCategory(
-      NULL === $this->getEntityCategory() && $entity_type_id === 'menu'
-        ? 'bundle'
-        : $this->getEntityCategory()
-    );
-
-    switch ($this->getEntityCategory()) {
-      case 'bundle':
-        $this->setEntityTypeId($this->entityHelper->getBundleEntityTypeId($form_entity));
-        $this->setBundleName($form_entity->id());
-        $this->setInstanceId(NULL);
-        break;
-
-      case 'instance':
-        $this->setEntityTypeId($entity_type_id);
-        $this->setBundleName($this->entityHelper->getEntityInstanceBundleName($form_entity));
-        // New menu link's id is '' instead of NULL, hence checking for empty.
-        $this->setInstanceId(!$this->entityIsNew() ? $form_entity->id() : NULL);
-        break;
-
-      default:
-        return FALSE;
-    }
-    return TRUE;
-  }
-
-  /**
-   * Gets the object entity of the form if available.
+   * Regenerates sitemaps according to user setting.
    *
-   * @return \Drupal\Core\Entity\EntityBase|false
-   *   Entity or FALSE if non-existent or if form operation is
-   *   'delete'.
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
    */
-  protected function getFormEntity() {
-    $form_object = $this->formState->getFormObject();
-    if (NULL !== $form_object
-      && method_exists($form_object, 'getOperation')
-      && method_exists($form_object, 'getEntity')
-      && in_array($form_object->getOperation(), self::$allowedFormOperations)) {
-      return $form_object->getEntity();
+  public function regenerateNowFormSubmit(array &$form, FormStateInterface $form_state) {
+    if ($form_state->getValue('simple_sitemap_regenerate_now')) {
+      $this->generator->setVariants()->rebuildQueue()->generate();
     }
-
-    return FALSE;
   }
 
   /**
-   * Removes gathered form information from service object.
+   * Returns a form to configure the sitemap settings.
    *
-   * Needed because this service may contain form info from the previous
-   * operation when revived from the container.
+   * @param array $form
+   *   The form where the settings form is being included in.
+   * @param array $settings
+   *   The sitemap settings.
    *
-   * @return $this
+   * @return array
+   *   The form elements for the sitemap settings.
    */
-  public function cleanUpFormInfo() {
-    $this->entityCategory = NULL;
-    $this->entityTypeId = NULL;
-    $this->bundleName = NULL;
-    $this->instanceId = NULL;
-    $this->settings = NULL;
-
-    return $this;
-  }
+  public function settingsForm(array $form, array $settings): array {
+    $form['#after_build'][] = [static::class, 'settingsFormStates'];
 
-  /**
-   * Checks if simple_sitemap values have been changed after submitting the form.
-   * To be used in an entity form submit.
-   *
-   * @param $form
-   * @param array $values
-   *
-   * @return bool
-   *   TRUE if simple_sitemap form values have been altered by the user.
-   *
-   * @todo Make it work with variants.
-   */
-  public function valuesChanged($form, array $values) {
-//    foreach (self::$valuesToCheck as $field_name) {
-//      if (!isset($form['simple_sitemap'][$field_name]['#default_value'])
-//        || (isset($values[$field_name]) && $values[$field_name] != $form['simple_sitemap'][$field_name]['#default_value'])) {
-//        return TRUE;
-//      }
-//    }
-//
-//    return FALSE;
-
-    return TRUE;
-  }
+    $form['index'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Index'),
+      '#default_value' => (int) ($settings['index'] ?? 0),
+    ];
+    $form['priority'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Priority'),
+      '#default_value' => $settings['priority'] ?? NULL,
+      '#options' => static::getPriorityOptions(),
+    ];
+    $form['changefreq'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Change frequency'),
+      '#default_value' => $settings['changefreq'] ?? NULL,
+      '#options' => static::getChangefreqOptions(),
+      '#empty_option' => $this->t('- Not specified -'),
+    ];
+    $form['include_images'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Include images'),
+      '#default_value' => (int) ($settings['include_images'] ?? 0),
+      '#options' => [$this->t('No'), $this->t('Yes')],
+    ];
 
-  /**
-   * Gets the values needed to display the variant dropdown setting.
-   *
-   * @return array
-   */
-  public function getVariantSelectValues() {
-    return array_map(
-      function($variant) { return $this->t($variant['label']); },
-      $this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE)
-    );
+    return $form;
   }
 
   /**
-   * Returns correct default value for variant select list.
-   *
-   * If only one variant is available, return it, otherwise check if a default
-   * variant is provided and return it.
-   *
-   * @param string|null $default_value
-   *  Actual default value from the database.
+   * After-build callback to set the correct #states.
    *
-   * @return string|null
-   *  Value to be set on form.
-   */
-  public function getVariantSelectValuesDefault($default_value) {
-    $options = $this->getVariantSelectValues();
-    return NULL === $default_value
-      ? (1 === count($options)
-        ? array_keys($options)[0]
-        : (!empty($default = $this->generator->getSetting('default_variant'))
-          ? $default
-          : $default_value
-        )
-      )
-      : $default_value;
-  }
-
-  /**
-   * Gets the values needed to display the priority dropdown setting.
+   * @param array $element
+   *   The element structure.
    *
    * @return array
+   *   The element structure.
    */
-  public function getPrioritySelectValues() {
-    $options = [];
-    foreach (range(0, self::PRIORITY_HIGHEST) as $value) {
-      $value = $this->formatPriority($value / self::PRIORITY_DIVIDER);
-      $options[$value] = $value;
+  public static function settingsFormStates(array $element): array {
+    $conditions = $element['index']['#type'] === 'checkbox' ? ['checked' => TRUE] : ['value' => 1];
+    $selector = ':input[name="' . $element['index']['#name'] . '"]';
+
+    foreach (Element::children($element) as $key) {
+      if ($key !== 'index') {
+        $element[$key]['#states']['visible'][$selector] = $conditions;
+      }
     }
 
-    return $options;
+    return $element;
   }
 
   /**
-   * Gets the values needed to display the changefreq dropdown setting.
+   * Gets the options for the priority dropdown setting.
    *
    * @return array
+   *   The options for the priority dropdown setting.
    */
-  public function getChangefreqSelectValues() {
-    $options = ['' => $this->t('- Not specified -')];
-    foreach (self::getChangefreqOptions() as $setting) {
-      $options[$setting] = $this->t($setting);
+  public static function getPriorityOptions(): array {
+    $options = [];
+
+    foreach (range(0, static::PRIORITY_HIGHEST) as $value) {
+      $value = static::formatPriority($value / static::PRIORITY_DIVIDER);
+      $options[$value] = $value;
     }
 
     return $options;
   }
 
   /**
+   * Gets the options for the changefreq dropdown setting.
+   *
    * @return array
-   */
-  public static function getChangefreqOptions() {
-    return self::$changefreqValues;
+   *   The options for the changefreq dropdown setting.
+   */
+  public static function getChangefreqOptions(): array {
+    return [
+      'always' => t('always'),
+      'hourly' => t('hourly'),
+      'daily' => t('daily'),
+      'weekly' => t('weekly'),
+      'monthly' => t('monthly'),
+      'yearly' => t('yearly'),
+      'never' => t('never'),
+    ];
   }
 
   /**
+   * Formats the given priority.
+   *
    * @param string $priority
+   *   The priority to format.
+   *
    * @return string
+   *   The formatted priority.
    */
-  public function formatPriority($priority) {
+  public static function formatPriority(string $priority): string {
     return number_format((float) $priority, 1, '.', '');
   }
 
   /**
+   * Validates the priority.
+   *
    * @param string|int $priority
+   *   The priority value.
+   *
    * @return bool
+   *   TRUE if the priority is valid.
    */
-  public static function isValidPriority($priority) {
+  public static function isValidPriority(string $priority): bool {
     return is_numeric($priority) && $priority >= 0 && $priority <= 1;
   }
 
   /**
+   * Validates the change frequency.
+   *
    * @param string $changefreq
+   *   The change frequency value.
+   *
    * @return bool
+   *   TRUE if the change frequency is valid.
    */
-  public static function isValidChangefreq($changefreq) {
-    return in_array($changefreq, self::$changefreqValues);
+  public static function isValidChangefreq(string $changefreq): bool {
+    return array_key_exists($changefreq, static::getChangefreqOptions());
   }
 
   /**
+   * Gets the cron intervals.
+   *
    * @return array
+   *   Cron intervals.
    */
-  public static function getCronIntervalOptions() {
+  public static function getCronIntervalOptions(): array {
     /** @var \Drupal\Core\Datetime\DateFormatter $formatter */
     $formatter = \Drupal::service('date.formatter');
-    $intervals = array_flip(self::$cronIntervals);
+    $intervals = array_flip(static::$cronIntervals);
     foreach ($intervals as $interval => &$label) {
       $label = $formatter->formatInterval($interval * 60 * 60);
     }
@@ -602,10 +413,4 @@ public static function getCronIntervalOptions() {
     return [0 => t('On every cron run')] + $intervals;
   }
 
-  /**
-   * @return string
-   */
-  public static function getDonationText() {
-    return '<div class="description">' . t('If you would like to say thanks and support the development of this module, a <a target="_blank" href="@url">donation</a> will be much appreciated.', ['@url' => 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5AFYRSBLGSC3W']) . '</div>';
-  }
 }
diff --git a/web/modules/simple_sitemap/src/Form/Handler/BundleEntityFormHandler.php b/web/modules/simple_sitemap/src/Form/Handler/BundleEntityFormHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..c7ab9367f0055a02a853823651f437b9528d5388
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/Handler/BundleEntityFormHandler.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form\Handler;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+
+/**
+ * Defines the handler for bundle entity forms.
+ */
+class BundleEntityFormHandler extends EntityFormHandlerBase {
+
+  use BundleEntityFormHandlerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form): array {
+    $form = parent::settingsForm($form);
+
+    if ($this->bundleName !== NULL) {
+      $bundle_label = $this->entityHelper
+        ->getBundleLabel($this->entityTypeId, $this->bundleName);
+    }
+
+    foreach (SimpleSitemap::loadMultiple() as $variant => $sitemap) {
+      $variant_form = &$form[$variant];
+
+      if (isset($bundle_label)) {
+        $variant_form['index']['#options'] = [
+          $this->t('Do not index entities of type <em>@bundle</em> in sitemap <em>@sitemap</em>', [
+            '@bundle' => $bundle_label,
+            '@sitemap' => $sitemap->label(),
+          ]),
+          $this->t('Index entities of type <em>@bundle</em> in sitemap <em>@sitemap</em>', [
+            '@bundle' => $bundle_label,
+            '@sitemap' => $sitemap->label(),
+          ]),
+        ];
+      }
+
+      $variant_form['priority']['#description'] = $this->t('The priority entities of this type will have in the eyes of search engine bots.');
+      $variant_form['changefreq']['#description'] = $this->t('The frequency with which entities of this type change. Search engine bots may take this as an indication of how often to index them.');
+      $variant_form['include_images']['#description'] = $this->t('Determines if images referenced by entities of this type should be included in the sitemap.');
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    // @todo No need to load all sitemaps here.
+    foreach (SimpleSitemap::loadMultiple() as $variant => $sitemap) {
+      $settings = $form_state->getValue(['simple_sitemap', $variant]);
+
+      // Variants may have changed since form load.
+      if ($settings) {
+        $this->generator
+          ->setVariants($variant)
+          ->entityManager()
+          ->setBundleSettings($this->entityTypeId, $this->bundleName, $settings);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/Handler/BundleEntityFormHandlerTrait.php b/web/modules/simple_sitemap/src/Form/Handler/BundleEntityFormHandlerTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e1d1b24d027cdcc8ded3f0a0a547954f4b88480
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/Handler/BundleEntityFormHandlerTrait.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form\Handler;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a trait for bundle entity forms.
+ */
+trait BundleEntityFormHandlerTrait {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntity(EntityInterface $entity) {
+    parent::setEntity($entity);
+
+    $this->entityTypeId = $this->entityHelper->getEntityBundleOf($entity);
+    $this->bundleName = $entity->id();
+
+    if ($this->entityTypeId === NULL) {
+      throw new \InvalidArgumentException('Entity does not provide bundles for another entity type');
+    }
+    if ($this->bundleName !== NULL) {
+      $this->bundleName = (string) $this->bundleName;
+    }
+
+    return $this;
+  }
+
+  /**
+   * Sets the entity type ID.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return $this
+   */
+  public function setEntityTypeId(string $entity_type_id): self {
+    $this->entityTypeId = $entity_type_id;
+    $this->entity = NULL;
+    return $this;
+  }
+
+  /**
+   * Sets the bundle name.
+   *
+   * @param string $bundle_name
+   *   The bundle name.
+   *
+   * @return $this
+   */
+  public function setBundleName(string $bundle_name): self {
+    $this->bundleName = $bundle_name;
+    $this->entity = NULL;
+    return $this;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandler.php b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9c2870b1ff83852f437cd358eed687dc0932ccd
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandler.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form\Handler;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Form\FormHelper;
+
+/**
+ * Defines the handler for entity forms.
+ */
+class EntityFormHandler extends EntityFormHandlerBase {
+
+  use EntityFormHandlerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $operations = ['default', 'edit', 'add', 'register'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formAlter(array &$form, FormStateInterface $form_state) {
+    parent::formAlter($form, $form_state);
+
+    $form['simple_sitemap']['#description'] = $this->t('Settings for this entity can be overridden here.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form): array {
+    $form = parent::settingsForm($form);
+
+    $settings = $this->getSettings();
+    $bundle_label = $this->entityHelper
+      ->getBundleLabel($this->entityTypeId, $this->bundleName);
+
+    foreach (SimpleSitemap::loadMultiple() as $variant => $sitemap) {
+      $variant_form = &$form[$variant];
+
+      $variant_form['index']['#options'] = [
+        $this->t('Do not index this <em>@bundle</em> entity in sitemap <em>@sitemap</em>', [
+          '@bundle' => $bundle_label,
+          '@sitemap' => $sitemap->label(),
+        ]),
+        $this->t('Index this <em>@bundle</em> entity in sitemap <em>@sitemap</em>', [
+          '@bundle' => $bundle_label,
+          '@sitemap' => $sitemap->label(),
+        ]),
+      ];
+
+      // Disable fields of entity instance whose bundle is not indexed.
+      $variant_form['#disabled'] = empty($settings[$variant]['bundle_settings']['index']);
+
+      $variant_form['priority']['#description'] = $this->t('The priority this <em>@bundle</em> entity will have in the eyes of search engine bots.', ['@bundle' => $bundle_label]);
+      $variant_form['changefreq']['#description'] = $this->t('The frequency with which this <em>@bundle</em> entity changes. Search engine bots may take this as an indication of how often to index it.', ['@bundle' => $bundle_label]);
+      $variant_form['include_images']['#description'] = $this->t('Determines if images referenced by this <em>@bundle</em> entity should be included in the sitemap.', ['@bundle' => $bundle_label]);
+
+      // Mark the default option.
+      if (isset($settings[$variant]['bundle_settings']['index'])) {
+        $value = (int) $settings[$variant]['bundle_settings']['index'];
+
+        if (isset($variant_form['index']['#options'][$value])) {
+          $variant_form['index']['#options'][$value] .= ' <em>(' . $this->t('default') . ')</em>';
+        }
+      }
+
+      // Mark the default option.
+      if (isset($settings[$variant]['bundle_settings']['priority'])) {
+        $value = FormHelper::formatPriority($settings[$variant]['bundle_settings']['priority']);
+
+        if (isset($variant_form['priority']['#options'][$value])) {
+          $variant_form['priority']['#options'][$value] .= ' (' . $this->t('default') . ')';
+        }
+      }
+
+      // Mark the default option.
+      if (isset($settings[$variant]['bundle_settings']['changefreq'])) {
+        $value = $settings[$variant]['bundle_settings']['changefreq'];
+
+        if (isset($variant_form['changefreq']['#options'][$value])) {
+          $variant_form['changefreq']['#options'][$value] .= ' (' . $this->t('default') . ')';
+        }
+        elseif ($value === '' && isset($variant_form['changefreq']['#empty_option'])) {
+          $variant_form['changefreq']['#empty_option'] .= ' (' . $this->t('default') . ')';
+        }
+      }
+
+      // Mark the default option.
+      if (isset($settings[$variant]['bundle_settings']['include_images'])) {
+        $value = (int) $settings[$variant]['bundle_settings']['include_images'];
+
+        if (isset($variant_form['include_images']['#options'][$value])) {
+          $variant_form['include_images']['#options'][$value] .= ' (' . $this->t('default') . ')';
+        }
+      }
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    // Make sure the entity is saved first for multi-step forms,
+    // see https://www.drupal.org/project/simple_sitemap/issues/3080510.
+    if ($this->entity->isNew()) {
+      return;
+    }
+
+    // @todo No need to load all sitemaps here.
+    foreach (SimpleSitemap::loadMultiple() as $variant => $sitemap) {
+      $settings = $form_state->getValue(['simple_sitemap', $variant]);
+
+      // Variants may have changed since form load.
+      if ($settings) {
+        $this->generator
+          ->setVariants($variant)
+          ->entityManager()
+          ->setEntityInstanceSettings($this->entityTypeId, $this->entity->id(), $settings);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSettings(): array {
+    if (!isset($this->settings)) {
+      // New menu link's id is '' instead of NULL, hence checking for empty.
+      $entity_id = !$this->entity->isNew() ? $this->entity->id() : NULL;
+
+      // @todo Simplify after getEntityInstanceSettings() works with multiple variants.
+      foreach (parent::getSettings() as $variant => $settings) {
+        if (NULL !== $entity_id) {
+          $this->settings[$variant] = $this->generator
+            ->setVariants($variant)
+            ->entityManager()
+            ->getEntityInstanceSettings($this->entityTypeId, $entity_id)[$variant];
+        }
+        $this->settings[$variant]['bundle_settings'] = $settings;
+      }
+    }
+    return $this->settings;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerBase.php b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..bcccb2a45c419e651aef6cc9ca196d312d9a32fb
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerBase.php
@@ -0,0 +1,268 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form\Handler;
+
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Entity\EntityFormInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Manager\Generator;
+use Drupal\simple_sitemap\Form\FormHelper;
+
+/**
+ * Defines a base class for altering an entity form.
+ */
+abstract class EntityFormHandlerBase implements EntityFormHandlerInterface {
+
+  use DependencySerializationTrait;
+  use StringTranslationTrait;
+
+  /**
+   * The sitemap generator service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\Generator
+   */
+  protected $generator;
+
+  /**
+   * Helper class for working with entities.
+   *
+   * @var \Drupal\simple_sitemap\Entity\EntityHelper
+   */
+  protected $entityHelper;
+
+  /**
+   * Helper class for working with forms.
+   *
+   * @var \Drupal\simple_sitemap\Form\FormHelper
+   */
+  protected $formHelper;
+
+  /**
+   * The entity being used by this form handler.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * The entity type ID.
+   *
+   * @var string|null
+   */
+  protected $entityTypeId;
+
+  /**
+   * The bundle name.
+   *
+   * @var string|null
+   */
+  protected $bundleName;
+
+  /**
+   * The sitemap settings.
+   *
+   * @var array
+   */
+  protected $settings;
+
+  /**
+   * Supported form operations.
+   *
+   * @var array
+   */
+  protected $operations = ['default', 'edit', 'add'];
+
+  /**
+   * EntityFormHandlerBase constructor.
+   *
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   */
+  public function __construct(Generator $generator, EntityHelper $entity_helper, FormHelper $form_helper) {
+    $this->generator = $generator;
+    $this->entityHelper = $entity_helper;
+    $this->formHelper = $form_helper;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.form_helper')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formAlter(array &$form, FormStateInterface $form_state) {
+    $this->processFormState($form_state);
+
+    $form['simple_sitemap'] = [
+      '#type' => 'details',
+      '#group' => 'advanced',
+      '#title' => $this->t('Simple XML Sitemap'),
+      '#attributes' => ['class' => ['simple-sitemap-fieldset']],
+      '#tree' => TRUE,
+      '#weight' => 10,
+    ];
+
+    // Only attach fieldset summary js to 'additional settings' vertical tabs.
+    if (isset($form['additional_settings'])) {
+      $form['simple_sitemap']['#attached']['library'][] = 'simple_sitemap/fieldsetSummaries';
+      $form['simple_sitemap']['#group'] = 'additional_settings';
+    }
+
+    $form['simple_sitemap'] = $this->settingsForm($form['simple_sitemap']);
+    $form['simple_sitemap'] = $this->formHelper->regenerateNowForm($form['simple_sitemap']);
+
+    $this->addSubmitHandlers($form, [$this, 'submitForm'],
+      [$this->formHelper, 'regenerateNowFormSubmit']
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form): array {
+    $sitemaps = SimpleSitemap::loadMultiple();
+    $settings = $this->getSettings();
+    if ($sitemaps) {
+      $form['#markup'] = '<strong>' . $this->t('Sitemaps') . '</strong>';
+    }
+    else {
+      $form['#markup'] = $this->t('At least one sitemap needs to be defined for a bundle to be indexable.<br>Sitemaps can be configured <a href="@url">here</a>.',
+        ['@url' => Url::fromRoute('entity.simple_sitemap.collection')->toString(),]
+      );
+    }
+
+    foreach ($sitemaps as $variant => $sitemap) {
+      $variant_form = &$form[$variant];
+
+      $variant_form = [
+        '#type' => 'details',
+        '#title' => '<em>' . $sitemap->label() . '</em>',
+        '#open' => !empty($settings[$variant]['index']),
+      ];
+
+      $variant_form = $this->formHelper
+        ->settingsForm($variant_form, $settings[$variant]);
+
+      $variant_form['index']['#attributes']['data-simple-sitemap-label'] = $sitemap->label();
+      $variant_form['index']['#type'] = 'radios';
+      $variant_form['index']['#title'] = NULL;
+
+      $variant_form['index']['#options'] = [
+        $this->t('Do not index entities of this type in sitemap <em>@sitemap</em>', ['@sitemap' => $sitemap->label()]),
+        $this->t('Index entities of this type in sitemap <em>@sitemap</em>', ['@sitemap' => $sitemap->label()]),
+      ];
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->processFormState($form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntity(EntityInterface $entity) {
+    $this->entity = $entity;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityTypeId(): ?string {
+    return $this->entityTypeId;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleName(): ?string {
+    return $this->bundleName;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSupportedOperation(string $operation): bool {
+    return in_array($operation, $this->operations, TRUE);
+  }
+
+  /**
+   * Retrieves data from form state.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \InvalidArgumentException
+   *   In case the form is not an entity form.
+   */
+  protected function processFormState(FormStateInterface $form_state) {
+    $form_object = $form_state->getFormObject();
+
+    if (!$form_object instanceof EntityFormInterface) {
+      throw new \InvalidArgumentException('Invalid form state');
+    }
+
+    $this->setEntity($form_object->getEntity());
+  }
+
+  /**
+   * Gets the sitemap settings.
+   *
+   * @return array
+   *   The sitemap settings.
+   */
+  protected function getSettings(): array {
+    if (!isset($this->settings)) {
+      $this->settings = $this->generator
+        ->setVariants()
+        ->entityManager()
+        ->getBundleSettings($this->entityTypeId, $this->bundleName);
+    }
+    return $this->settings;
+  }
+
+  /**
+   * Adds the submit handlers to the structured form array.
+   *
+   * @param array $element
+   *   An associative array containing the structure of the current element.
+   * @param callable ...$handlers
+   *   The submit handlers to add.
+   */
+  protected function addSubmitHandlers(array &$element, callable ...$handlers) {
+    // Add new handlers only if a handler for the 'save' action is present.
+    if (!empty($element['#submit']) && in_array('::save', $element['#submit'], TRUE)) {
+      array_push($element['#submit'], ...$handlers);
+    }
+
+    // Process child elements.
+    foreach (Element::children($element) as $key) {
+      $this->addSubmitHandlers($element[$key], ...$handlers);
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerInterface.php b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8465d2076c2527e3d95548e4da831e07291a172
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerInterface.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form\Handler;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines an interface for altering an entity form.
+ */
+interface EntityFormHandlerInterface extends ContainerInjectionInterface {
+
+  /**
+   * Alters the entity form to provide sitemap settings.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see simple_sitemap_form_alter()
+   * @see simple_sitemap_engines_form_alter()
+   */
+  public function formAlter(array &$form, FormStateInterface $form_state);
+
+  /**
+   * Returns a form to configure the sitemap settings.
+   *
+   * @param array $form
+   *   The form where the settings form is being included in.
+   *
+   * @return array
+   *   The form elements for the sitemap settings.
+   */
+  public function settingsForm(array $form): array;
+
+  /**
+   * Form submission handler.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state);
+
+  /**
+   * Sets the form entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity the current form should operate upon.
+   *
+   * @return $this
+   */
+  public function setEntity(EntityInterface $entity);
+
+  /**
+   * Gets the entity type ID.
+   *
+   * @return string|null
+   *   The entity type ID if available, or NULL otherwise.
+   */
+  public function getEntityTypeId(): ?string;
+
+  /**
+   * Gets the bundle name.
+   *
+   * @return string|null
+   *   The bundle name if available, or NULL otherwise.
+   */
+  public function getBundleName(): ?string;
+
+  /**
+   * Determines whether the specified form operation is supported.
+   *
+   * @param string $operation
+   *   The name of the operation.
+   *
+   * @return bool
+   *   TRUE if the form operation is supported, FALSE otherwise.
+   */
+  public function isSupportedOperation(string $operation): bool;
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerTrait.php b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..ff6f3abe8e89f7563c4de1c66a8d5d56e8168dd6
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/Handler/EntityFormHandlerTrait.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form\Handler;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a trait for entity form handlers.
+ */
+trait EntityFormHandlerTrait {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntity(EntityInterface $entity) {
+    parent::setEntity($entity);
+
+    $this->entityTypeId = $entity->getEntityTypeId();
+    $this->bundleName = $this->entityHelper->getEntityBundle($entity);
+
+    return $this;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/SimplesitemapSettingsForm.php b/web/modules/simple_sitemap/src/Form/SettingsForm.php
similarity index 60%
rename from web/modules/simple_sitemap/src/Form/SimplesitemapSettingsForm.php
rename to web/modules/simple_sitemap/src/Form/SettingsForm.php
index 3d65a9fb1883b974fb8227137b4f0fa5a0c50e5b..f3704f42e431fb4994c0875a654f5f31ed0f40b8 100644
--- a/web/modules/simple_sitemap/src/Form/SimplesitemapSettingsForm.php
+++ b/web/modules/simple_sitemap/src/Form/SettingsForm.php
@@ -2,36 +2,53 @@
 
 namespace Drupal\simple_sitemap\Form;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\simple_sitemap\Simplesitemap;
+use Drupal\simple_sitemap\Manager\Generator;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Language\LanguageManagerInterface;
 
 /**
- * Class SimplesitemapSettingsForm
- * @package Drupal\simple_sitemap\Form
+ * Provides form to manage settings.
  */
-class SimplesitemapSettingsForm extends SimplesitemapFormBase {
+class SettingsForm extends SimpleSitemapFormBase {
 
   /**
+   * The language manager.
+   *
    * @var \Drupal\Core\Language\LanguageManagerInterface
    */
   protected $languageManager;
 
   /**
-   * SimplesitemapSettingsForm constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   * SettingsForm constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
   public function __construct(
-    Simplesitemap $generator,
+    ConfigFactoryInterface $config_factory,
+    Generator $generator,
+    Settings $settings,
     FormHelper $form_helper,
     LanguageManagerInterface $language_manager
   ) {
     parent::__construct(
+      $config_factory,
       $generator,
+      $settings,
       $form_helper
     );
     $this->languageManager = $language_manager;
@@ -42,7 +59,9 @@ public function __construct(
    */
   public static function create(ContainerInterface $container) {
     return new static(
+      $container->get('config.factory'),
       $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.settings'),
       $container->get('simple_sitemap.form_helper'),
       $container->get('language_manager')
     );
@@ -51,65 +70,70 @@ public static function create(ContainerInterface $container) {
   /**
    * {@inheritdoc}
    */
-  public function getFormId() {
+  public function getFormId(): string {
     return 'simple_sitemap_settings_form';
   }
 
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state) {
+  public function buildForm(array $form, FormStateInterface $form_state): array {
 
-    $form['simple_sitemap_settings']['#prefix'] = FormHelper::getDonationText();
-
-    $form['simple_sitemap_settings']['settings'] = [
+    $form['settings'] = [
       '#type' => 'fieldset',
       '#title' => $this->t('Settings'),
     ];
 
-    $form['simple_sitemap_settings']['settings']['cron_generate'] = [
+    $form['settings']['cron_generate'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Regenerate the sitemaps during cron runs'),
       '#description' => $this->t('Uncheck this if you intend to only regenerate the sitemaps manually or via drush.'),
-      '#default_value' => $this->generator->getSetting('cron_generate', TRUE),
+      '#default_value' => $this->settings->getEditable('cron_generate', TRUE),
     ];
 
-    $form['simple_sitemap_settings']['settings']['cron_generate_interval'] = [
+    $form['settings']['cron_generate_interval'] = [
       '#type' => 'select',
       '#title' => $this->t('Sitemap generation interval'),
       '#description' => $this->t('The sitemap will be generated according to this interval.'),
-      '#default_value' => $this->generator->getSetting('cron_generate_interval', 0),
+      '#default_value' => $this->settings->getEditable('cron_generate_interval', 0),
       '#options' => FormHelper::getCronIntervalOptions(),
       '#states' => [
         'visible' => [':input[name="cron_generate"]' => ['checked' => TRUE]],
       ],
     ];
 
-    $form['simple_sitemap_settings']['settings']['xsl'] = [
+    $form['settings']['xsl'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Add styling and sorting to sitemaps'),
       '#description' => $this->t('If checked, sitemaps will be displayed as tables with sortable entries and thus become much friendlier towards human visitors. Search engines will not care.'),
-      '#default_value' => $this->generator->getSetting('xsl', TRUE),
+      '#default_value' => $this->settings->getEditable('xsl', FALSE),
+    ];
+
+    $form['settings']['hide_branding'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Hide branding'),
+      '#description' => $this->t("Remove the 'Generated by the Simple XML Sitemap Drupal module' string from the XSL output."),
+      '#default_value' => $this->settings->getEditable('hide_branding', FALSE),
     ];
 
-    $form['simple_sitemap_settings']['settings']['languages'] = [
+    $form['settings']['languages'] = [
       '#type' => 'details',
       '#title' => $this->t('Language settings'),
       '#open' => FALSE,
     ];
 
-    $form['simple_sitemap_settings']['settings']['languages']['disable_language_hreflang'] = [
+    $form['settings']['languages']['disable_language_hreflang'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Remove hreflang markup in HTML'),
       '#description' => $this->t('Google recommends displaying hreflang definitions either in the HTML markup or in the sitemap, but not in both places.<br>If checked, hreflang definitions created by the language module will be removed from the markup reducing its size.'),
-      '#default_value' => $this->generator->getSetting('disable_language_hreflang', FALSE),
+      '#default_value' => $this->settings->getEditable('disable_language_hreflang', FALSE),
     ];
 
-    $form['simple_sitemap_settings']['settings']['languages']['skip_untranslated'] = [
+    $form['settings']['languages']['skip_untranslated'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Skip non-existent translations'),
       '#description' => $this->t('If checked, entity links are generated exclusively for languages the entity has been translated to as long as the language is not excluded below.<br>Otherwise entity links are generated for every language installed on the site apart from languages excluded below.<br>Bear in mind that non-entity paths like homepage will always be generated for every non-excluded language.'),
-      '#default_value' => $this->generator->getSetting('skip_untranslated', FALSE),
+      '#default_value' => $this->settings->getEditable('skip_untranslated', FALSE),
     ];
 
     $language_options = [];
@@ -118,73 +142,78 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         $language_options[$language->getId()] = $language->getName();
       }
     }
-    $form['simple_sitemap_settings']['settings']['languages']['excluded_languages'] = [
+    $form['settings']['languages']['excluded_languages'] = [
       '#title' => $this->t('Exclude languages'),
       '#type' => 'checkboxes',
       '#options' => $language_options,
       '#description' => !empty($language_options)
         ? $this->t('There will be no links generated for languages checked here.')
-        : $this->t('There are no languages other than the default language <a href="@url">available</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/regional/language']),
-      '#default_value' => $this->generator->getSetting('excluded_languages', []),
+        : $this->t('There are no languages other than the default language available.'),
+      '#default_value' => $this->settings->get('excluded_languages', []),
     ];
 
-    $form['simple_sitemap_settings']['advanced'] = [
+    $form['advanced'] = [
       '#type' => 'details',
       '#title' => $this->t('Advanced settings'),
       '#open' => TRUE,
     ];
 
-    $variants = $this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE);
-    $default_variant = $this->generator->getSetting('default_variant');
-    $form['simple_sitemap_settings']['advanced']['default_variant'] = [
+    $sitemaps = SimpleSitemap::loadMultiple();
+    $default_variant = $this->settings->getEditable('default_variant');
+    $form['advanced']['default_variant'] = [
       '#type' => 'select',
-      '#title' => $this->t('Default sitemap variant'),
-      '#description' => $this->t('This sitemap variant will be available under <em>/sitemap.xml</em> in addition to its default path <em>/variant-name/sitemap.xml</em>.<br>Variants can be configured <a href="@url">here</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/variants']),
-      '#default_value' => isset($variants[$default_variant]) ? $default_variant : '',
-      '#options' => ['' => $this->t('- None -')] + array_map(function($variant) { return $this->t($variant['label']); }, $variants),
-      ];
+      '#title' => $this->t('Default sitemap'),
+      '#description' => $this->t('This sitemap will be available under <em>/sitemap.xml</em> in addition to its default path <em>/variant-name/sitemap.xml</em>.<br>Sitemaps can be configured <a href="@url">here</a>.',
+        ['@url' => Url::fromRoute('entity.simple_sitemap.collection')->toString()]
+      ),
+      '#default_value' => isset($sitemaps[$default_variant]) ? $default_variant : '',
+      '#options' => ['' => $this->t('- None -')] + array_map(function ($sitemap) {
+        return $sitemap->label();
+      }, $sitemaps),
+    ];
 
-    $form['simple_sitemap_settings']['advanced']['base_url'] = [
+    $form['advanced']['base_url'] = [
       '#type' => 'textfield',
       '#title' => $this->t('Default base URL'),
-      '#default_value' => $this->generator->getSetting('base_url', ''),
+      '#default_value' => $this->settings->getEditable('base_url', ''),
       '#size' => 30,
       '#description' => $this->t('On some hosting providers it is impossible to pass parameters to cron to tell Drupal which URL to bootstrap with. In this case the base URL of sitemap links can be overridden here.<br>Example: <em>@url</em>', ['@url' => $GLOBALS['base_url']]),
     ];
 
-    $form['simple_sitemap_settings']['advanced']['remove_duplicates'] = [
+    $form['advanced']['remove_duplicates'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Exclude duplicate links'),
-      '#description' => $this->t('Prevent per-sitemap variant duplicate links.<br>Unchecking this may help avoiding PHP memory errors on huge sites.'),
-      '#default_value' => $this->generator->getSetting('remove_duplicates', TRUE),
+      '#description' => $this->t('Prevent per-sitemap duplicate links.<br>Unchecking this may help avoiding PHP memory errors on huge sites.'),
+      '#default_value' => $this->settings->getEditable('remove_duplicates', TRUE),
     ];
 
-    $form['simple_sitemap_settings']['advanced']['max_links'] = [
+    $form['advanced']['max_links'] = [
       '#type' => 'number',
       '#title' => $this->t('Maximum links in a sitemap'),
       '#min' => 1,
       '#description' => $this->t('The maximum number of links one sitemap can hold. If more links are generated than set here, a sitemap index will be created and the links split into several sub-sitemaps.<br>50 000 links is the maximum Google will parse per sitemap, but choosing a lower value may be needed to avoid PHP memory errors on huge sites.<br>If left blank, all links will be shown on a single sitemap.'),
-      '#default_value' => $this->generator->getSetting('max_links'),
+      '#default_value' => $this->settings->getEditable('max_links', 2000),
     ];
 
-    $form['simple_sitemap_settings']['advanced']['generate_duration'] = [
+    $form['advanced']['generate_duration'] = [
       '#type' => 'number',
       '#title' => $this->t('Sitemap generation max duration'),
       '#min' => 1,
       '#description' => $this->t('The maximum duration <strong>in seconds</strong> the generation task can run during a single cron run or during one batch process iteration.<br>The higher the number, the quicker the generation process, but higher the risk of PHP timeout errors.'),
-      '#default_value' => $this->generator->getSetting('generate_duration', 10000) / 1000,
+      '#default_value' => $this->settings->getEditable('generate_duration', 10000) / 1000,
       '#required' => TRUE,
     ];
 
-    $form['simple_sitemap_settings']['advanced']['entities_per_queue_item'] = [
+    $form['advanced']['entities_per_queue_item'] = [
       '#type' => 'number',
       '#title' => $this->t('Entities per queue item'),
       '#min' => 1,
       '#description' => $this->t('The number of entities to process in each queue item.<br>Increasing this number will use more memory but will result in less queries improving generation speed.'),
-      '#default_value' => $this->generator->getSetting('entities_per_queue_item', 50),
+      '#default_value' => $this->settings->getEditable('entities_per_queue_item', 50),
     ];
 
-    $this->formHelper->displayRegenerateNow($form['simple_sitemap_settings']);
+    $form = $this->formHelper
+      ->regenerateNowForm($form);
 
     return parent::buildForm($form, $form_state);
   }
@@ -205,27 +234,23 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     foreach (['max_links',
-               'cron_generate',
-               'cron_generate_interval',
-               'remove_duplicates',
-               'skip_untranslated',
-               'xsl',
-               'base_url',
-               'default_variant',
-               'disable_language_hreflang',
-               'entities_per_queue_item'] as $setting_name) {
-      $this->generator->saveSetting($setting_name, $form_state->getValue($setting_name));
+      'cron_generate',
+      'cron_generate_interval',
+      'remove_duplicates',
+      'skip_untranslated',
+      'xsl',
+      'hide_branding',
+      'base_url',
+      'default_variant',
+      'disable_language_hreflang',
+      'entities_per_queue_item',
+    ] as $setting_name) {
+      $this->settings->save($setting_name, $form_state->getValue($setting_name));
     }
-    $this->generator->saveSetting('excluded_languages', array_filter($form_state->getValue('excluded_languages')));
-    $this->generator->saveSetting('generate_duration', $form_state->getValue('generate_duration') * 1000);
+    $this->settings->save('excluded_languages', array_filter($form_state->getValue('excluded_languages')));
+    $this->settings->save('generate_duration', $form_state->getValue('generate_duration') * 1000);
 
     parent::submitForm($form, $form_state);
-
-    // Regenerate sitemaps according to user setting.
-    if ($form_state->getValue('simple_sitemap_regenerate_now')) {
-      $this->generator->setVariants(TRUE)
-        ->rebuildQueue()
-        ->generateSitemap();
-    }
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Form/SimpleSitemapEntityForm.php b/web/modules/simple_sitemap/src/Form/SimpleSitemapEntityForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..ae62173b207f88756a82fe133ba61bf9edc75d96
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/SimpleSitemapEntityForm.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap\Entity\SimpleSitemapType;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Form handler for sitemap edit forms.
+ */
+class SimpleSitemapEntityForm extends EntityForm {
+
+  /**
+   * Entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * SimpleSitemapEntityForm constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
+   *   Entity type manager service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_manager) {
+    $this->entityTypeManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $this->entity->label(),
+    ];
+
+    $form['status'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enabled'),
+      '#description' => $this->t('Include this sitemap during generation'),
+      '#default_value' => $this->entity->isEnabled(),
+    ];
+
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#default_value' => $this->entity->id(),
+      '#disabled' => !$this->entity->isNew(),
+      '#maxlength' => EntityTypeInterface::ID_MAX_LENGTH,
+      '#required' => TRUE,
+      '#machine_name' => [
+        'exists' => '\Drupal\simple_sitemap\Entity\SimpleSitemap::load',
+        'replace_pattern' => '[^a-z0-9-_]+',
+        'replace' => '-',
+        'error' => $this->t('The sitemap ID will be part of the URL and can only contain lowercase letters, numbers, dashes and underscores.'),
+      ],
+      '#description' => $this->t('The sitemap ID will be part of the URL and can only contain lowercase letters, numbers, dashes and underscores.'),
+    ];
+
+    $form['type'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Sitemap type'),
+      '#options' => array_map(function ($sitemap_type) {
+        return $sitemap_type->label();
+      }, SimpleSitemapType::loadMultiple()),
+      '#default_value' => !$this->entity->isNew() ? $this->entity->getType()->id() : NULL,
+      '#required' => TRUE,
+      '#description' => $this->t('The sitemap\'s type defines its looks and content. Sitemaps types can be configured <a href="@url">here</a>.',
+        ['@url' => Url::fromRoute('entity.simple_sitemap_type.collection')->toString()]
+      ),
+    ];
+
+    $form['description'] = [
+      '#type' => 'textarea',
+      '#default_value' => $this->entity->get('description'),
+      '#title' => $this->t('Administrative description'),
+    ];
+
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    if ($this->entity->save() === SAVED_UPDATED) {
+      $this->messenger()->addStatus($this->t('Sitemap %label has been updated.', ['%label' => $this->entity->label()]));
+    }
+    else {
+      $this->messenger()->addStatus($this->t('Sitemap %label has been created.', ['%label' => $this->entity->label()]));
+    }
+
+    $form_state->setRedirectUrl($this->entity->toUrl('collection'));
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/SimpleSitemapFormBase.php b/web/modules/simple_sitemap/src/Form/SimpleSitemapFormBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..113b2beec567a68171ef7114ee7503800919ddc1
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/SimpleSitemapFormBase.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\simple_sitemap\Settings;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\simple_sitemap\Manager\Generator;
+
+/**
+ * Base class for Simple XML Sitemap forms.
+ */
+abstract class SimpleSitemapFormBase extends ConfigFormBase {
+
+  /**
+   * The sitemap generator service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\Generator
+   */
+  protected $generator;
+
+  /**
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
+   */
+  protected $settings;
+
+  /**
+   * Helper class for working with forms.
+   *
+   * @var \Drupal\simple_sitemap\Form\FormHelper
+   */
+  protected $formHelper;
+
+  /**
+   * SimpleSitemapFormBase constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    Generator $generator,
+    Settings $settings,
+    FormHelper $form_helper
+  ) {
+    $this->generator = $generator;
+    $this->settings = $settings;
+    $this->formHelper = $form_helper;
+
+    parent::__construct($config_factory);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.settings'),
+      $container->get('simple_sitemap.form_helper')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames(): array {
+    return ['simple_sitemap.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $this->formHelper->regenerateNowFormSubmit($form, $form_state);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/SimpleSitemapTypeEntityForm.php b/web/modules/simple_sitemap/src/Form/SimpleSitemapTypeEntityForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ccf25f2b24af107dba649b8cd240d248be147e5
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/SimpleSitemapTypeEntityForm.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Form handler for sitemap type edit forms.
+ */
+class SimpleSitemapTypeEntityForm extends EntityForm {
+
+  /**
+   * Entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The SitemapGenerator plugin manager.
+   *
+   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
+   */
+  protected $sitemapGeneratorManager;
+
+  /**
+   * The UrlGenerator plugin manager.
+   *
+   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
+   */
+  protected $urlGeneratorManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('plugin.manager.simple_sitemap.sitemap_generator'),
+      $container->get('plugin.manager.simple_sitemap.url_generator')
+    );
+  }
+
+  /**
+   * SimpleSitemapTypeEntityForm constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
+   *   Entity type manager service.
+   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager $sitemap_generator_manager
+   *   The SitemapGenerator plugin manager.
+   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager $url_generator_manager
+   *   The UrlGenerator plugin manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_manager, SitemapGeneratorManager $sitemap_generator_manager, UrlGeneratorManager $url_generator_manager) {
+    $this->entityTypeManager = $entity_manager;
+    $this->sitemapGeneratorManager = $sitemap_generator_manager;
+    $this->urlGeneratorManager = $url_generator_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $this->entity->label(),
+    ];
+
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#default_value' => $this->entity->id(),
+      '#disabled' => !$this->entity->isNew(),
+      '#maxlength' => EntityTypeInterface::ID_MAX_LENGTH,
+      '#required' => TRUE,
+      '#machine_name' => [
+        'exists' => '\Drupal\simple_sitemap\Entity\SimpleSitemapType::load',
+      ],
+    ];
+
+    $form['sitemap_generator'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Sitemap generator'),
+      '#options' => array_map(function ($sitemap_generator) {
+        return $sitemap_generator['label'];
+      }, $this->sitemapGeneratorManager->getDefinitions()),
+      '#default_value' => !$this->entity->isNew() ? $this->entity->get('sitemap_generator') : NULL,
+      '#required' => TRUE,
+      '#description' => $this->t('Sitemaps of this type will be built according to the sitemap generator plugin chosen here.'),
+    ];
+
+    $form['url_generators'] = [
+      '#type' => 'select',
+      '#title' => $this->t('URL generators'),
+      '#options' => array_map(function ($url_generator) {
+        return $url_generator['label'];
+      }, $this->urlGeneratorManager->getDefinitions()),
+      '#default_value' => !$this->entity->isNew() ? $this->entity->get('url_generators') : NULL,
+      '#multiple' => TRUE,
+      '#required' => TRUE,
+      '#description' => $this->t('Sitemaps of this type will be populated with URLs generated by these URL generator plugins.'),
+    ];
+
+    $form['description'] = [
+      '#type' => 'textarea',
+      '#default_value' => $this->entity->get('description'),
+      '#title' => $this->t('Administrative description'),
+    ];
+
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    if ($this->entity->save() === SAVED_UPDATED) {
+      $this->messenger()->addStatus($this->t('Sitemap type %label has been updated.', ['%label' => $this->entity->label()]));
+    }
+    else {
+      $this->messenger()->addStatus($this->t('Sitemap type %label has been created.', ['%label' => $this->entity->label()]));
+    }
+
+    $form_state->setRedirectUrl($this->entity->toUrl('collection'));
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Form/SimplesitemapCustomLinksForm.php b/web/modules/simple_sitemap/src/Form/SimplesitemapCustomLinksForm.php
deleted file mode 100644
index 5af84391dcfcd28b5ab7712239930c77a4c6f051..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Form/SimplesitemapCustomLinksForm.php
+++ /dev/null
@@ -1,229 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Form;
-
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\simple_sitemap\Simplesitemap;
-use Drupal\Core\Path\PathValidator;
-
-/**
- * Class SimplesitemapCustomLinksForm
- * @package Drupal\simple_sitemap\Form
- */
-class SimplesitemapCustomLinksForm extends SimplesitemapFormBase {
-
-  /**
-   * @var \Drupal\Core\Path\PathValidator
-   */
-  protected $pathValidator;
-
-  /**
-   * SimplesitemapCustomLinksForm constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
-   * @param \Drupal\Core\Path\PathValidator $path_validator
-   */
-  public function __construct(
-    Simplesitemap $generator,
-    FormHelper $form_helper,
-    PathValidator $path_validator
-  ) {
-    parent::__construct(
-      $generator,
-      $form_helper
-    );
-    $this->pathValidator = $path_validator;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('simple_sitemap.generator'),
-      $container->get('simple_sitemap.form_helper'),
-      $container->get('path.validator')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'simple_sitemap_custom_links_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-
-    $form['simple_sitemap_custom'] = [
-      '#title' => $this->t('Custom links'),
-      '#type' => 'fieldset',
-      '#markup' => '<div class="description">' . $this->t('Add custom internal drupal paths to the XML sitemap.') . '</div>',
-      '#prefix' => FormHelper::getDonationText(),
-    ];
-
-    $form['simple_sitemap_custom']['custom_links'] = [
-      '#type' => 'textarea',
-      '#title' => $this->t('Relative Drupal paths'),
-      '#default_value' => $this->customLinksToString($this->generator->setVariants(TRUE)->getCustomLinks(NULL, FALSE)),
-      '#description' => $this->t("Please specify drupal internal (relative) paths, one per line. Do not forget to prepend the paths with a '/'.<br>Optionally link priority <em>(0.0 - 1.0)</em> can be added by appending it after a space.<br> Optionally link change frequency <em>(always / hourly / daily / weekly / monthly / yearly / never)</em> can be added by appending it after a space.<br/<br><strong>Examples:</strong><br><em>/ 1.0 daily</em> -> home page with the highest priority and daily change frequency<br><em>/contact</em> -> contact page with the default priority and no change frequency information"),
-    ];
-
-    $form['simple_sitemap_custom']['variants'] = [
-      '#type' => 'select',
-      '#multiple' => TRUE,
-      '#title' => $this->t('Sitemap variants'),
-      '#description' => $this->t('The sitemap variants to include the above links in.<br>Variants can be configured <a href="@url">here</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/variants']),
-      '#options' => array_map(
-        function($variant) { return $this->t($variant['label']); },
-        $this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE)
-      ),
-      '#default_value' => array_keys(array_filter(
-          $this->generator->setVariants(TRUE)->getCustomLinks(NULL, FALSE, TRUE),
-          function($e) { return !empty($e);})
-      ),
-    ];
-
-    $form['simple_sitemap_custom']['include_images'] = [
-      '#type' => 'select',
-      '#title' => $this->t('Include images'),
-      '#description' => $this->t('If a custom link points to an entity, include its referenced images in the sitemap.'),
-      '#default_value' => $this->generator->getSetting('custom_links_include_images', FALSE),
-      '#options' => [0 => $this->t('No'), 1 => $this->t('Yes')],
-    ];
-
-    $this->formHelper->displayRegenerateNow($form['simple_sitemap_custom']);
-
-    return parent::buildForm($form, $form_state);
-  }
-
-  protected function negotiateVariant() {
-
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    if (!empty($form_state->getValue('custom_links')) && empty($form_state->getValue('variants'))) {
-      $form_state->setErrorByName('variants', $this->t('Custom links must be assigned to at least one sitemap variant.'));
-    }
-
-    foreach ($this->stringToCustomLinks($form_state->getValue('custom_links')) as $i => $link_config) {
-      $placeholders = [
-        '@line' => ++$i,
-        '@path' => $link_config['path'],
-        '@priority' => isset($link_config['priority']) ? $link_config['priority'] : '',
-        '@changefreq' => isset($link_config['changefreq']) ? $link_config['changefreq'] : '',
-        '@changefreq_options' => implode(', ', FormHelper::getChangefreqOptions()),
-      ];
-
-      // Checking if internal path exists.
-      if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($link_config['path'])
-      // Path validator does not see a double slash as an error. Catching this to prevent breaking path generation.
-       || strpos($link_config['path'], '//') !== FALSE) {
-        $form_state->setErrorByName('', $this->t('<strong>Line @line</strong>: The path <em>@path</em> does not exist.', $placeholders));
-      }
-
-      // Making sure the paths start with a slash.
-      if ($link_config['path'][0] !== '/') {
-        $form_state->setErrorByName('', $this->t("<strong>Line @line</strong>: The path <em>@path</em> needs to start with a '/'.", $placeholders));
-      }
-
-      // Making sure the priority is formatted correctly.
-      if (isset($link_config['priority']) && !FormHelper::isValidPriority($link_config['priority'])) {
-        $form_state->setErrorByName('', $this->t('<strong>Line @line</strong>: The priority setting <em>@priority</em> for path <em>@path</em> is incorrect. Set the priority from 0.0 to 1.0.', $placeholders));
-      }
-
-      // Making sure changefreq is formatted correctly.
-      if (isset($link_config['changefreq']) && !FormHelper::isValidChangefreq($link_config['changefreq'])) {
-        $form_state->setErrorByName('', $this->t('<strong>Line @line</strong>: The changefreq setting <em>@changefreq</em> for path <em>@path</em> is incorrect. The following are the correct values: <em>@changefreq_options</em>.', $placeholders));
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->generator->setVariants(TRUE)->removeCustomLinks();
-    if (!empty($variants = $form_state->getValue('variants')) && !empty($links = $form_state->getValue('custom_links'))) {
-      $this->generator->setVariants(array_values($variants));
-      foreach ($this->stringToCustomLinks($links) as $link_config) {
-        $this->generator->addCustomLink($link_config['path'], $link_config);
-      }
-    }
-
-    $this->generator->saveSetting('custom_links_include_images', (bool) $form_state->getValue('include_images'));
-    parent::submitForm($form, $form_state);
-
-    // Regenerate sitemaps according to user setting.
-    if ($form_state->getValue('simple_sitemap_regenerate_now')) {
-      $this->generator->setVariants(TRUE)
-        ->rebuildQueue()
-        ->generateSitemap();
-    }
-  }
-
-  /**
-   * @param $custom_links_string
-   * @return array
-   */
-  protected function stringToCustomLinks($custom_links_string) {
-
-    // Unify newline characters and explode into array.
-    $custom_links_string_lines = explode("\n", str_replace("\r\n", "\n", $custom_links_string));
-
-    // Remove empty values and whitespaces from array.
-    $custom_links_string_lines = array_filter(array_map('trim', $custom_links_string_lines));
-
-    $custom_links = [];
-    foreach ($custom_links_string_lines as $i => &$line) {
-      $link_settings = explode(' ', $line);
-      $custom_links[$i]['path'] = $link_settings[0];
-
-      // If two arguments are provided for a link, assume the first to be
-      // priority, the second to be changefreq.
-      if (!empty($link_settings[1]) && !empty($link_settings[2])) {
-        $custom_links[$i]['priority'] = $link_settings[1];
-        $custom_links[$i]['changefreq'] = $link_settings[2];
-      }
-      else {
-        // If one argument is provided for a link, guess if it is priority or
-        // changefreq.
-        if (!empty($link_settings[1])) {
-          if (is_numeric($link_settings[1])) {
-            $custom_links[$i]['priority'] = $link_settings[1];
-          }
-          else {
-            $custom_links[$i]['changefreq'] = $link_settings[1];
-          }
-        }
-      }
-    }
-    return $custom_links;
-  }
-
-  /**
-   * @param array $links
-   * @return string
-   */
-  protected function customLinksToString(array $links) {
-    $setting_string = '';
-    foreach ($links as $custom_link) {
-      $setting_string .= $custom_link['path'];
-      $setting_string .= isset($custom_link['priority'])
-        ? ' ' . $this->formHelper->formatPriority($custom_link['priority'])
-        : '';
-      $setting_string .= isset($custom_link['changefreq'])
-        ? ' ' . $custom_link['changefreq']
-        : '';
-      $setting_string .= "\r\n";
-    }
-    return $setting_string;
-  }
-}
diff --git a/web/modules/simple_sitemap/src/Form/SimplesitemapEntitiesForm.php b/web/modules/simple_sitemap/src/Form/SimplesitemapEntitiesForm.php
deleted file mode 100644
index 5bd498eae3fe384f41cde5daaf21ee740dee01f4..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Form/SimplesitemapEntitiesForm.php
+++ /dev/null
@@ -1,205 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Form;
-
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\simple_sitemap\Simplesitemap;
-use Drupal\simple_sitemap\EntityHelper;
-
-/**
- * Class SimplesitemapEntitiesForm
- * @package Drupal\simple_sitemap\Form
- */
-class SimplesitemapEntitiesForm extends SimplesitemapFormBase {
-
-  /**
-   * @var \Drupal\simple_sitemap\EntityHelper
-   */
-  protected $entityHelper;
-
-  /**
-   * SimplesitemapEntitiesForm constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
-   * @param \Drupal\simple_sitemap\EntityHelper $entity_helper
-   */
-  public function __construct(
-    Simplesitemap $generator,
-    FormHelper $form_helper,
-    EntityHelper $entity_helper
-  ) {
-    parent::__construct(
-      $generator,
-      $form_helper
-    );
-    $this->entityHelper = $entity_helper;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('simple_sitemap.generator'),
-      $container->get('simple_sitemap.form_helper'),
-      $container->get('simple_sitemap.entity_helper')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'simple_sitemap_entities_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-
-    $form['simple_sitemap_entities']['#prefix'] = FormHelper::getDonationText();
-
-    $form['simple_sitemap_entities']['entities'] = [
-      '#title' => $this->t('Sitemap entities'),
-      '#type' => 'fieldset',
-      '#markup' => '<div class="description">' . $this->t('Simple XML Sitemap settings will be added only to entity forms of entity types enabled here. For all entity types featuring bundles (e.g. <em>node</em>) sitemap settings have to be set on their bundle pages (e.g. <em>page</em>).') . '</div>',
-    ];
-
-    $form['#attached']['library'][] = 'simple_sitemap/sitemapEntities';
-    $form['#attached']['drupalSettings']['simple_sitemap'] = ['all_entities' => [], 'atomic_entities' => []];
-
-    $variants = $this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE);
-    $all_bundle_settings = $this->generator->setVariants(TRUE)->getBundleSettings(NULL, NULL, TRUE, TRUE);
-    $indexed_bundles = [];
-    foreach ($all_bundle_settings as $variant => $entity_types) {
-      foreach ($entity_types as $entity_type_name => $bundles) {
-        foreach ($bundles as $bundle_name => $bundle_settings) {
-          if (!empty($bundle_settings['index'])) {
-            $indexed_bundles[$entity_type_name][$bundle_name]['variants'][] = $this->t($variants[$variant]['label']);
-            $indexed_bundles[$entity_type_name][$bundle_name]['bundle_label'] = $this->entityHelper->getBundleLabel($entity_type_name, $bundle_name);
-          }
-        }
-      }
-    }
-
-    $entity_type_labels = [];
-    foreach ($this->entityHelper->getSupportedEntityTypes() as $entity_type_id => $entity_type) {
-      $entity_type_labels[$entity_type_id] = $entity_type->getLabel() ? : $entity_type_id;
-    }
-    asort($entity_type_labels);
-
-    foreach ($entity_type_labels as $entity_type_id => $entity_type_label) {
-      $enabled_entity_type = $this->generator->entityTypeIsEnabled($entity_type_id);
-      $atomic_entity_type = $this->entityHelper->entityTypeIsAtomic($entity_type_id);
-      $css_entity_type_id = str_replace('_', '-', $entity_type_id);
-
-      $form['simple_sitemap_entities']['entities'][$entity_type_id] = [
-        '#type' => 'details',
-        '#title' => $entity_type_label,
-        '#open' => $enabled_entity_type,
-      ];
-
-      $form['simple_sitemap_entities']['entities'][$entity_type_id][$entity_type_id . '_enabled'] = [
-        '#type' => 'checkbox',
-        '#title' => $this->t('Enable @entity_type_label <em>(@entity_type_id)</em> support', ['@entity_type_label' => $entity_type_label, '@entity_type_id' => $entity_type_id]),
-        '#description' => $atomic_entity_type
-          ? $this->t('Sitemap settings for the entity type <em>@entity_type_label</em> can be set below and overridden on its entity pages.', ['@entity_type_label' => $entity_type_label])
-          : $this->t('Sitemap settings for the entity type <em>@entity_type_label</em> can be set on its bundle pages and overridden on its entity pages.', ['@entity_type_label' => $entity_type_label]),
-        '#default_value' => $enabled_entity_type,
-      ];
-
-      if ($form['simple_sitemap_entities']['entities'][$entity_type_id][$entity_type_id . '_enabled']['#default_value']) {
-
-        $indexed_bundles_string = '';
-        if (isset($indexed_bundles[$entity_type_id])) {
-          foreach ($indexed_bundles[$entity_type_id] as $bundle => $bundle_data) {
-            $indexed_bundles_string .= '<br><em>' . $bundle_data['bundle_label'] . '</em> <span class="description">(' . $this->t('sitemap variants') . ': <em>' . implode(', ', $bundle_data['variants']) . '</em>)</span>';
-          }
-        }
-
-        $bundle_info = '';
-        if (!$atomic_entity_type) {
-          $bundle_info .= '<div id="indexed-bundles-' . $css_entity_type_id . '">'
-            . (!empty($indexed_bundles_string)
-              ? $this->t("<em>@entity_type_label</em> bundles set to be indexed:", ['@entity_type_label' => $entity_type_label]) . ' ' . $indexed_bundles_string
-              : $this->t('No <em>@entity_type_label</em> bundles are set to be indexed yet.', ['@entity_type_label' => $entity_type_label]))
-            . '</div>';
-        }
-
-        if (!empty($indexed_bundles_string)) {
-          $bundle_info .= '<div id="warning-' . $css_entity_type_id . '">'
-            . ($atomic_entity_type
-              ? $this->t("<strong>Warning:</strong> This entity type's sitemap settings including per-entity overrides will be deleted after hitting <em>Save</em>.")
-              : $this->t("<strong>Warning:</strong> The sitemap settings and any per-entity overrides will be deleted for the following bundles:" . $indexed_bundles_string))
-            . '</div>';
-        }
-
-        $form['simple_sitemap_entities']['entities'][$entity_type_id][$entity_type_id . '_enabled']['#suffix'] = $bundle_info;
-      }
-
-      $form['#attached']['drupalSettings']['simple_sitemap']['all_entities'][] = $css_entity_type_id;
-
-      if ($atomic_entity_type) {
-        $form['simple_sitemap_entities']['entities'][$entity_type_id][$entity_type_id . '_settings']['#prefix'] = '<div id="indexed-bundles-' . $css_entity_type_id . '">';
-        $form['simple_sitemap_entities']['entities'][$entity_type_id][$entity_type_id . '_settings']['#suffix'] = '</div>';
-
-        $this->formHelper
-          ->cleanUpFormInfo()
-          ->setEntityCategory('bundle')
-          ->setEntityTypeId($entity_type_id)
-          ->setBundleName($entity_type_id)
-          ->negotiateSettings()
-          ->displayEntitySettings(
-            $form['simple_sitemap_entities']['entities'][$entity_type_id][$entity_type_id . '_settings']
-          );
-      }
-    }
-
-    $this->formHelper->displayRegenerateNow($form['simple_sitemap_entities']['entities']);
-
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $values = $form_state->getValues();
-    foreach ($values as $field_name => $value) {
-      if (substr($field_name, -strlen('_enabled')) === '_enabled') {
-        $entity_type_id = substr($field_name, 0, -8);
-        if ($value) {
-          $this->generator->enableEntityType($entity_type_id);
-          if ($this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
-            foreach ($this->generator->getSitemapManager()->getSitemapVariants(NULL, FALSE) as $variant => $definition) {
-              if (isset($values['index_' . $variant . '_' . $entity_type_id . '_settings'])) {
-                $this->generator
-                  ->setVariants($variant)
-                  ->setBundleSettings($entity_type_id, $entity_type_id, [
-                    'index' => (bool) $values['index_' . $variant . '_' . $entity_type_id . '_settings'],
-                    'priority' => $values['priority_' . $variant . '_' . $entity_type_id . '_settings'],
-                    'changefreq' => $values['changefreq_' . $variant . '_' . $entity_type_id . '_settings'],
-                    'include_images' => (bool) $values['include_images_' . $variant . '_' . $entity_type_id . '_settings'],
-                    ]);
-              }
-            }
-          }
-        }
-        else {
-          $this->generator->disableEntityType($entity_type_id);
-        }
-      }
-    }
-    parent::submitForm($form, $form_state);
-
-    // Regenerate sitemaps according to user setting.
-    if ($form_state->getValue('simple_sitemap_regenerate_now')) {
-      $this->generator->setVariants(TRUE)
-        ->rebuildQueue()
-        ->generateSitemap();
-    }
-  }
-
-}
diff --git a/web/modules/simple_sitemap/src/Form/SimplesitemapFormBase.php b/web/modules/simple_sitemap/src/Form/SimplesitemapFormBase.php
deleted file mode 100644
index ac5118d41172ad317934e524cb01e5a4afdedc13..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Form/SimplesitemapFormBase.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Form;
-
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Form\ConfigFormBase;
-use Drupal\simple_sitemap\Simplesitemap;
-
-/**
- * Class SimplesitemapFormBase
- * @package Drupal\simple_sitemap\Form
- */
-abstract class SimplesitemapFormBase extends ConfigFormBase {
-
-  /**
-   * @var \Drupal\simple_sitemap\Simplesitemap
-   */
-  protected $generator;
-
-  /**
-   * @var \Drupal\simple_sitemap\Form\FormHelper
-   */
-  protected $formHelper;
-
-  /**
-   * SimplesitemapFormBase constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
-   */
-  public function __construct(
-    Simplesitemap $generator,
-    FormHelper $form_helper
-  ) {
-    $this->generator = $generator;
-    $this->formHelper = $form_helper;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('simple_sitemap.generator'),
-      $container->get('simple_sitemap.form_helper')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getEditableConfigNames() {
-    return ['simple_sitemap.settings'];
-  }
-
-}
diff --git a/web/modules/simple_sitemap/src/Form/SimplesitemapSitemapsForm.php b/web/modules/simple_sitemap/src/Form/SimplesitemapSitemapsForm.php
deleted file mode 100644
index 7a8d98a05e2ce3a5332884824b2ff2c49bab1e21..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Form/SimplesitemapSitemapsForm.php
+++ /dev/null
@@ -1,254 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Form;
-
-use Drupal\Core\Datetime\DateFormatter;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\simple_sitemap\Simplesitemap;
-use Drupal\Core\Database\Connection;
-
-/**
- * Class SimplesitemapSitemapsForm
- * @package Drupal\simple_sitemap\Form
- */
-class SimplesitemapSitemapsForm extends SimplesitemapFormBase {
-
-  /**
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $db;
-
-  /**
-   * @var \Drupal\Core\Datetime\DateFormatter
-   */
-  protected $dateFormatter;
-
-  /**
-   * SimplesitemapSitemapsForm constructor.
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
-   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
-   * @param \Drupal\Core\Database\Connection $database
-   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
-   */
-  public function __construct(
-    Simplesitemap $generator,
-    FormHelper $form_helper,
-    Connection $database,
-    DateFormatter $date_formatter
-  ) {
-    parent::__construct(
-      $generator,
-      $form_helper
-    );
-    $this->db = $database;
-    $this->dateFormatter = $date_formatter;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('simple_sitemap.generator'),
-      $container->get('simple_sitemap.form_helper'),
-      $container->get('database'),
-      $container->get('date.formatter')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'simple_sitemap_sitemaps_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-
-    $form['simple_sitemap_settings']['#prefix'] = FormHelper::getDonationText();
-    $form['simple_sitemap_settings']['#attached']['library'][] = 'simple_sitemap/sitemaps';
-    $queue_worker = $this->generator->getQueueWorker();
-
-    $form['simple_sitemap_settings']['status'] = [
-      '#type' => 'fieldset',
-      '#title' => $this->t('Sitemap status'),
-      '#markup' => '<div class="description">' . $this->t('Sitemaps can be regenerated on demand here.') . '</div>',
-      '#description' => $this->t('Variants can be configured <a href="@url">here</a>.', ['@url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/variants']),
-    ];
-
-    $form['simple_sitemap_settings']['status']['actions'] = [
-      '#prefix' => '<div class="clearfix"><div class="form-item">',
-      '#suffix' => '</div></div>',
-    ];
-
-    $form['simple_sitemap_settings']['status']['actions']['rebuild_queue_submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Rebuild queue'),
-      '#submit' => ['::rebuildQueue'],
-      '#validate' => [],
-    ];
-
-    $form['simple_sitemap_settings']['status']['actions']['regenerate_submit'] = [
-      '#type' => 'submit',
-      '#value' => $queue_worker->generationInProgress()
-        ? $this->t('Resume generation')
-        : $this->t('Rebuild queue & generate'),
-      '#submit' => ['::generateSitemap'],
-      '#validate' => [],
-    ];
-
-    $form['simple_sitemap_settings']['status']['progress'] = [
-      '#prefix' => '<div class="clearfix">',
-      '#suffix' => '</div>',
-    ];
-
-    $form['simple_sitemap_settings']['status']['progress']['title']['#markup'] = $this->t('Progress of sitemap regeneration');
-
-    $total_count = $queue_worker->getInitialElementCount();
-    if (!empty($total_count)) {
-      $indexed_count = $queue_worker->getProcessedElementCount();
-      $percent = round(100 * $indexed_count / $total_count);
-
-      // With all results processed, there still may be some stashed results to be indexed.
-      $percent = $percent === 100 && $queue_worker->generationInProgress() ? 99 : $percent;
-
-      $index_progress = [
-        '#theme' => 'progress_bar',
-        '#percent' => $percent,
-        '#message' => $this->t('@indexed out of @total queue items have been processed.<br>Each sitemap variant is published after all of its items have been processed.', ['@indexed' => $indexed_count, '@total' => $total_count]),
-      ];
-      $form['simple_sitemap_settings']['status']['progress']['bar']['#markup'] = render($index_progress);
-    }
-    else {
-      $form['simple_sitemap_settings']['status']['progress']['bar']['#markup'] = '<div class="description">' . $this->t('There are no items to be indexed.') . '</div>';
-    }
-
-    $sitemap_manager = $this->generator->getSitemapManager();
-    $sitemap_settings = [
-      'base_url' => $this->generator->getSetting('base_url', ''),
-      'default_variant' => $this->generator->getSetting('default_variant', NULL),
-    ];
-    $sitemap_statuses = $this->fetchSitemapInstanceInfo();
-    $published_timestamps = $this->fetchSitemapInstancePublishedTimestamps();
-    foreach ($sitemap_manager->getSitemapTypes() as $type_name => $type_definition) {
-      if (!empty($variants = $sitemap_manager->getSitemapVariants($type_name, FALSE))) {
-        $sitemap_generator = $sitemap_manager
-          ->getSitemapGenerator($type_definition['sitemapGenerator'])
-          ->setSettings($sitemap_settings);
-
-        $form['simple_sitemap_settings']['status']['types'][$type_name] = [
-          '#type' => 'details',
-          '#title' => '<em>' . $type_definition['label'] . '</em> ' . $this->t('sitemaps'),
-          '#open' => !empty($variants) && count($variants) <= 5,
-          '#description' => !empty($type_definition['description']) ? '<div class="description">' . $type_definition['description'] . '</div>' : '',
-        ];
-        $form['simple_sitemap_settings']['status']['types'][$type_name]['table'] = [
-          '#type' => 'table',
-          '#header' => [$this->t('Variant'), $this->t('Status'), $this->t('Link count')],
-          '#attributes' => ['class' => ['form-item', 'clearfix']],
-        ];
-        foreach ($variants as $variant_name => $variant_definition) {
-          if (!isset($sitemap_statuses[$variant_name])) {
-            $row['name']['data']['#markup'] = '<span title="' . $variant_name . '">' . $this->t($variant_definition['label']) . '</span>';
-            $row['status'] = $this->t('pending');
-            $row['count'] = '';
-          }
-          else {
-            switch ($sitemap_statuses[$variant_name]['status']) {
-
-              case 0:
-                $row['name']['data']['#markup'] = '<span title="' . $variant_name . '">' . $this->t($variant_definition['label']) . '</span>';
-                $row['status'] = $this->t('generating');
-                $row['count'] = '';
-                break;
-
-              case 1:
-              case 2:
-                $row['name']['data']['#markup'] = $this->t('<a href="@url" target="_blank">@variant</a>',
-                  ['@url' => $sitemap_generator->setSitemapVariant($variant_name)->getSitemapUrl(), '@variant' => $this->t($variant_definition['label'])]
-                );
-                $row['status'] = $this->t(($sitemap_statuses[$variant_name]['status'] === 1
-                  ? 'published on @time'
-                  : 'published on @time, regenerating'
-                ), ['@time' => $this->dateFormatter->format($published_timestamps[$variant_name])]);
-                // Once the sitemap has been regenerated after
-                // simple_sitemap_update_8305() there will always be a link
-                // count.
-                $row['count'] = $sitemap_statuses[$variant_name]['link_count'] > 0
-                  ? $sitemap_statuses[$variant_name]['link_count']
-                  : $this->t('unavailable');
-                break;
-            }
-          }
-          $form['simple_sitemap_settings']['status']['types'][$type_name]['table']['#rows'][$variant_name] = isset($row) ? $row : [];
-          unset($sitemap_statuses[$variant_name]);
-        }
-      }
-    }
-    if (empty($form['simple_sitemap_settings']['status']['types'])) {
-      $form['simple_sitemap_settings']['status']['types']['#markup'] = $this->t('No variants have been defined');
-    }
-
-    return $form;
-  }
-
-  /**
-   * @return array
-   *  Array of sitemap statuses and link counts keyed by variant name.
-   *  Status values:
-   *  0: Instance is unpublished
-   *  1: Instance is published
-   *  2: Instance is published but is being regenerated
-   *
-   * @todo Implement SitemapGeneratorBase::isPublished() per sitemap instead or at least return a constant.
-   */
-  protected function fetchSitemapInstanceInfo() {
-    $results = $this->db
-      ->query('SELECT type, status, SUM(link_count) as link_count FROM {simple_sitemap} GROUP BY type, status ORDER BY type, status ASC')
-      ->fetchAll();
-
-    $instance_info = [];
-    foreach ($results as $i => $result) {
-      $instance_info[$result->type] = [
-        'status' => isset($instance_info[$result->type]) ? $result->status + 1 : (int) $result->status,
-        'link_count' => (int) $result->link_count,
-      ];
-    }
-
-    return $instance_info;
-  }
-
-  /**
-   * @return array
-   *
-   * @todo Implement SitemapGeneratorBase::getPublishedTimestamp() per sitemap instead or at least return a constant.
-   */
-  protected function fetchSitemapInstancePublishedTimestamps() {
-    return $this->db
-      ->query('SELECT type, MAX(sitemap_created) FROM (SELECT sitemap_created, type FROM {simple_sitemap} WHERE status = :status) AS timestamps GROUP BY type', [':status' => 1])
-      ->fetchAllKeyed(0, 1);
-  }
-
-  /**
-   * @param array $form
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function generateSitemap(array &$form, FormStateInterface $form_state) {
-    $this->generator->generateSitemap();
-  }
-
-  /**
-   * @param array $form
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function rebuildQueue(array &$form, FormStateInterface $form_state) {
-    $this->generator->rebuildQueue();
-  }
-
-}
diff --git a/web/modules/simple_sitemap/src/Form/SimplesitemapVariantsForm.php b/web/modules/simple_sitemap/src/Form/SimplesitemapVariantsForm.php
deleted file mode 100644
index 0bba1066db018973090f9e3dbde578584b547d47..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Form/SimplesitemapVariantsForm.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\simple_sitemap\SimplesitemapManager;
-
-/**
- * Class SimplesitemapVariantsForm
- * @package Drupal\simple_sitemap\Form
- */
-class SimplesitemapVariantsForm extends SimplesitemapFormBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'simple_sitemap_variants_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-
-    $form['simple_sitemap_variants'] = [
-      '#title' => $this->t('Sitemap variants'),
-      '#type' => 'fieldset',
-      '#markup' => '<div class="description">' . $this->t('Define sitemap variants. A sitemap variant is a sitemap instance of a certain type (specific sitemap generator and URL generators) accessible under a certain URL.<br>Each variant can have its own entity bundle settings (to be defined on bundle edit pages).') . '</div>',
-      '#prefix' => FormHelper::getDonationText(),
-    ];
-
-    $form['simple_sitemap_variants']['variants'] = [
-      '#type' => 'textarea',
-      '#title' => $this->t('Variants'),
-      '#default_value' => $this->variantsToString($this->generator->getSitemapManager()->getSitemapVariants(NULL, TRUE)),
-      '#description' => $this->t("Please specify sitemap variants, one per line. <strong>Caution: </strong>Removing variants here will delete their bundle settings, custom links and corresponding sitemap instances.<br><br>A variant definition consists of the variant name (used as the variant's path), the sitemap type it belongs to (optional) and the variant label (optional). These three values have to be separated by the | pipe | symbol.<br><br><strong>Examples:</strong><br><em>default | default_hreflang | Default</em> -> variant of the <em>default_hreflang</em> sitemap type and <em>Default</em> as label; accessible under <em>/default/sitemap.xml</em><br><em>test</em> -> variant of the <em>@default_sitemap_type</em> sitemap type and <em>test</em> as label; accessible under <em>/test/sitemap.xml</em><br><br><strong>Available sitemap types:</strong>", ['@default_sitemap_type' => SimplesitemapManager::DEFAULT_SITEMAP_TYPE]),
-    ];
-
-    foreach ($this->generator->getSitemapManager()->getSitemapTypes() as $sitemap_type => $definition) {
-      $form['simple_sitemap_variants']['variants']['#description'] .= '<br>' . '<em>' . $sitemap_type . '</em>' . (!empty($definition['description']) ? (': ' . $definition['description']) : '');
-    }
-
-    $this->formHelper->displayRegenerateNow($form['simple_sitemap_custom']);
-
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * @todo Show multiple errors at once.
-   * @todo Allow numeric variant names, but bear in mind that they are stored as integer array keys due to how php arrays work.
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    $line = 0;
-    $sitemap_types = $this->generator->getSitemapManager()->getSitemapTypes();
-    foreach ($this->stringToVariants($form_state->getValue('variants')) as $variant_name => $variant_definition) {
-      $placeholders = [
-        '@line' => ++$line,
-        '@name' => $variant_name,
-        '@type' => isset($variant_definition['type']) ? $variant_definition['type'] : '',
-        '@label' => isset($variant_definition['label']) ? $variant_definition['label'] : '',
-      ];
-
-      if (trim($variant_name) === '') {
-        $form_state->setErrorByName('', $this->t("<strong>Line @line</strong>: The variant name cannot be empty.", $placeholders));
-      }
-
-      if (!preg_match('/^[\w\-_]+$/', $variant_name)) {
-        $form_state->setErrorByName('', $this->t("<strong>Line @line</strong>: The variant name <em>@name</em> can only include alphanumeric characters, dashes and underscores.", $placeholders));
-      }
-
-      if (is_numeric($variant_name)) {
-        $form_state->setErrorByName('', $this->t("<strong>Line @line</strong>: The variant name cannot be numeric.", $placeholders));
-      }
-
-      if (!isset($sitemap_types[$variant_definition['type']])) {
-        $form_state->setErrorByName('', $this->t("<strong>Line @line</strong>: The variant <em>@name</em> is of a sitemap type <em>@type</em> that does not exist.", $placeholders));
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $manager = $this->generator->getSitemapManager();
-    $new_variants = $this->stringToVariants($form_state->getValue('variants'));
-    $remove_variants = array_values(array_diff(
-      array_keys($manager->getSitemapVariants(NULL, FALSE)),
-      array_keys($new_variants)
-    ));
-    $manager->removeSitemapVariants($remove_variants);
-    $weight = 0;
-    foreach ($new_variants as $variant_name => $variant_definition) {
-      $manager->addSitemapVariant($variant_name, $variant_definition + ['weight' => $weight]);
-      $weight++;
-    }
-
-    parent::submitForm($form, $form_state);
-
-    // Regenerate sitemaps according to user setting.
-    if ($form_state->getValue('simple_sitemap_regenerate_now')) {
-      $this->generator->setVariants(TRUE)
-        ->rebuildQueue()
-        ->generateSitemap();
-    }
-  }
-
-  /**
-   * @param $variant_string
-   * @return array
-   */
-  protected function stringToVariants($variant_string) {
-
-    // Unify newline characters and explode into array.
-    $variants_string_lines = explode("\n", str_replace("\r\n", "\n", $variant_string));
-
-    // Remove empty values and whitespaces from array.
-    $variants_string_lines = array_filter(array_map('trim', $variants_string_lines));
-
-    $variants = [];
-    foreach ($variants_string_lines as $i => &$line) {
-      $variant_settings = explode('|', $line);
-      $name = strtolower(trim($variant_settings[0]));
-      $variants[$name]['type'] = !empty($variant_settings[1]) ? trim($variant_settings[1]) : SimplesitemapManager::DEFAULT_SITEMAP_TYPE;
-      $variants[$name]['label'] = !empty($variant_settings[2]) ? trim($variant_settings[2]) : $name;
-    }
-
-    return $variants;
-  }
-
-  /**
-   * @param array $variants
-   * @return string
-   */
-  protected function variantsToString(array $variants) {
-    $variants_string = '';
-    foreach ($variants as $variant_name => $variant_definition) {
-      $variants_string .= $variant_name
-        . ' | ' . $variant_definition['type']
-        . ' | ' . $variant_definition['label']
-        . "\r\n";
-    }
-
-    return $variants_string;
-  }
-}
diff --git a/web/modules/simple_sitemap/src/Form/StatusForm.php b/web/modules/simple_sitemap/src/Form/StatusForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..53312950e4cdbfed890274e296bdaf4df7039b2c
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Form/StatusForm.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace Drupal\simple_sitemap\Form;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Datetime\DateFormatter;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\simple_sitemap\Queue\QueueWorker;
+use Drupal\simple_sitemap\Settings;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\simple_sitemap\Manager\Generator as SimplesitemapOld;
+use Drupal\Core\Database\Connection;
+
+/**
+ * Provides form to manage sitemap status.
+ */
+class StatusForm extends SimpleSitemapFormBase {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $db;
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatter
+   */
+  protected $dateFormatter;
+
+  /**
+   * The simple_sitemap.queue_worker service.
+   *
+   * @var \Drupal\simple_sitemap\Queue\QueueWorker
+   */
+  protected $queueWorker;
+
+  /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * StatusForm constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\simple_sitemap\Manager\Generator $generator
+   *   The sitemap generator service.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Form\FormHelper $form_helper
+   *   Helper class for working with forms.
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection.
+   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
+   *   The date formatter service.
+   * @param \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker
+   *   The simple_sitemap.queue_worker service.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    SimplesitemapOld $generator,
+    Settings $settings,
+    FormHelper $form_helper,
+    Connection $database,
+    DateFormatter $date_formatter,
+    QueueWorker $queue_worker,
+    RendererInterface $renderer
+  ) {
+    parent::__construct(
+      $config_factory,
+      $generator,
+      $settings,
+      $form_helper
+    );
+    $this->db = $database;
+    $this->dateFormatter = $date_formatter;
+    $this->queueWorker = $queue_worker;
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('simple_sitemap.generator'),
+      $container->get('simple_sitemap.settings'),
+      $container->get('simple_sitemap.form_helper'),
+      $container->get('database'),
+      $container->get('date.formatter'),
+      $container->get('simple_sitemap.queue_worker'),
+      $container->get('renderer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'simple_sitemap_status_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state): array {
+
+    $form['#attached']['library'][] = 'simple_sitemap/sitemaps';
+
+    $form['status'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Sitemap status'),
+      '#markup' => '<div class="description">' . $this->t('Sitemaps can be regenerated on demand here.') . '</div>',
+    ];
+
+    $form['status']['actions'] = [
+      '#prefix' => '<div class="clearfix"><div class="form-item">',
+      '#suffix' => '</div></div>',
+    ];
+
+    $form['status']['actions']['rebuild_queue_submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Rebuild queue'),
+      '#submit' => [self::class . '::rebuildQueue'],
+      '#validate' => [],
+    ];
+
+    $form['status']['actions']['regenerate_submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->queueWorker->generationInProgress()
+        ? $this->t('Resume generation')
+        : $this->t('Rebuild queue & generate'),
+      '#submit' => [self::class . '::generate'],
+      '#validate' => [],
+    ];
+
+    $form['status']['progress'] = [
+      '#prefix' => '<div class="clearfix">',
+      '#suffix' => '</div>',
+    ];
+
+    $form['status']['progress']['title']['#markup'] = $this->t('Progress of sitemap regeneration');
+
+    $total_count = $this->queueWorker->getInitialElementCount();
+    if ($total_count > 0) {
+      $indexed_count = $this->queueWorker->getProcessedElementCount();
+      $percent = round(100 * $indexed_count / $total_count);
+
+      // With all results processed, there still may be some stashed results to
+      // be indexed.
+      $percent = $percent == 100 && $this->queueWorker->generationInProgress() ? 99 : $percent;
+
+      $index_progress = [
+        '#theme' => 'progress_bar',
+        '#percent' => $percent,
+        '#message' => $this->t('@indexed out of @total queue items have been processed.<br>Each sitemap is published after all of its items have been processed.', [
+          '@indexed' => $indexed_count,
+          '@total' => $total_count,
+        ]),
+      ];
+      $form['status']['progress']['bar']['#markup'] = $this->renderer->render($index_progress);
+    }
+    else {
+      $form['status']['progress']['bar']['#markup'] = '<div class="description">' . $this->t('There are no items to be indexed.') . '</div>';
+    }
+
+    return $form;
+  }
+
+  /**
+   * Generates the sitemap content.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public static function generate(array &$form, FormStateInterface $form_state): void {
+    /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
+    $generator = \Drupal::service('simple_sitemap.generator');
+    $generator->generate();
+  }
+
+  /**
+   * Rebuilds the queue.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public static function rebuildQueue(array &$form, FormStateInterface $form_state): void {
+    /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
+    $generator = \Drupal::service('simple_sitemap.generator');
+    $generator->rebuildQueue();
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Logger.php b/web/modules/simple_sitemap/src/Logger.php
index 081c8ee87691ef936ed406290893bef81de99bf8..d441ec9a1392f2e6ea89d9e075825fabb4713cfd 100644
--- a/web/modules/simple_sitemap/src/Logger.php
+++ b/web/modules/simple_sitemap/src/Logger.php
@@ -8,53 +8,66 @@
 use Psr\Log\LoggerInterface;
 
 /**
- * Class Logger
- * @package Drupal\simple_sitemap
+ * Simple XML Sitemap logger.
  */
 class Logger {
 
   use StringTranslationTrait;
 
-  /*
+  /**
    * Can be debug/info/notice/warning/error.
    */
-  const LOG_SEVERITY_LEVEL_DEFAULT = 'notice';
+  protected const LOG_SEVERITY_LEVEL_DEFAULT = 'notice';
 
-  /*
+  /**
    * Can be status/warning/error.
    */
-  const DISPLAY_MESSAGE_TYPE_DEFAULT = 'status';
+  protected const DISPLAY_MESSAGE_TYPE_DEFAULT = 'status';
 
   /**
+   * A logger instance.
+   *
    * @var \Psr\Log\LoggerInterface
    */
   protected $logger;
 
   /**
+   * The messenger.
+   *
    * @var \Drupal\Core\Messenger\MessengerInterface
    */
   protected $messenger;
 
   /**
+   * The current user.
+   *
    * @var \Drupal\Core\Session\AccountProxyInterface
    */
   protected $currentUser;
 
   /**
+   * The actual message.
+   *
    * @var string
    */
   protected $message = '';
 
   /**
+   * The actual substitutions.
+   *
    * @var array
    */
   protected $substitutions = [];
 
   /**
    * Logger constructor.
+   *
    * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
    * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger.
    * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   The current user.
    */
   public function __construct(
     LoggerInterface $logger,
@@ -67,36 +80,52 @@ public function __construct(
   }
 
   /**
-   * @param $message
+   * Sets the message with substitutions.
+   *
+   * @param string $message
+   *   Message to set.
    * @param array $substitutions
+   *   Substitutions to set.
+   *
    * @return $this
    */
-  public function m($message, $substitutions = []) {
+  public function m(string $message, array $substitutions = []): Logger {
     $this->message = $message;
     $this->substitutions = $substitutions;
     return $this;
   }
 
   /**
+   * Logs with an arbitrary level.
+   *
    * @param string $logSeverityLevel
+   *   The severity level.
+   *
    * @return $this
    */
-  public function log($logSeverityLevel = self::LOG_SEVERITY_LEVEL_DEFAULT) {
+  public function log(string $logSeverityLevel = self::LOG_SEVERITY_LEVEL_DEFAULT): Logger {
     $this->logger->$logSeverityLevel(strtr($this->message, $this->substitutions));
 
     return $this;
   }
 
   /**
+   * Displays the message given the right permission.
+   *
    * @param string $displayMessageType
+   *   The message's type.
    * @param string $permission
+   *   The permission to check for.
+   *
    * @return $this
    */
-  public function display($displayMessageType = self::DISPLAY_MESSAGE_TYPE_DEFAULT, $permission = '') {
+  public function display(string $displayMessageType = self::DISPLAY_MESSAGE_TYPE_DEFAULT, string $permission = ''): Logger {
     if (empty($permission) || $this->currentUser->hasPermission($permission)) {
+      // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
       $this->messenger->addMessage($this->t($this->message, $this->substitutions), $displayMessageType);
     }
 
     return $this;
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Manager/CustomLinkManager.php b/web/modules/simple_sitemap/src/Manager/CustomLinkManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbfe1824d0f08f1fae42d89abf02633f4e761a69
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Manager/CustomLinkManager.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace Drupal\simple_sitemap\Manager;
+
+use Drupal\Core\Path\PathValidatorInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+
+/**
+ * The simple_sitemap.custom_link_manager service.
+ */
+class CustomLinkManager {
+
+  use VariantSetterTrait;
+  use LinkSettingsTrait;
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The path validator service.
+   *
+   * @var \Drupal\Core\Path\PathValidatorInterface
+   */
+  protected $pathValidator;
+
+  /**
+   * Default link settings.
+   *
+   * @var array
+   */
+  protected static $linkSettingDefaults = [
+    'priority' => '0.5',
+    'changefreq' => '',
+  ];
+
+  /**
+   * CustomLinkManager constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
+   *   The path validator service.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    PathValidatorInterface $path_validator
+  ) {
+    $this->configFactory = $config_factory;
+    $this->pathValidator = $path_validator;
+  }
+
+  /**
+   * Stores a custom path along with its settings to configuration.
+   *
+   * Does so for the currently set variants.
+   *
+   * @param string $path
+   *   The path to add.
+   * @param array $settings
+   *   Settings that are not provided are supplemented by defaults.
+   *
+   * @return $this
+   *
+   * @todo Validate $settings.
+   */
+  public function add(string $path, array $settings = []): CustomLinkManager {
+    if (empty($variants = $this->getVariants())) {
+      return $this;
+    }
+
+    if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($path)) {
+      throw new \InvalidArgumentException("The path '$path' must be local and known to Drupal.");
+    }
+    if ($path[0] !== '/') {
+      throw new \InvalidArgumentException("The path '$path' must start with a '/'.");
+    }
+
+    $variant_links = $this->get();
+    foreach ($variants as $variant) {
+      $links = [];
+      $link_key = 0;
+      if (isset($variant_links[$variant])) {
+        $links = $variant_links[$variant];
+        $link_key = count($links);
+        foreach ($links as $key => $link) {
+          if ($link['path'] === $path) {
+            $link_key = $key;
+            break;
+          }
+        }
+      }
+
+      $links[$link_key] = ['path' => $path] + $settings;
+      $this->configFactory->getEditable("simple_sitemap.custom_links.$variant")
+        ->set('links', $links)->save();
+    }
+
+    return $this;
+  }
+
+  /**
+   * Gets custom link settings for the currently set variants.
+   *
+   * @param string|null $path
+   *   Limits the result set by an internal path.
+   *
+   * @return array
+   *   An array of custom link settings keyed by variant name.
+   */
+  public function get(?string $path = NULL): array {
+    $all_custom_links = [];
+    foreach ($this->getVariants() as $variant) {
+      $custom_links = $this->configFactory
+        ->get("simple_sitemap.custom_links.$variant")
+        ->get('links');
+
+      $custom_links = $custom_links ?: [];
+
+      if ($custom_links && $path !== NULL) {
+        foreach ($custom_links as $key => $link) {
+          if ($link['path'] !== $path) {
+            unset($custom_links[$key]);
+          }
+        }
+      }
+
+      foreach ($custom_links as $i => $link_settings) {
+        self::supplementDefaultSettings($link_settings);
+        $custom_links[$i] = $link_settings;
+      }
+
+      $custom_links = $path !== NULL && $custom_links
+        ? array_values($custom_links)[0]
+        : array_values($custom_links);
+
+      if ($custom_links) {
+        $all_custom_links[$variant] = $custom_links;
+      }
+    }
+
+    return $all_custom_links;
+  }
+
+  /**
+   * Removes custom links from currently set variants.
+   *
+   * @param array|string|null $paths
+   *   Limits the removal to certain paths.
+   *
+   * @return $this
+   */
+  public function remove($paths = NULL): CustomLinkManager {
+    if (empty($variants = $this->getVariants())) {
+      return $this;
+    }
+
+    if (NULL === $paths) {
+      foreach ($variants as $variant) {
+        $this->configFactory
+          ->getEditable("simple_sitemap.custom_links.$variant")->delete();
+      }
+    }
+    else {
+      $variant_links = $this->get();
+      foreach ($variant_links as $variant => $links) {
+        $custom_links = $links;
+        $save = FALSE;
+        foreach ((array) $paths as $path) {
+          foreach ($custom_links as $key => $link) {
+            if ($link['path'] === $path) {
+              unset($custom_links[$key]);
+              $save = TRUE;
+              break 2;
+            }
+          }
+        }
+        if ($save) {
+          $this->configFactory->getEditable("simple_sitemap.custom_links.$variant")
+            ->set('links', array_values($custom_links))->save();
+        }
+      }
+    }
+
+    return $this;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Manager/EntityManager.php b/web/modules/simple_sitemap/src/Manager/EntityManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..78d34a75553bfb6dfab83deccb5e100bff3d981b
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Manager/EntityManager.php
@@ -0,0 +1,598 @@
+<?php
+
+namespace Drupal\simple_sitemap\Manager;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Settings;
+
+/**
+ * The simple_sitemap.entity_manager service.
+ */
+class EntityManager {
+
+  use VariantSetterTrait;
+  use LinkSettingsTrait;
+
+  /**
+   * Default link settings.
+   *
+   * @var array
+   */
+  protected static $linkSettingDefaults = [
+    'index' => FALSE,
+    'priority' => '0.5',
+    'changefreq' => '',
+    'include_images' => FALSE,
+  ];
+
+  /**
+   * Helper class for working with entities.
+   *
+   * @var \Drupal\simple_sitemap\Entity\EntityHelper
+   */
+  protected $entityHelper;
+
+  /**
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
+   */
+  protected $settings;
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * Simplesitemap constructor.
+   *
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
+   */
+  public function __construct(
+    EntityHelper $entity_helper,
+    Settings $settings,
+    ConfigFactoryInterface $config_factory,
+    Connection $database,
+    EntityTypeManagerInterface $entity_type_manager,
+    EntityFieldManagerInterface $entity_field_manager
+  ) {
+    $this->entityHelper = $entity_helper;
+    $this->settings = $settings;
+    $this->configFactory = $config_factory;
+    $this->database = $database;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityFieldManager = $entity_field_manager;
+  }
+
+  /**
+   * Enables sitemap support for an entity type.
+   *
+   * Enabled entity types show
+   * sitemap settings on their bundle setting forms. If an enabled entity type
+   * features bundles (e.g. 'node'), it needs to be set up with
+   * setBundleSettings() as well.
+   *
+   * @param string $entity_type_id
+   *   Entity type ID.
+   *
+   * @return $this
+   */
+  public function enableEntityType(string $entity_type_id): EntityManager {
+    $enabled_entity_types = $this->settings->get('enabled_entity_types', []);
+
+    if (!in_array($entity_type_id, $enabled_entity_types, TRUE)) {
+      $enabled_entity_types[] = $entity_type_id;
+      $this->settings->save('enabled_entity_types', $enabled_entity_types);
+
+      // Clear necessary caches to apply field definition updates.
+      // @see simple_sitemap_entity_extra_field_info()
+      $this->entityFieldManager->clearCachedFieldDefinitions();
+    }
+
+    return $this;
+  }
+
+  /**
+   * Disables sitemap support for an entity type.
+   *
+   * Disabling support for an entity type deletes its sitemap settings
+   * permanently and removes sitemap settings from entity forms.
+   *
+   * @param string $entity_type_id
+   *   Entity type ID.
+   *
+   * @return $this
+   */
+  public function disableEntityType(string $entity_type_id): EntityManager {
+
+    // Updating settings.
+    $enabled_entity_types = $this->settings->get('enabled_entity_types', []);
+    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types, TRUE))) {
+      unset($enabled_entity_types[$key]);
+      $this->settings->save('enabled_entity_types', array_values($enabled_entity_types));
+
+      // Clear necessary caches to apply field definition updates.
+      // @see simple_sitemap_entity_extra_field_info()
+      $this->entityFieldManager->clearCachedFieldDefinitions();
+    }
+
+    // Deleting inclusion settings.
+    foreach ($this->configFactory->listAll('simple_sitemap.bundle_settings.') as $config_name) {
+      if (explode('.', $config_name)[3] === $entity_type_id) {
+        $this->configFactory->getEditable($config_name)->delete();
+      }
+    }
+
+    // @todo Implement hook to be used inside simple_sitemap_engines?
+    // Deleting inclusion settings for simple_sitemap_engines.
+    foreach ($this->configFactory->listAll('simple_sitemap_engines.bundle_settings.') as $config_name) {
+      if (explode('.', $config_name)[2] === $entity_type_id) {
+        $this->configFactory->getEditable($config_name)->delete();
+      }
+    }
+
+    // Deleting entity overrides.
+    $this->setVariants()->removeEntityInstanceSettings($entity_type_id);
+
+    return $this;
+  }
+
+  /**
+   * Sets settings for bundle or non-bundle entity types.
+   *
+   * This is done for the currently set variant. Note that this method takes
+   * only the first set variant into account.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string|null $bundle_name
+   *   The bundle of the entity.
+   * @param array $settings
+   *   Settings to set.
+   *
+   * @return $this
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   *
+   * @todo Make work for multiple variants.
+   * @todo Throw exception on non-existing entity type/bundle.
+   */
+  public function setBundleSettings(string $entity_type_id, ?string $bundle_name = NULL, array $settings = ['index' => TRUE]): EntityManager {
+    if (empty($variants = $this->getVariants())) {
+      return $this;
+    }
+    if (!isset($this->entityHelper->getSupportedEntityTypes()[$entity_type_id])) {
+      return $this;
+    }
+
+    // @todo Not working with menu link content.
+    // phpcs:disable
+//    if ($bundle_name && !isset($this->entityHelper->getBundleInfo($entity_type_id)[$bundle_name])) {
+//      return $this;
+//    }
+    // phpcs:enable
+
+    $bundle_name = $bundle_name ?? $entity_type_id;
+
+    if ($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name)) {
+      $old_settings = reset($old_settings);
+      $settings = array_merge($old_settings, $settings);
+    }
+    self::supplementDefaultSettings($settings);
+
+    if ($settings != $old_settings) {
+
+      // Save new bundle settings to configuration.
+      $bundle_settings = $this->configFactory
+        ->getEditable("simple_sitemap.bundle_settings.$variants[0].$entity_type_id.$bundle_name");
+      foreach ($settings as $setting_key => $setting) {
+        $bundle_settings->set($setting_key, $setting);
+      }
+      $bundle_settings->save();
+
+      if (empty($entity_ids = $this->entityHelper->getEntityInstanceIds($entity_type_id, $bundle_name))) {
+        return $this;
+      }
+
+      // Delete all entity overrides in case bundle indexation is disabled.
+      if (empty($settings['index'])) {
+        $this->removeEntityInstanceSettings($entity_type_id, $entity_ids);
+
+        return $this;
+      }
+
+      // Delete entity overrides which are identical to new bundle settings.
+      // @todo Enclose into some sensible method.
+      $query = $this->database->select('simple_sitemap_entity_overrides', 'o')
+        ->fields('o', ['id', 'inclusion_settings'])
+        ->condition('o.entity_type', $entity_type_id)
+        ->condition('o.type', $variants[0]);
+      if (!empty($entity_ids)) {
+        $query->condition('o.entity_id', $entity_ids, 'IN');
+      }
+
+      $delete_instances = [];
+      foreach ($query->execute()->fetchAll() as $result) {
+        $delete = TRUE;
+        $instance_settings = unserialize($result->inclusion_settings);
+        foreach ($instance_settings as $setting_key => $instance_setting) {
+          if ($instance_setting != $settings[$setting_key]) {
+            $delete = FALSE;
+            break;
+          }
+        }
+        if ($delete) {
+          $delete_instances[] = $result->id;
+        }
+      }
+      if (!empty($delete_instances)) {
+
+        // @todo Use removeEntityInstanceSettings() instead.
+        $this->database->delete('simple_sitemap_entity_overrides')
+          ->condition('id', $delete_instances, 'IN')
+          ->execute();
+      }
+    }
+
+    return $this;
+  }
+
+  /**
+   * Gets sitemap settings for an entity type (bundle).
+   *
+   * This is done for the currently set variants.
+   *
+   * @param string $entity_type_id
+   *   Limit the result set to a specific entity type.
+   * @param string|null $bundle_name
+   *   Limit the result set to a specific bundle name.
+   *
+   * @return array
+   *   An array of settings keyed by variant name.
+   *
+   * @todo Throw exception on non-existing entity type/bundle.
+   */
+  public function getBundleSettings(string $entity_type_id, ?string $bundle_name = NULL): array {
+    if (!isset($this->entityHelper->getSupportedEntityTypes()[$entity_type_id])) {
+      return [];
+    }
+    // @todo Not working with menu link content.
+    // phpcs:disable
+//    if ($bundle_name && !isset($this->entityHelper->getBundleInfo($entity_type_id)[$bundle_name])) {
+//      return [];
+//    }
+    // phpcs:enable
+    $bundle_name = $bundle_name ?? $entity_type_id;
+    $all_bundle_settings = [];
+
+    foreach ($this->getVariants() as $variant) {
+      $bundle_settings = $this->configFactory
+        ->get("simple_sitemap.bundle_settings.$variant.$entity_type_id.$bundle_name")
+        ->get();
+
+      if (empty($bundle_settings)) {
+        self::supplementDefaultSettings($bundle_settings);
+      }
+      $all_bundle_settings[$variant] = $bundle_settings;
+    }
+
+    return $all_bundle_settings;
+  }
+
+  /**
+   * Gets settings for all entity types (bundles).
+   *
+   * This is done for the currently set variants.
+   *
+   * @return array
+   *   An array of settings keyed by variant name, entity type and bundle names.
+   */
+  public function getAllBundleSettings() {
+    $all_bundle_settings = [];
+    foreach ($this->getVariants() as $variant) {
+      $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$variant.");
+      $bundle_settings = [];
+      foreach ($config_names as $config_name) {
+        $config_name_parts = explode('.', $config_name);
+        $bundle_settings[$config_name_parts[3]][$config_name_parts[4]] = $this->configFactory->get($config_name)->get();
+      }
+
+      // Supplement default bundle settings for all bundles not found in
+      // simple_sitemap.bundle_settings.*.* configuration.
+      foreach ($this->entityHelper->getSupportedEntityTypes() as $type_id => $type_definition) {
+        foreach ($this->entityHelper->getBundleInfo($type_id) as $bundle => $bundle_definition) {
+          if (!isset($bundle_settings[$type_id][$bundle])) {
+            self::supplementDefaultSettings($bundle_settings[$type_id][$bundle]);
+          }
+        }
+      }
+      $all_bundle_settings[$variant] = $bundle_settings;
+    }
+
+    return $all_bundle_settings;
+  }
+
+  /**
+   * Removes settings for bundle or a non-bundle entity types.
+   *
+   * This is done for the currently set variants.
+   *
+   * @param string|null $entity_type_id
+   *   Limit the removal to a specific entity type.
+   * @param string|null $bundle_name
+   *   Limit the removal to a specific bundle name.
+   *
+   * @return $this
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function removeBundleSettings(?string $entity_type_id = NULL, ?string $bundle_name = NULL): EntityManager {
+    if (empty($variants = $this->getVariants())) {
+      return $this;
+    }
+
+    if (NULL !== $entity_type_id) {
+      $bundle_name = $bundle_name ?? $entity_type_id;
+
+      foreach ($variants as $variant) {
+        $this->configFactory
+          ->getEditable("simple_sitemap.bundle_settings.$variant.$entity_type_id.$bundle_name")->delete();
+      }
+
+      if (!empty($entity_ids = $this->entityHelper->getEntityInstanceIds($entity_type_id, $bundle_name))) {
+        $this->removeEntityInstanceSettings($entity_type_id, $entity_ids);
+      }
+    }
+    else {
+      foreach ($variants as $variant) {
+        $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$variant.");
+        foreach ($config_names as $config_name) {
+          $this->configFactory->getEditable($config_name)->delete();
+        }
+      }
+      $this->removeEntityInstanceSettings();
+    }
+
+    return $this;
+  }
+
+  /**
+   * Overrides settings for a single entity for the currently set variants.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $id
+   *   The entity identifier.
+   * @param array $settings
+   *   Settings to set.
+   *
+   * @return $this
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   *
+   * @todo Pass entity object instead of id and entity type?
+   */
+  public function setEntityInstanceSettings(string $entity_type_id, string $id, array $settings): EntityManager {
+    if (empty($variants = $this->getVariants())) {
+      return $this;
+    }
+
+    if (($entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id)) === NULL) {
+      // @todo Exception.
+      return $this;
+    }
+
+    $all_bundle_settings = $this->getBundleSettings(
+      $entity_type_id, $this->entityHelper->getEntityBundle($entity)
+    );
+
+    foreach ($all_bundle_settings as $variant => $bundle_settings) {
+      if (!empty($bundle_settings)) {
+
+        // Only one variant at a time.
+        $this->setVariants($variant);
+
+        // Check if overrides are different from bundle setting before saving.
+        $override = FALSE;
+        foreach ($settings as $key => $setting) {
+          if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
+            $override = TRUE;
+            break;
+          }
+        }
+
+        // Save overrides for this entity if something is different.
+        if ($override) {
+          $this->database->merge('simple_sitemap_entity_overrides')
+            ->keys([
+              'type' => $variant,
+              'entity_type' => $entity_type_id,
+              'entity_id' => $id,
+            ])
+            ->fields([
+              'type' => $variant,
+              'entity_type' => $entity_type_id,
+              'entity_id' => $id,
+              'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),
+            ])
+            ->execute();
+        }
+        // Else unset override.
+        else {
+          $this->removeEntityInstanceSettings($entity_type_id, $id);
+        }
+      }
+    }
+
+    // Restore original variants.
+    $this->setVariants($variants);
+
+    return $this;
+  }
+
+  /**
+   * Gets sitemap settings for an entity instance.
+   *
+   * If instance-specific setting overrides are not saved, returns bundle
+   * settings. This is done for the currently set variant.
+   * Please note, this method takes only the first set
+   * variant into account.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $id
+   *   The entity identifier.
+   *
+   * @return array|false
+   *   Array of entity instance settings or the settings of its bundle. False if
+   *   entity or variant does not exist.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   *
+   * @todo Make work for multiple variants.
+   * @todo Pass entity object instead of id and entity type?
+   */
+  public function getEntityInstanceSettings(string $entity_type_id, string $id) {
+    if (empty($variants = $this->getVariants())) {
+      return FALSE;
+    }
+    $variant = reset($variants);
+
+    $results = $this->database->select('simple_sitemap_entity_overrides', 'o')
+      ->fields('o', ['inclusion_settings'])
+      ->condition('o.type', $variant)
+      ->condition('o.entity_type', $entity_type_id)
+      ->condition('o.entity_id', $id)
+      ->execute()
+      ->fetchField();
+
+    if (!empty($results)) {
+      return [$variant => unserialize($results)];
+    }
+
+    if (($entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id)) === NULL) {
+      return FALSE;
+    }
+
+    $bundle_settings = $this->getBundleSettings(
+      $entity_type_id,
+      $this->entityHelper->getEntityBundle($entity)
+    );
+
+    return $bundle_settings ?: FALSE;
+  }
+
+  /**
+   * Removes sitemap settings for entities that override bundle settings.
+   *
+   * This is done for the currently set variants.
+   *
+   * @param string|null $entity_type_id
+   *   Limits the removal to a certain entity type.
+   * @param string|array|null $entity_ids
+   *   Limits the removal to entities with certain IDs.
+   *
+   * @return $this
+   */
+  public function removeEntityInstanceSettings(?string $entity_type_id = NULL, $entity_ids = NULL): EntityManager {
+    if (empty($variants = $this->getVariants())) {
+      return $this;
+    }
+
+    $query = $this->database->delete('simple_sitemap_entity_overrides')
+      ->condition('type', $variants, 'IN');
+
+    if (NULL !== $entity_type_id) {
+      $query->condition('entity_type', $entity_type_id);
+
+      if (NULL !== $entity_ids) {
+        $query->condition('entity_id', (array) $entity_ids, 'IN');
+      }
+    }
+
+    $query->execute();
+
+    return $this;
+  }
+
+  /**
+   * Checks the index status for an entity bundle.
+   *
+   * Checks if an entity bundle (or a non-bundle entity type) is set to be
+   * indexed for any of the currently set variants.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string|null $bundle_name
+   *   The bundle of the entity.
+   *
+   * @return bool
+   *   TRUE if an entity bundle is indexed, FALSE otherwise.
+   */
+  public function bundleIsIndexed(string $entity_type_id, ?string $bundle_name = NULL): bool {
+    foreach ($this->getBundleSettings($entity_type_id, $bundle_name) as $bundle_settings) {
+      if (!empty($bundle_settings['index'])) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Checks if an entity type is enabled in the sitemap settings.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return bool
+   *   TRUE if an entity type is enabled, FALSE otherwise.
+   */
+  public function entityTypeIsEnabled(string $entity_type_id): bool {
+    return in_array($entity_type_id, $this->settings->get('enabled_entity_types', []), TRUE);
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Manager/Generator.php b/web/modules/simple_sitemap/src/Manager/Generator.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd918c60bc58f5de44abe34ff8bf2a29ee2b724b
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Manager/Generator.php
@@ -0,0 +1,237 @@
+<?php
+
+namespace Drupal\simple_sitemap\Manager;
+
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Logger;
+use Drupal\simple_sitemap\Queue\QueueWorker;
+use Drupal\simple_sitemap\Settings;
+
+/**
+ * Main managing service.
+ *
+ * Capable of setting/loading module settings, queuing elements and generating
+ * the sitemap. Services for custom link and entity link generation can be
+ * fetched from this service as well.
+ */
+class Generator {
+
+  use VariantSetterTrait;
+
+  /**
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
+   */
+  protected $settings;
+
+  /**
+   * The simple_sitemap.queue_worker service.
+   *
+   * @var \Drupal\simple_sitemap\Queue\QueueWorker
+   */
+  protected $queueWorker;
+
+  /**
+   * The lock backend that should be used.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
+  /**
+   * Simple XML Sitemap logger.
+   *
+   * @var \Drupal\simple_sitemap\Logger
+   */
+  protected $logger;
+
+  /**
+   * Simplesitemap constructor.
+   *
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker
+   *   The simple_sitemap.queue_worker service.
+   * @param \Drupal\Core\Lock\LockBackendInterface|null $lock
+   *   The lock backend that should be used.
+   * @param \Drupal\simple_sitemap\Logger|null $logger
+   *   Simple XML Sitemap logger.
+   */
+  public function __construct(
+    Settings $settings,
+    QueueWorker $queue_worker,
+    LockBackendInterface $lock = NULL,
+    Logger $logger = NULL
+  ) {
+    $this->settings = $settings;
+    $this->queueWorker = $queue_worker;
+    $this->lock = $lock;
+    $this->logger = $logger;
+  }
+
+  /**
+   * Returns a specific setting or a default value if setting does not exist.
+   *
+   * @param string $name
+   *   Name of the setting, like 'max_links'.
+   * @param mixed $default
+   *   Value to be returned if the setting does not exist in the configuration.
+   *
+   * @return mixed
+   *   The current setting from configuration or a default value.
+   */
+  public function getSetting(string $name, $default = NULL) {
+    return $this->settings->get($name, $default);
+  }
+
+  /**
+   * Stores a specific sitemap setting in configuration.
+   *
+   * @param string $name
+   *   Setting name, like 'max_links'.
+   * @param mixed $setting
+   *   The setting to be saved.
+   *
+   * @return $this
+   */
+  public function saveSetting(string $name, $setting): Generator {
+    $this->settings->save($name, $setting);
+
+    return $this;
+  }
+
+  /**
+   * Gets the default variant from the currently set variants.
+   *
+   * @return string|null
+   *   The default variant or NULL if there are no variants.
+   */
+  public function getDefaultVariant(): ?string {
+    if (empty($variants = $this->getVariants())) {
+      return NULL;
+    }
+
+    if (count($variants) > 1) {
+      $variant = $this->getSetting('default_variant');
+
+      if ($variant && in_array($variant, $variants)) {
+        return $variant;
+      }
+    }
+
+    return reset($variants);
+  }
+
+  /**
+   * Returns a sitemap variant, its index, or its requested chunk.
+   *
+   * @param int|null $delta
+   *   Optional delta of the chunk.
+   *
+   * @return string|null
+   *   If no chunk delta is provided, either the sitemap string is returned,
+   *   or its index string in case of a chunked sitemap.
+   *   If a chunk delta is provided, the relevant chunk string is returned.
+   *   Returns null if the content is not retrievable from the database.
+   */
+  public function getContent(?int $delta = NULL): ?string {
+    $variant = $this->getDefaultVariant();
+
+    /** @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap */
+    if ($variant && ($sitemap = SimpleSitemap::load($variant)) && $sitemap->isEnabled()
+      && ($sitemap_string = $sitemap->fromPublished()->toString($delta))) {
+      return $sitemap_string;
+    }
+
+    return NULL;
+  }
+
+  /**
+   * Generates all sitemaps.
+   *
+   * @param string $from
+   *   Can be 'form', 'drush', 'cron' and 'backend'.
+   *
+   * @return $this
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function generate(string $from = QueueWorker::GENERATE_TYPE_FORM): Generator {
+    if (!$this->lock->lockMayBeAvailable(QueueWorker::LOCK_ID)) {
+      $this->logger->m('Unable to acquire a lock for sitemap generation.')->log('error')->display('error');
+      return $this;
+    }
+    switch ($from) {
+      case QueueWorker::GENERATE_TYPE_FORM:
+      case QueueWorker::GENERATE_TYPE_DRUSH;
+        $this->queueWorker->batchGenerate($from);
+        break;
+
+      case QueueWorker::GENERATE_TYPE_CRON:
+      case QueueWorker::GENERATE_TYPE_BACKEND:
+        $this->queueWorker->generate($from);
+        break;
+    }
+
+    return $this;
+  }
+
+  /**
+   * Queues links from currently set variants.
+   *
+   * @return $this
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function queue(): Generator {
+    $this->queueWorker->queue($this->getVariants());
+
+    return $this;
+  }
+
+  /**
+   * Deletes the queue and queues links from currently set variants.
+   *
+   * @return $this
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function rebuildQueue(): Generator {
+    if (!$this->lock->lockMayBeAvailable(QueueWorker::LOCK_ID)) {
+      $this->logger->m('Unable to acquire a lock for sitemap generation.')->log('error')->display('error');
+      return $this;
+    }
+    $this->queueWorker->rebuildQueue($this->getVariants());
+
+    return $this;
+  }
+
+  /**
+   * Gets the simple_sitemap.entity_manager service.
+   *
+   * @return \Drupal\simple_sitemap\Manager\EntityManager
+   *   The simple_sitemap.entity_manager service.
+   */
+  public function entityManager(): EntityManager {
+    /** @var \Drupal\simple_sitemap\Manager\EntityManager $entities */
+    $entities = \Drupal::service('simple_sitemap.entity_manager');
+
+    return $entities->setVariants($this->getVariants());
+  }
+
+  /**
+   * Gets the simple_sitemap.custom_link_manager service.
+   *
+   * @return \Drupal\simple_sitemap\Manager\CustomLinkManager
+   *   The simple_sitemap.custom_link_manager service.
+   */
+  public function customLinkManager(): CustomLinkManager {
+    /** @var \Drupal\simple_sitemap\Manager\CustomLinkManager $custom_links */
+    $custom_links = \Drupal::service('simple_sitemap.custom_link_manager');
+
+    return $custom_links->setVariants($this->getVariants());
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Manager/LinkSettingsTrait.php b/web/modules/simple_sitemap/src/Manager/LinkSettingsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..497ca7bd981a45fbedf9671d0c50971ece89975f
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Manager/LinkSettingsTrait.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\simple_sitemap\Manager;
+
+/**
+ * Provides a helper for supplementing the link settings.
+ */
+trait LinkSettingsTrait {
+
+  /**
+   * Supplements all missing link setting with default values.
+   *
+   * @param array|null &$settings
+   *   Link settings to supplement.
+   * @param array $overrides
+   *   Link settings overrides.
+   */
+  public static function supplementDefaultSettings(&$settings, array $overrides = []): void {
+    foreach (self::$linkSettingDefaults as $setting => $value) {
+      if (!isset($settings[$setting])) {
+        $settings[$setting] = $overrides[$setting] ?? $value;
+      }
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Manager/VariantSetterTrait.php b/web/modules/simple_sitemap/src/Manager/VariantSetterTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8da198642fcc7e2d00deeb1fc967112a73d7d79
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Manager/VariantSetterTrait.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\simple_sitemap\Manager;
+
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+
+/**
+ * Provides a helper to setting/getting variants.
+ */
+trait VariantSetterTrait {
+
+  /**
+   * The currently set variants.
+   *
+   * @var array
+   */
+  protected $variants;
+
+  /**
+   * Sets the variants.
+   *
+   * @param array|string|null $variants
+   *   array: Array of variants to be set.
+   *   string: A particular variant to be set.
+   *   null: All existing variants will be set.
+   *
+   * @return $this
+   *
+   * @todo Check if variants exist and throw exception.
+   */
+  public function setVariants($variants = NULL): self {
+    if ($variants === NULL) {
+      // @todo No need to load all sitemaps here.
+      $this->variants = array_keys(SimpleSitemap::loadMultiple());
+    }
+    else {
+      $this->variants = (array) $variants;
+    }
+
+    return $this;
+  }
+
+  /**
+   * Gets the currently set variants, or all variants if none are set.
+   *
+   * @return array
+   *   The currently set variants, or all variants if none are set.
+   */
+  protected function getVariants(): array {
+    if (NULL === $this->variants) {
+      $this->setVariants();
+    }
+
+    return $this->variants;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/PathProcessor/PathProcessorSitemapVariantIn.php b/web/modules/simple_sitemap/src/PathProcessor/PathProcessorSitemapVariantIn.php
deleted file mode 100644
index 265c91d9d0bbf277678c493c8d46eac52faee762..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/PathProcessor/PathProcessorSitemapVariantIn.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\PathProcessor;
-
-use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Class PathProcessorSitemapVariantIn
- * @package Drupal\simple_sitemap\PathProcessor
- */
-class PathProcessorSitemapVariantIn implements InboundPathProcessorInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function processInbound($path, Request $request) {
-    $args = explode('/', $path);
-    if (count($args) === 3 && $args[2] === 'sitemap.xml') {
-      $path = '/sitemaps/' . $args[1] . '/sitemap.xml';
-    }
-
-    return $path;
-  }
-}
diff --git a/web/modules/simple_sitemap/src/PathProcessor/PathProcessorSitemapVariantOut.php b/web/modules/simple_sitemap/src/PathProcessor/PathProcessorSitemapVariantOut.php
deleted file mode 100644
index 1af3da2ef01ea48aeab5cbb19f81423f58ca54b6..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/PathProcessor/PathProcessorSitemapVariantOut.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\PathProcessor;
-
-use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Drupal\Core\Render\BubbleableMetadata;
-
-/**
- * Class PathProcessorSitemapVariantOut
- * @package Drupal\simple_sitemap\PathProcessor
- */
-class PathProcessorSitemapVariantOut implements OutboundPathProcessorInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
-    $args = explode('/', $path);
-    if (count($args) === 4 && $args[3] === 'sitemap.xml') {
-      $path = '/' . $args[2] . '/sitemap.xml';
-    }
-
-    return $path;
-  }
-
-}
diff --git a/web/modules/simple_sitemap/src/PathProcessor/SitemapPathProcessor.php b/web/modules/simple_sitemap/src/PathProcessor/SitemapPathProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..b15e455a4760383d90a555c7679b1ba68634b6f9
--- /dev/null
+++ b/web/modules/simple_sitemap/src/PathProcessor/SitemapPathProcessor.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\simple_sitemap\PathProcessor;
+
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Processes the inbound and outbound sitemap paths.
+ */
+class SitemapPathProcessor implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processInbound($path, Request $request) {
+    $args = explode('/', $path ?? '');
+    if (count($args) === 3 && $args[2] === 'sitemap.xml') {
+      $path = '/sitemaps/' . $args[1] . '/sitemap.xml';
+    }
+
+    return $path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processOutbound($path, &$options = [], ?Request $request = NULL, ?BubbleableMetadata $bubbleable_metadata = NULL) {
+    $args = explode('/', $path ?? '');
+    if (count($args) === 4 && $args[3] === 'sitemap.xml') {
+      $path = '/' . $args[2] . '/sitemap.xml';
+    }
+
+    return $path;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimpleSitemapPluginBase.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimpleSitemapPluginBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f92cce9368f0eea999cfdaae28b4b9065b561b5
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimpleSitemapPluginBase.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\simple_sitemap\Plugin\simple_sitemap;
+
+use Drupal\Core\Plugin\PluginBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a base class for Simple XML Sitemap plugins.
+ */
+abstract class SimpleSitemapPluginBase extends PluginBase implements SimpleSitemapPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): SimpleSitemapPluginBase {
+    return new static($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label(): string {
+    return $this->getPluginDefinition()['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function description(): string {
+    return $this->getPluginDefinition()['description'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settings(): array {
+    return $this->getPluginDefinition()['settings'];
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimpleSitemapPluginInterface.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimpleSitemapPluginInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..64b90dec248e073ecd95dfbcee52f8d16cadd4e9
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimpleSitemapPluginInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\simple_sitemap\Plugin\simple_sitemap;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+
+/**
+ * Provides an interface for Simple XML Sitemap plugins.
+ */
+interface SimpleSitemapPluginInterface extends ContainerFactoryPluginInterface {
+
+  /**
+   * Gets the label of this plugin.
+   *
+   * @return string
+   *   The label of this plugin.
+   */
+  public function label(): string;
+
+  /**
+   * Gets the description of this plugin.
+   *
+   * @return string
+   *   The description of this plugin.
+   */
+  public function description(): string;
+
+  /**
+   * Gets the settings of this plugin.
+   *
+   * @return array
+   *   The settings of this plugin.
+   */
+  public function settings(): array;
+
+}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimplesitemapPluginBase.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimplesitemapPluginBase.php
deleted file mode 100644
index 8f5142d43f3fb5a4dfe64e202d5a78c485af6c5a..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SimplesitemapPluginBase.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Plugin\simple_sitemap;
-
-use Drupal\Core\Plugin\PluginBase;
-use Drupal\Component\Plugin\PluginInspectionInterface;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Class UrlGeneratorBase
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
- */
-abstract class SimplesitemapPluginBase extends PluginBase implements PluginInspectionInterface, ContainerFactoryPluginInterface {
-
-  /**
-   * SimplesitemapPluginBase constructor.
-   * @param array $configuration
-   * @param string $plugin_id
-   * @param mixed $plugin_definition
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-  }
-
-  /**
-   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
-   * @param array $configuration
-   * @param string $plugin_id
-   * @param mixed $plugin_definition
-   * @return static
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static($configuration, $plugin_id, $plugin_definition);
-  }
-}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/DefaultSitemapGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/DefaultSitemapGenerator.php
index abf6e39e6d02d20881bafb2a7a8c6e8adc6dc467..bacaabac9a28959a894521442374172ad4efe2b5 100755
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/DefaultSitemapGenerator.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/DefaultSitemapGenerator.php
@@ -3,8 +3,7 @@
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator;
 
 /**
- * Class DefaultSitemapGenerator
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator
+ * Provides the default sitemap generator.
  *
  * @SitemapGenerator(
  *   id = "default",
@@ -14,42 +13,29 @@
  */
 class DefaultSitemapGenerator extends SitemapGeneratorBase {
 
-  const XMLNS_XHTML = 'http://www.w3.org/1999/xhtml';
-  const XMLNS_IMAGE = 'http://www.google.com/schemas/sitemap-image/1.1';
-
-  /**
-   * @var bool
-   */
-  protected $isHreflangSitemap;
+  protected const XMLNS_XHTML = 'http://www.w3.org/1999/xhtml';
+  protected const XMLNS_IMAGE = 'http://www.google.com/schemas/sitemap-image/1.1';
 
   /**
+   * An array of attributes.
+   *
    * @var array
    */
-  protected static $attributes = [
+  protected const ATTRIBUTES = [
     'xmlns' => self::XMLNS,
     'xmlns:xhtml' => self::XMLNS_XHTML,
     'xmlns:image' => self::XMLNS_IMAGE,
   ];
 
   /**
-   * Generates and returns a sitemap chunk.
-   *
-   * @param array $links
-   *   All links with their multilingual versions and settings.
-   *
-   * @return string
-   *   Sitemap chunk
+   * {@inheritdoc}
    */
-  protected function getXml(array $links) {
+  public function getChunkContent(array $links): string {
     $this->writer->openMemory();
     $this->writer->setIndent(TRUE);
     $this->writer->startSitemapDocument();
 
-    // Add the XML stylesheet to document if enabled.
-    if ($this->settings['xsl']) {
-      $this->writer->writeXsl();
-    }
-
+    $this->addXslUrl();
     $this->writer->writeGeneratedBy();
     $this->writer->startElement('urlset');
     $this->addSitemapAttributes();
@@ -63,13 +49,12 @@ protected function getXml(array $links) {
   /**
    * Adds attributes to the sitemap.
    */
-  protected function addSitemapAttributes() {
-    $attributes = self::$attributes;
-    if (!$this->isHreflangSitemap()) {
+  protected function addSitemapAttributes(): void {
+    $attributes = self::ATTRIBUTES;
+    if (!$this->sitemap->isMultilingual()) {
       unset($attributes['xmlns:xhtml']);
     }
-    $sitemap_variant = $this->sitemapVariant;
-    $this->moduleHandler->alter('simple_sitemap_attributes', $attributes, $sitemap_variant);
+    $this->moduleHandler->alter('simple_sitemap_attributes', $attributes, $this->sitemap);
     foreach ($attributes as $name => $value) {
       $this->writer->writeAttribute($name, $value);
     }
@@ -79,8 +64,9 @@ protected function addSitemapAttributes() {
    * Adds URL elements to the sitemap.
    *
    * @param array $links
+   *   An array of URL elements.
    */
-  protected function addLinks(array $links) {
+  protected function addLinks(array $links): void {
     foreach ($links as $url_data) {
       $this->writer->startElement('url');
       $this->addUrl($url_data);
@@ -94,40 +80,38 @@ protected function addLinks(array $links) {
    * @param array $url_data
    *   The array of properties for this URL.
    */
-  protected function addUrl(array $url_data) {
-    $this->writer->writeElement('loc', $url_data['url']);
+  protected function addUrl(array $url_data): void {
+    if (isset($url_data['url'])) {
+      $this->writer->writeElement('loc', $url_data['url']);
+    }
 
     // If more than one language is enabled, add all translation variant URLs
     // as alternate links to this link turning the sitemap into a hreflang
     // sitemap.
-    if (isset($url_data['alternate_urls']) && $this->isHreflangSitemap()) {
+    if (isset($url_data['alternate_urls']) && $this->sitemap->isMultilingual()) {
       $this->addAlternateUrls($url_data['alternate_urls']);
     }
 
-    // Add lastmod if any.
     if (isset($url_data['lastmod'])) {
       $this->writer->writeElement('lastmod', $url_data['lastmod']);
     }
 
-    // Add changefreq if any.
     if (isset($url_data['changefreq'])) {
       $this->writer->writeElement('changefreq', $url_data['changefreq']);
     }
 
-    // Add priority if any.
     if (isset($url_data['priority'])) {
       $this->writer->writeElement('priority', $url_data['priority']);
     }
 
-    // Add images if any.
     if (!empty($url_data['images'])) {
       foreach ($url_data['images'] as $image) {
         $this->writer->startElement('image:image');
         $this->writer->writeElement('image:loc', $image['path']);
-        if (strlen($image['title']) > 0) {
+        if (isset($image['title']) && $image['title'] !== '') {
           $this->writer->writeElement('image:title', $image['title']);
         }
-        if (strlen($image['alt']) > 0) {
+        if (isset($image['alt']) && $image['alt'] !== '') {
           $this->writer->writeElement('image:caption', $image['alt']);
         }
         $this->writer->endElement();
@@ -139,8 +123,9 @@ protected function addUrl(array $url_data) {
    * Adds all translation variant URLs as alternate URLs to a URL.
    *
    * @param array $alternate_urls
+   *   An array of alternate URLs.
    */
-  protected function addAlternateUrls(array $alternate_urls) {
+  protected function addAlternateUrls(array $alternate_urls): void {
     foreach ($alternate_urls as $language_id => $alternate_url) {
       $this->writer->startElement('xhtml:link');
       $this->addAlternateUrl($language_id, $alternate_url);
@@ -151,23 +136,50 @@ protected function addAlternateUrls(array $alternate_urls) {
   /**
    * Adds a translation variant URL as alternate URL to a URL.
    *
-   * @param $language_id
-   * @param $alternate_url
+   * @param string $language_id
+   *   The language ID.
+   * @param string $alternate_url
+   *   The alternate URL.
    */
-  protected function addAlternateUrl($language_id, $alternate_url) {
+  protected function addAlternateUrl(string $language_id, string $alternate_url): void {
     $this->writer->writeAttribute('rel', 'alternate');
     $this->writer->writeAttribute('hreflang', $language_id);
     $this->writer->writeAttribute('href', $alternate_url);
   }
 
   /**
-   * Checks if sitemap is hreflang compliant.
-   *
-   * @return bool
+   * {@inheritdoc}
    */
-  protected function isHreflangSitemap() {
-    return NULL !== $this->isHreflangSitemap
-      ? $this->isHreflangSitemap
-      : self::isMultilingualSitemap();
+  public function getXslContent(): ?string {
+    $module_path = $this->moduleList->getPath('simple_sitemap');
+    $xsl_content = file_get_contents($module_path . '/xsl/simple_sitemap.xsl');
+    $replacements = [
+      '[title]' => $this->t('Sitemap file'),
+      '[generated-by]' => $this->settings->get('hide_branding')
+        ? ''
+        : $this->t('Generated by the <a href="@url">@module_name</a> Drupal module.', [
+          '@url' => 'https://www.drupal.org/project/simple_sitemap',
+          '@module_name' => 'Simple XML Sitemap',
+        ]),
+      '[number-of-sitemaps]' => $this->t('Number of sitemaps in this index'),
+      '[sitemap-url]' => $this->t('Sitemap URL'),
+      '[number-of-urls]' => $this->t('Number of URLs in this sitemap'),
+      '[url-location]' => $this->t('URL location'),
+      '[lastmod]' => $this->t('Last modification date'),
+      '[changefreq]' => $this->t('Change frequency'),
+      '[priority]' => $this->t('Priority'),
+      '[translation-set]' => $this->t('Translation set'),
+      '[images]' => $this->t('Images'),
+      '[image-title]' => $this->t('Title'),
+      '[image-caption]' => $this->t('Caption'),
+      '[jquery]' => base_path() . 'core/assets/vendor/jquery/jquery.min.js',
+      '[jquery-tablesorter]' => base_path() . $module_path . '/xsl/jquery.tablesorter.min.js',
+      '[parser-date-iso8601]' => base_path() . $module_path . '/xsl/parser-date-iso8601.min.js',
+      '[xsl-js]' => base_path() . $module_path . '/xsl/simple_sitemap.xsl.js',
+      '[xsl-css]' => base_path() . $module_path . '/xsl/simple_sitemap.xsl.css',
+    ];
+
+    return strtr($xsl_content, $replacements);
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorBase.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorBase.php
index 0abef041027582e827c4582418ec11e994fb804c..cc84efb0f02f2ae576df0f972eb6b7597020bdd9 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorBase.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorBase.php
@@ -2,66 +2,58 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator;
 
-use Drupal\Core\Url;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SimplesitemapPluginBase;
+use Drupal\Core\Extension\ModuleExtensionList;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
+use Drupal\simple_sitemap\Entity\SimpleSitemapInterface;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Extension\ModuleHandler;
-use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Component\Datetime\Time;
-use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
 
 /**
- * Class SitemapGeneratorBase
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides a base class for SitemapGenerator plugins.
  */
-abstract class SitemapGeneratorBase extends SimplesitemapPluginBase implements SitemapGeneratorInterface {
+abstract class SitemapGeneratorBase extends SimpleSitemapPluginBase implements SitemapGeneratorInterface {
 
-  const FIRST_CHUNK_DELTA = 1;
-  const INDEX_DELTA = 0;
-  const XMLNS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
+  protected const XMLNS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
 
   /**
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $db;
-
-  /**
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
-  /**
-   * @var \Drupal\Core\Extension\ModuleHandler
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
    */
   protected $moduleHandler;
 
   /**
-   * @var \Drupal\Component\Datetime\Time
-   */
-  protected $time;
-
-  /**
-   * @var array
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
    */
   protected $settings;
 
   /**
+   * Sitemap XML writer.
+   *
    * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapWriter
    */
   protected $writer;
 
   /**
-   * @var string
+   * The sitemap entity.
+   *
+   * @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface
    */
-  protected $sitemapVariant;
+  protected $sitemap;
 
   /**
-   * @var array
+   * The extension.list.module service.
+   *
+   * @var \Drupal\Core\Extension\ModuleExtensionList
    */
-  protected $sitemapUrlSettings;
+  protected $moduleList;
 
   /**
+   * An array of index attributes.
+   *
    * @var array
    */
   protected static $indexAttributes = [
@@ -70,109 +62,94 @@ abstract class SitemapGeneratorBase extends SimplesitemapPluginBase implements S
 
   /**
    * SitemapGeneratorBase constructor.
+   *
    * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
    * @param mixed $plugin_definition
-   * @param \Drupal\Core\Database\Connection $database
-   * @param \Drupal\Core\Extension\ModuleHandler $module_handler
-   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
-   * @param \Drupal\Component\Datetime\Time $time
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler service.
    * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapWriter $sitemap_writer
+   *   Sitemap XML writer.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\Core\Extension\ModuleExtensionList $module_list
+   *   The extension.list.module service.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Connection $database,
-    ModuleHandler $module_handler,
-    LanguageManagerInterface $language_manager,
-    Time $time,
-    SitemapWriter $sitemap_writer
+    ModuleHandlerInterface $module_handler,
+    SitemapWriter $sitemap_writer,
+    Settings $settings,
+    ModuleExtensionList $module_list
   ) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->db = $database;
     $this->moduleHandler = $module_handler;
-    $this->languageManager = $language_manager;
-    $this->time = $time;
     $this->writer = $sitemap_writer;
+    $this->settings = $settings;
+    $this->moduleList = $module_list;
   }
 
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('database'),
       $container->get('module_handler'),
-      $container->get('language_manager'),
-      $container->get('datetime.time'),
-      $container->get('simple_sitemap.sitemap_writer')
+      $container->get('simple_sitemap.sitemap_writer'),
+      $container->get('simple_sitemap.settings'),
+      $container->get('extension.list.module')
     );
   }
 
   /**
-   * @param string $sitemap_variant
-   * @return $this
+   * {@inheritdoc}
    */
-  public function setSitemapVariant($sitemap_variant) {
-    $this->sitemapVariant = $sitemap_variant;
+  public function setSitemap(SimpleSitemapInterface $sitemap): SitemapGeneratorInterface {
+    $this->sitemap = $sitemap;
 
     return $this;
   }
 
   /**
-   * @return bool
-   */
-  protected function isDefaultVariant() {
-    return $this->sitemapVariant === $this->settings['default_variant'];
-  }
-
-  /**
-   * @param array $links
-   * @return string
+   * {@inheritdoc}
    */
-  abstract protected function getXml(array $links);
-
-  protected function getChunkInfo() {
-    return $this->db->select('simple_sitemap', 's')
-      ->fields('s', ['delta', 'sitemap_created', 'type'])
-      ->condition('s.type', $this->sitemapVariant)
-      ->condition('s.delta', self::INDEX_DELTA, '<>')
-      ->condition('s.status', 0)
-      ->execute()
-      ->fetchAllAssoc('delta');
-  }
+  abstract public function getChunkContent(array $links): string;
 
   /**
-   * @param array $chunk_info
-   * @return string
+   * {@inheritdoc}
+   *
+   * @throws \Drupal\Core\Entity\EntityMalformedException
    */
-  protected function getIndexXml(array $chunk_info) {
+  public function getIndexContent(): string {
     $this->writer->openMemory();
     $this->writer->setIndent(TRUE);
     $this->writer->startSitemapDocument();
 
-    // Add the XML stylesheet to document if enabled.
-    if ($this->settings['xsl']) {
-      $this->writer->writeXsl();
-    }
-
+    $this->addXslUrl();
     $this->writer->writeGeneratedBy();
     $this->writer->startElement('sitemapindex');
 
     // Add attributes to document.
     $attributes = self::$indexAttributes;
-    $sitemap_variant = $this->sitemapVariant;
-    $this->moduleHandler->alter('simple_sitemap_index_attributes', $attributes, $sitemap_variant);
+    $this->moduleHandler->alter('simple_sitemap_index_attributes', $attributes, $this->sitemap);
     foreach ($attributes as $name => $value) {
       $this->writer->writeAttribute($name, $value);
     }
 
     // Add sitemap chunk locations to document.
-    foreach ($chunk_info as $chunk_data) {
+    for ($delta = 1; $delta <= $this->sitemap->fromUnpublished()->getChunkCount(); $delta++) {
       $this->writer->startElement('sitemap');
-      $this->writer->writeElement('loc', $this->getSitemapUrl($chunk_data->delta));
-      $this->writer->writeElement('lastmod', date('c', $chunk_data->sitemap_created));
+      $this->writer->writeElement('loc', $this->sitemap->toUrl('canonical', ['delta' => $delta])->toString());
+      // @todo Should this be current time instead?
+      $this->writer->writeElement('lastmod', date('c', $this->sitemap->fromUnpublished()->getCreated()));
       $this->writer->endElement();
     }
 
@@ -183,204 +160,19 @@ protected function getIndexXml(array $chunk_info) {
   }
 
   /**
-   * @param string $mode
-   * @return $this
-   */
-  public function remove($mode = 'all') {
-    self::purgeSitemapVariants($this->sitemapVariant, $mode);
-
-    return $this;
-  }
-
-  public static function purgeSitemapVariants($variants = NULL, $mode = 'all') {
-    if (NULL === $variants || !empty((array) $variants)) {
-      $delete_query = \Drupal::database()->delete('simple_sitemap');
-
-      switch($mode) {
-        case 'published':
-          $delete_query->condition('status', 1);
-          break;
-
-        case 'unpublished':
-          $delete_query->condition('status', 0);
-          break;
-
-        case 'all':
-          break;
-
-        default:
-          //todo: throw error
-      }
-
-      if (NULL !== $variants) {
-        $delete_query->condition('type', (array) $variants, 'IN');
-      }
-
-      $delete_query->execute();
-    }
-  }
-
-  /**
-   * @param array $links
-   * @return $this
-   * @throws \Exception
-   */
-  public function generate(array $links) {
-    $highest_id = $this->db->query('SELECT MAX(id) FROM {simple_sitemap}')->fetchField();
-    $highest_delta = $this->db->query('SELECT MAX(delta) FROM {simple_sitemap} WHERE type = :type AND status = :status', [':type' => $this->sitemapVariant, ':status' => 0])
-      ->fetchField();
-
-    $this->db->insert('simple_sitemap')->fields([
-      'id' => NULL === $highest_id ? 0 : $highest_id + 1,
-      'delta' => NULL === $highest_delta ? self::FIRST_CHUNK_DELTA : $highest_delta + 1,
-      'type' =>  $this->sitemapVariant,
-      'sitemap_string' => $this->getXml($links),
-      'sitemap_created' => $this->time->getRequestTime(),
-      'status' => 0,
-      'link_count' => count($links),
-    ])->execute();
-
-    return $this;
-  }
-
-  /**
-   * @return $this
-   * @throws \Exception
-   */
-  public function generateIndex() {
-    if (!empty($chunk_info = $this->getChunkInfo()) && count($chunk_info) > 1) {
-      $index_xml = $this->getIndexXml($chunk_info);
-      $highest_id = $this->db->query('SELECT MAX(id) FROM {simple_sitemap}')->fetchField();
-      $this->db->merge('simple_sitemap')
-        ->keys([
-          'delta' => self::INDEX_DELTA,
-          'type' => $this->sitemapVariant,
-          'status' => 0
-        ])
-        ->insertFields([
-          'id' => NULL === $highest_id ? 0 : $highest_id + 1,
-          'delta' => self::INDEX_DELTA,
-          'type' =>  $this->sitemapVariant,
-          'sitemap_string' => $index_xml,
-          'sitemap_created' => $this->time->getRequestTime(),
-          'status' => 0,
-        ])
-        ->updateFields([
-          'sitemap_string' => $index_xml,
-          'sitemap_created' => $this->time->getRequestTime(),
-        ])
-        ->execute();
-    }
-
-    return $this;
-  }
-
-  /**
-   * @return $this
-   */
-  public function publish() {
-    $unpublished_chunk = $this->db->query('SELECT MAX(id) FROM {simple_sitemap} WHERE type = :type AND status = :status', [
-      ':type' => $this->sitemapVariant, ':status' => 0
-    ])->fetchField();
-
-    // Only allow publishing a sitemap variant if there is an unpublished
-    // sitemap variant, as publishing involves deleting the currently published
-    // variant.
-    if (FALSE !== $unpublished_chunk) {
-      $this->remove('published');
-      $this->db->query('UPDATE {simple_sitemap} SET status = :status WHERE type = :type', [':type' => $this->sitemapVariant, ':status' => 1]);
-    }
-
-    return $this;
-  }
-
-  /**
-   * @param array $settings
-   * @return $this
-   */
-  public function setSettings(array $settings) {
-    $this->settings = $settings;
-
-    return $this;
-  }
-
-  /**
-   * @return string
+   * Adds the XML stylesheet.
    */
-  protected function getCustomBaseUrl() {
-    $customBaseUrl = $this->settings['base_url'];
-
-    return !empty($customBaseUrl) ? $customBaseUrl : $GLOBALS['base_url'];
-  }
-
-  protected function getSitemapUrlSettings() {
-    if (NULL === $this->sitemapUrlSettings) {
-      $this->sitemapUrlSettings = [
-        'absolute' => TRUE,
-        'base_url' => $this->getCustomBaseUrl(),
-        'language' => $this->languageManager->getLanguage(LanguageInterface::LANGCODE_NOT_APPLICABLE),
-      ];
+  protected function addXslUrl(): void {
+    if ($this->settings->get('xsl')) {
+      $this->writer->writeXsl($this->getPluginId());
     }
-
-    return $this->sitemapUrlSettings;
   }
 
   /**
-   * @param null $delta
-   * @return \Drupal\Core\GeneratedUrl|string
+   * {@inheritdoc}
    */
-  public function getSitemapUrl($delta = NULL) {
-    $parameters = NULL !== $delta ? ['page' => $delta] : [];
-    $url = $this->isDefaultVariant()
-      ? Url::fromRoute(
-        'simple_sitemap.sitemap_default',
-        $parameters,
-        $this->getSitemapUrlSettings())
-      : Url::fromRoute(
-        'simple_sitemap.sitemap_variant',
-        $parameters + ['variant' => $this->sitemapVariant],
-        $this->getSitemapUrlSettings()
-      );
-
-    return $url->toString();
-  }
-
-  /**
-   * Determines if the sitemap is to be a multilingual sitemap based on several
-   * factors.
-   *
-   * A hreflang/multilingual sitemap is only wanted if there are indexable
-   * languages available and if there is a language negotiation method enabled
-   * that is based on URL discovery. Any other language negotiation methods
-   * should be irrelevant, as a sitemap can only use URLs to guide to the
-   * correct language.
-   *
-   * @see https://www.drupal.org/project/simple_sitemap/issues/3154570#comment-13730522
-   *
-   * @return bool
-   */
-  public static function isMultilingualSitemap() {
-    if (!\Drupal::moduleHandler()->moduleExists('language')) {
-      return FALSE;
-    }
-
-    /** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
-    $language_negotiator = \Drupal::service('language_negotiator');
-
-    $url_negotiation_method_enabled = FALSE;
-    foreach ($language_negotiator->getNegotiationMethods(LanguageInterface::TYPE_URL) as $method) {
-      if ($language_negotiator->isNegotiationMethodEnabled($method['id'])) {
-        $url_negotiation_method_enabled = TRUE;
-        break;
-      }
-    }
-
-    $has_multiple_indexable_languages = count(
-        array_diff_key(\Drupal::languageManager()->getLanguages(),
-          \Drupal::service('simple_sitemap.generator')->getSetting('excluded_languages', []))
-      ) > 1;
-
-    return $url_negotiation_method_enabled && $has_multiple_indexable_languages;
+  public function getXslContent(): ?string {
+    return NULL;
   }
 
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorInterface.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorInterface.php
index db9bfdd30412d5425f883e9270bcf594337c3f1f..f9720a53e8c84e45f39a6a3f6aae227454e9b6c2 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorInterface.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorInterface.php
@@ -2,23 +2,49 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator;
 
+use Drupal\simple_sitemap\Entity\SimpleSitemapInterface;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginInterface;
+
 /**
- * Interface SitemapGeneratorInterface
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator
+ * Provides an interface for SitemapGenerator plugins.
  */
-interface SitemapGeneratorInterface {
-
-  public function setSitemapVariant($sitemap_variant);
-
-  public function setSettings(array $settings);
-
-  public function generate(array $links);
-
-  public function generateIndex();
-
-  public function publish();
-
-  public function remove();
+interface SitemapGeneratorInterface extends SimpleSitemapPluginInterface {
+
+  /**
+   * Sets the sitemap.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap
+   *   The sitemap entity to set.
+   *
+   * @return $this
+   */
+  public function setSitemap(SimpleSitemapInterface $sitemap): SitemapGeneratorInterface;
+
+  /**
+   * Generates and returns a sitemap chunk.
+   *
+   * @param array $links
+   *   All links with their multilingual versions and settings.
+   *
+   * @return string
+   *   Sitemap chunk.
+   */
+  public function getChunkContent(array $links): string;
+
+  /**
+   * Generates and returns a sitemap index.
+   *
+   * @return string
+   *   Sitemap index.
+   */
+  public function getIndexContent(): string;
+
+  /**
+   * Generates and returns sitemap XSL.
+   *
+   * @return string|null
+   *   Sitemap XSL.
+   */
+  public function getXslContent(): ?string;
 
-  public function getSitemapUrl($delta = NULL);
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorManager.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorManager.php
index 8f7e2e33996187489906903d75c17494d04eb563..a42080c4c03cb828852d0666f932d7adab2168b0 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorManager.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapGeneratorManager.php
@@ -5,18 +5,23 @@
 use Drupal\Core\Plugin\DefaultPluginManager;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\simple_sitemap\Annotation\SitemapGenerator;
 
 /**
- * Class SitemapGeneratorManager
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator
+ * Manages discovery of SitemapGenerator plugins.
  */
 class SitemapGeneratorManager extends DefaultPluginManager {
 
   /**
    * SitemapGeneratorManager constructor.
+   *
    * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
    */
   public function __construct(
     \Traversable $namespaces,
@@ -27,11 +32,12 @@ public function __construct(
       'Plugin/simple_sitemap/SitemapGenerator',
       $namespaces,
       $module_handler,
-      'Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorInterface',
-      'Drupal\simple_sitemap\Annotation\SitemapGenerator'
+      SitemapGeneratorInterface::class,
+      SitemapGenerator::class
     );
 
     $this->alterInfo('simple_sitemap_sitemap_generators');
     $this->setCacheBackend($cache_backend, 'simple_sitemap:sitemap_generator');
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapIndexGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapIndexGenerator.php
new file mode 100644
index 0000000000000000000000000000000000000000..51f672492cb64c6847bc5a1165bc9020197185c8
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapIndexGenerator.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator;
+
+/**
+ * Provides the sitemap index generator.
+ *
+ * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator
+ *
+ * @SitemapGenerator(
+ *   id = "index",
+ *   label = @Translation("Sitemap index generator"),
+ *   description = @Translation("Generates an index of your sitemaps."),
+ * )
+ */
+class SitemapIndexGenerator extends DefaultSitemapGenerator {
+
+  /**
+   * Generates and returns a sitemap chunk.
+   *
+   * @param array $links
+   *   All links with their multilingual versions and settings.
+   *
+   * @return string
+   *   Sitemap chunk
+   */
+  public function getChunkContent(array $links): string {
+    $this->writer->openMemory();
+    $this->writer->setIndent(TRUE);
+    $this->writer->startSitemapDocument();
+
+    $this->addXslUrl();
+    $this->writer->writeGeneratedBy();
+    $this->writer->startElement('sitemapindex');
+    $this->addSitemapAttributes();
+    $this->addLinks($links);
+    $this->writer->endElement();
+    $this->writer->endDocument();
+
+    return $this->writer->outputMemory();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function addLinks(array $links): void {
+    foreach ($links as $url_data) {
+      $this->writer->startElement('sitemap');
+
+      if (isset($url_data['url'])) {
+        $this->writer->writeElement('loc', $url_data['url']);
+      }
+
+      if (isset($url_data['lastmod'])) {
+        $this->writer->writeElement('lastmod', $url_data['lastmod']);
+      }
+
+      $this->writer->endElement();
+    }
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapWriter.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapWriter.php
index bfec3e9c30b1f0b0c1974caf50051408e222342b..20a0f392d2abf3b7b8fb0185b41ee561c39ce1fd 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapWriter.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapGenerator/SitemapWriter.php
@@ -5,20 +5,27 @@
 use Drupal\Core\Routing\RouteProviderInterface;
 
 /**
- * Class SitemapWriter
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator
+ * Sitemap XML writer.
  */
 class SitemapWriter extends \XMLWriter {
 
-  const GENERATED_BY = 'Generated by the Simple XML Sitemap Drupal module: https://drupal.org/project/simple_sitemap.';
-  const XML_VERSION = '1.0';
-  const ENCODING = 'UTF-8';
+  protected const GENERATED_BY = 'Generated by the Simple XML Sitemap Drupal module: https://drupal.org/project/simple_sitemap.';
+  protected const XML_VERSION = '1.0';
+  protected const ENCODING = 'UTF-8';
 
   /**
-   * @var \Drupal\Core\Routing\RouteProvider
+   * The route provider.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
    */
   protected $routeProvider;
 
+  /**
+   * SitemapWriter constructor.
+   *
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider.
+   */
   public function __construct(RouteProviderInterface $route_provider) {
     $this->routeProvider = $route_provider;
   }
@@ -26,30 +33,41 @@ public function __construct(RouteProviderInterface $route_provider) {
   /**
    * Adds the XML stylesheet to the XML page.
    */
-  public function writeXsl() {
+  public function writeXsl($generator_id): void {
     // Using this instead of URL::fromRoute() to avoid creating a path with the
     // subdomain from which creation was triggered which might lead to a CORS
-    // problem. See https://www.drupal.org/project/simple_sitemap/issues/3131672.
+    // problem.
+    // See https://www.drupal.org/project/simple_sitemap/issues/3131672.
     $xsl_url = $this->routeProvider
       ->getRouteByName('simple_sitemap.sitemap_xsl')
       ->getPath();
 
+    // Now substituting path placeholder as URL::fromRoute() would do.
+    $xsl_url = str_replace('{sitemap_generator}', $generator_id, $xsl_url);
+
     // The above workaround however generates an incorrect path when the site is
     // located in a subdirectory, which is why the following logic adds the base
     // path of the installation.
     // See https://www.drupal.org/project/simple_sitemap/issues/3154494.
-    // All of this seems to be an over engineered way of writing 'sitemap.xsl',
+    // All of this seems to be an over engineered way of writing
+    // '/sitemap_generator/{sitemap_generator}/sitemap.xsl',
     // but may be useful in cases where another module alters the routes.
     $xsl_url = base_path() . ltrim($xsl_url, '/');
 
     $this->writePI('xml-stylesheet', 'type="text/xsl" href="' . $xsl_url . '"');
   }
 
-  public function writeGeneratedBy() {
+  /**
+   * Writes 'generated by' comment.
+   */
+  public function writeGeneratedBy(): void {
     $this->writeComment(self::GENERATED_BY);
   }
 
-  public function startSitemapDocument() {
+  /**
+   * Creates document tag.
+   */
+  public function startSitemapDocument(): void {
     $this->startDocument(self::XML_VERSION, self::ENCODING);
   }
 
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/DefaultHreflangSitemapType.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/DefaultHreflangSitemapType.php
deleted file mode 100644
index 357e3c142d0d08a6b62a70ab77cfef25a86c509b..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/DefaultHreflangSitemapType.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType;
-
-/**
- * Class DefaultHreflangSitemapType
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType
- *
- * @SitemapType(
- *   id = "default_hreflang",
- *   label = @Translation("Default hreflang"),
- *   description = @Translation("The default hreflang sitemap type."),
- *   sitemapGenerator = "default",
- *   urlGenerators = {
- *     "custom",
- *     "entity",
- *     "entity_menu_link_content",
- *     "arbitrary",
- *   },
- * )
- */
-class DefaultHreflangSitemapType extends SitemapTypeBase {
-}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeBase.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeBase.php
deleted file mode 100644
index ed2c896e183d3bf546dba900cd51b596768b17da..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeBase.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType;
-
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SimplesitemapPluginBase;
-
-/**
- * Class SitemapTypeBase
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType
- */
-abstract class SitemapTypeBase extends SimplesitemapPluginBase implements SitemapTypeInterface {
-}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeInterface.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeInterface.php
deleted file mode 100644
index 28771f889f222e2bcc358470c176c19fc4387149..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeInterface.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType;
-
-/**
- * Interface SitemapTypeInterface
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType
- */
-interface SitemapTypeInterface {
-}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeManager.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeManager.php
deleted file mode 100644
index 06b93b485ff77b4881be047883df9b7200e18fc8..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/SitemapType/SitemapTypeManager.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType;
-
-use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-
-/**
- * Class SitemapTypeManager
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType
- */
-class SitemapTypeManager extends DefaultPluginManager {
-
-  /**
-   * SitemapTypeManager constructor.
-   * @param \Traversable $namespaces
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   */
-  public function __construct(
-    \Traversable $namespaces,
-    CacheBackendInterface $cache_backend,
-    ModuleHandlerInterface $module_handler
-  ) {
-    parent::__construct(
-      'Plugin/simple_sitemap/SitemapType',
-      $namespaces,
-      $module_handler,
-      'Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeInterface',
-      'Drupal\simple_sitemap\Annotation\SitemapType'
-    );
-
-    $this->alterInfo('simple_sitemap_sitemap_types');
-    $this->setCacheBackend($cache_backend, 'simple_sitemap:sitemap_type');
-  }
-}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/ArbitraryUrlGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/ArbitraryUrlGenerator.php
index ef20b73e673a672a56b5c591a08453ecaae13a2b..3d6bdeeff35df2fbe0e0b42d5314b764523aee87 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/ArbitraryUrlGenerator.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/ArbitraryUrlGenerator.php
@@ -3,13 +3,13 @@
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
 use Drupal\simple_sitemap\Logger;
-use Drupal\simple_sitemap\Simplesitemap;
-use Drupal\Core\Extension\ModuleHandler;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * Class ArbitraryUrlGenerator
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides the arbitrary URL generator.
  *
  * @UrlGenerator(
  *   id = "arbitrary",
@@ -19,66 +19,81 @@
  */
 class ArbitraryUrlGenerator extends UrlGeneratorBase {
 
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
   protected $moduleHandler;
 
   /**
    * ArbitraryUrlGenerator constructor.
+   *
    * @param array $configuration
-   * @param $plugin_id
-   * @param $plugin_definition
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
    * @param \Drupal\simple_sitemap\Logger $logger
-   * @param \Drupal\Core\Extension\ModuleHandler $module_handler
+   *   Simple XML Sitemap logger.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler service.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
     Logger $logger,
-    ModuleHandler $module_handler
+    Settings $settings,
+    ModuleHandlerInterface $module_handler
   ) {
     parent::__construct(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $generator,
-      $logger
+      $logger,
+      $settings
     );
     $this->moduleHandler = $module_handler;
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public static function create(
     ContainerInterface $container,
     array $configuration,
     $plugin_id,
-    $plugin_definition) {
+    $plugin_definition): SimpleSitemapPluginBase {
 
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
       $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings'),
       $container->get('module_handler')
     );
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  public function getDataSets() {
+  public function getDataSets(): array {
     $arbitrary_links = [];
-    $sitemap_variant = $this->sitemapVariant;
-    $this->moduleHandler->alter('simple_sitemap_arbitrary_links', $arbitrary_links, $sitemap_variant);
+    $this->moduleHandler->alter('simple_sitemap_arbitrary_links', $arbitrary_links, $this->sitemap);
 
     return array_values($arbitrary_links);
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  protected function processDataSet($data_set) {
+  protected function processDataSet($data_set): array {
     return $data_set;
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/CustomUrlGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/CustomUrlGenerator.php
index 589d81f6bcb439549d5bbd5d6181c59a27e3424e..73004de9c0d7250311ce884f2e5c9523a7a53f92 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/CustomUrlGenerator.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/CustomUrlGenerator.php
@@ -3,118 +3,151 @@
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
 use Drupal\Core\Url;
-use Drupal\simple_sitemap\Annotation\UrlGenerator;
-use Drupal\simple_sitemap\EntityHelper;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Exception\SkipElementException;
 use Drupal\simple_sitemap\Logger;
-use Drupal\simple_sitemap\Simplesitemap;
+use Drupal\simple_sitemap\Manager\CustomLinkManager;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Path\PathValidator;
+use Drupal\Core\Path\PathValidatorInterface;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * Class CustomUrlGenerator
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides the custom URL generator.
  *
  * @UrlGenerator(
  *   id = "custom",
  *   label = @Translation("Custom URL generator"),
  *   description = @Translation("Generates URLs set in admin/config/search/simplesitemap/custom."),
  * )
- *
  */
 class CustomUrlGenerator extends EntityUrlGeneratorBase {
 
-  const PATH_DOES_NOT_EXIST_MESSAGE = 'The custom path @path has been omitted from the XML sitemaps as it does not exist. You can review custom paths <a href="@custom_paths_url">here</a>.';
+  protected const PATH_DOES_NOT_EXIST_MESSAGE = 'The custom path @path has been omitted from the XML sitemaps as it does not exist. You can review custom paths <a href="@custom_paths_url">here</a>.';
 
+  /**
+   * The simple_sitemap.custom_link_manager service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\CustomLinkManager
+   */
+  protected $customLinks;
 
   /**
-   * @var \Drupal\Core\Path\PathValidator
+   * The path validator service.
+   *
+   * @var \Drupal\Core\Path\PathValidatorInterface
    */
   protected $pathValidator;
 
   /**
+   * Include images of custom links.
+   *
    * @var bool
    */
   protected $includeImages;
 
   /**
    * CustomUrlGenerator constructor.
+   *
    * @param array $configuration
-   * @param $plugin_id
-   * @param $plugin_definition
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
    * @param \Drupal\simple_sitemap\Logger $logger
+   *   Simple XML Sitemap logger.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
-   * @param \Drupal\Core\Path\PathValidator $path_validator
+   *   The entity type manager.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Manager\CustomLinkManager $custom_links
+   *   The simple_sitemap.custom_link_manager service.
+   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
+   *   The path validator service.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
     Logger $logger,
+    Settings $settings,
     LanguageManagerInterface $language_manager,
     EntityTypeManagerInterface $entity_type_manager,
-    EntityHelper $entityHelper,
-    PathValidator $path_validator) {
+    EntityHelper $entity_helper,
+    CustomLinkManager $custom_links,
+    PathValidatorInterface $path_validator) {
     parent::__construct(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $generator,
       $logger,
+      $settings,
       $language_manager,
       $entity_type_manager,
-      $entityHelper
+      $entity_helper
     );
+    $this->customLinks = $custom_links;
     $this->pathValidator = $path_validator;
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public static function create(
     ContainerInterface $container,
     array $configuration,
     $plugin_id,
-    $plugin_definition) {
+    $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
       $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings'),
       $container->get('language_manager'),
       $container->get('entity_type.manager'),
       $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.custom_link_manager'),
       $container->get('path.validator')
     );
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  public function getDataSets() {
-    $this->includeImages = $this->generator->getSetting('custom_links_include_images', FALSE);
+  public function getDataSets(): array {
+    $this->includeImages = $this->settings->get('custom_links_include_images', FALSE);
 
-    return array_values($this->generator->setVariants($this->sitemapVariant)->getCustomLinks());
+    $custom_link_settings = $this->customLinks->setVariants($this->sitemap->id())->get();
+    $custom_link_settings = $custom_link_settings ? reset($custom_link_settings) : [];
+
+    return array_values($custom_link_settings);
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  protected function processDataSet($data_set) {
-    if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($data_set['path'])) {
-      $this->logger->m(self::PATH_DOES_NOT_EXIST_MESSAGE,
-        ['@path' => $data_set['path'], '@custom_paths_url' => $GLOBALS['base_url'] . '/admin/config/search/simplesitemap/custom'])
+  protected function processDataSet($data_set): array {
+    if (!$this->pathValidator->getUrlIfValidWithoutAccessCheck($data_set['path'])) {
+      $this->logger->m(self::PATH_DOES_NOT_EXIST_MESSAGE, [
+        '@path' => $data_set['path'],
+        '@custom_paths_url' => Url::fromRoute('simple_sitemap.custom')->setAbsolute()->toString(),
+      ])
         ->display('warning', 'administer sitemap settings')
         ->log('warning');
 
-      return FALSE;
+      throw new SkipElementException();
     }
 
     $url_object = Url::fromUserInput($data_set['path'])->setAbsolute();
-    $path = $url_object->getInternalPath();
 
     $entity = $this->entityHelper->getEntityFromUrlObject($url_object);
 
@@ -123,16 +156,20 @@ protected function processDataSet($data_set) {
       'lastmod' => !empty($entity) && method_exists($entity, 'getChangedTime')
         ? date('c', $entity->getChangedTime())
         : NULL,
-      'priority' => isset($data_set['priority']) ? $data_set['priority'] : NULL,
+      'priority' => $data_set['priority'] ?? NULL,
       'changefreq' => !empty($data_set['changefreq']) ? $data_set['changefreq'] : NULL,
       'images' => $this->includeImages && !empty($entity)
         ? $this->getEntityImageData($entity)
         : [],
       'meta' => [
-        'path' => $path,
-      ]
+        'path' => $url_object->getInternalPath(),
+      ],
     ];
 
+    if (($query = $url_object->getOption('query')) && is_array($query)) {
+      $path_data['meta']['query'] = UrlHelper::buildQuery($query);
+    }
+
     // Additional info useful in hooks.
     if (!empty($entity)) {
       $path_data['meta']['entity_info'] = [
@@ -143,4 +180,5 @@ protected function processDataSet($data_set) {
 
     return $path_data;
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityMenuLinkContentUrlGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityMenuLinkContentUrlGenerator.php
index ca136153672718aab164c2e7081288509ee66779..2bba000d9e753663c34251e685efa91259669a1d 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityMenuLinkContentUrlGenerator.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityMenuLinkContentUrlGenerator.php
@@ -2,19 +2,20 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
-use Drupal\simple_sitemap\EntityHelper;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Exception\SkipElementException;
 use Drupal\simple_sitemap\Logger;
-use Drupal\simple_sitemap\Simplesitemap;
+use Drupal\simple_sitemap\Manager\EntityManager;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Menu\MenuLinkTreeInterface;
-use Drupal\Core\Menu\MenuLinkBase;
 
 /**
- * Class EntityMenuLinkContentUrlGenerator
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides the menu link URL generator.
  *
  * @UrlGenerator(
  *   id = "entity_menu_link_content",
@@ -30,75 +31,102 @@
 class EntityMenuLinkContentUrlGenerator extends EntityUrlGeneratorBase {
 
   /**
-   * @var \Drupal\Core\Menu\MenuLinkTree
+   * The menu tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
    */
   protected $menuLinkTree;
 
+  /**
+   * The simple_sitemap.entity_manager service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\EntityManager
+   */
+  protected $entitiesManager;
+
   /**
    * EntityMenuLinkContentUrlGenerator constructor.
+   *
    * @param array $configuration
-   * @param $plugin_id
-   * @param $plugin_definition
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
    * @param \Drupal\simple_sitemap\Logger $logger
+   *   Simple XML Sitemap logger.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
+   *   The entity type manager.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Manager\EntityManager $entities_manager
+   *   The simple_sitemap.entity_manager service.
    * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
+   *   The menu tree service.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
     Logger $logger,
+    Settings $settings,
     LanguageManagerInterface $language_manager,
     EntityTypeManagerInterface $entity_type_manager,
-    EntityHelper $entityHelper,
+    EntityHelper $entity_helper,
+    EntityManager $entities_manager,
     MenuLinkTreeInterface $menu_link_tree
   ) {
     parent::__construct(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $generator,
       $logger,
+      $settings,
       $language_manager,
       $entity_type_manager,
-      $entityHelper
+      $entity_helper
     );
+    $this->entitiesManager = $entities_manager;
     $this->menuLinkTree = $menu_link_tree;
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public static function create(
     ContainerInterface $container,
     array $configuration,
     $plugin_id,
-    $plugin_definition) {
+    $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
       $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings'),
       $container->get('language_manager'),
       $container->get('entity_type.manager'),
       $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.entity_manager'),
       $container->get('menu.link_tree')
     );
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  public function getDataSets() {
+  public function getDataSets(): array {
     $data_sets = [];
-    $bundle_settings = $this->generator
-      ->setVariants($this->sitemapVariant)
-      ->getBundleSettings();
-    if (!empty($bundle_settings['menu_link_content'])) {
-      foreach ($bundle_settings['menu_link_content'] as $bundle_name => $bundle_settings) {
-        if (!empty($bundle_settings['index'])) {
+    $bundle_settings = $this->entitiesManager
+      ->setVariants($this->sitemap->id())
+      ->getAllBundleSettings();
+    if (!empty($bundle_settings[$this->sitemap->id()]['menu_link_content'])) {
+      foreach ($bundle_settings[$this->sitemap->id()]['menu_link_content'] as $bundle_name => $settings) {
+        if (!empty($settings['index'])) {
 
           // Retrieve the expanded tree.
           $tree = $this->menuLinkTree->load($bundle_name, new MenuTreeParameters());
@@ -107,7 +135,7 @@ public function getDataSets() {
             ['callable' => 'menu.default_tree_manipulators:flatten'],
           ]);
 
-          foreach ($tree as $i => $item) {
+          foreach ($tree as $item) {
             $data_sets[] = $item->link;
           }
         }
@@ -118,60 +146,61 @@ public function getDataSets() {
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    *
    * @todo Find a way to be able to check if a menu link still exists. This is difficult as we don't operate on MenuLinkContent entities, but on Link entities directly (as some menu links are not MenuLinkContent entities).
    */
-  protected function processDataSet($data_set) {
+  protected function processDataSet($data_set): array {
 
-    /** @var  MenuLinkBase $data_set */
+    /** @var \Drupal\Core\Menu\MenuLinkInterface $data_set */
     if (!$data_set->isEnabled()) {
-      return FALSE;
+      throw new SkipElementException();
     }
 
     $url_object = $data_set->getUrlObject()->setAbsolute();
 
     // Do not include external paths.
     if ($url_object->isExternal()) {
-      return FALSE;
+      throw new SkipElementException();
     }
 
     // If not a menu_link_content link, use bundle settings.
     $meta_data = $data_set->getMetaData();
     if (empty($meta_data['entity_id'])) {
-      $entity_settings = $this->generator
-        ->setVariants($this->sitemapVariant)
+      $entity_settings = $this->entitiesManager
+        ->setVariants($this->sitemap->id())
         ->getBundleSettings('menu_link_content', $data_set->getMenuName());
     }
 
-    // If menu link is of entity type menu_link_content, take under account its entity override.
+    // If menu link is of entity type menu_link_content, take under account its
+    // entity override.
     else {
-      $entity_settings = $this->generator
-        ->setVariants($this->sitemapVariant)
+      $entity_settings = $this->entitiesManager
+        ->setVariants($this->sitemap->id())
         ->getEntityInstanceSettings('menu_link_content', $meta_data['entity_id']);
-
-      if (empty($entity_settings['index'])) {
-        return FALSE;
-      }
     }
+    if (empty($entity_settings[$this->sitemap->id()]['index'])) {
+      throw new SkipElementException();
+    }
+    $entity_settings = reset($entity_settings);
 
     if ($url_object->isRouted()) {
 
       // Do not include paths that have no URL.
       if (in_array($url_object->getRouteName(), ['<nolink>', '<none>'])) {
-        return FALSE;
+        throw new SkipElementException();
       }
 
       $path = $url_object->getInternalPath();
     }
     // There can be internal paths that are not rooted, like 'base:/path'.
-    else { // Handle base scheme.
-      if (strpos($uri = $url_object->toUriString(), 'base:/') === 0 ) {
-        $path = $uri[6] === '/' ? substr($uri, 7) : substr($uri, 6);
-      }
-      else { // Handle unforeseen schemes.
-        $path = $uri;
-      }
+    elseif (strpos($uri = $url_object->toUriString(), 'base:/') === 0) {
+      // Handle base scheme.
+      $path = $uri[6] === '/' ? substr($uri, 7) : substr($uri, 6);
+    }
+    else {
+      // Handle unforeseen schemes.
+      $path = $uri;
     }
 
     $entity = $this->entityHelper->getEntityFromUrlObject($url_object);
@@ -181,7 +210,7 @@ protected function processDataSet($data_set) {
       'lastmod' => !empty($entity) && method_exists($entity, 'getChangedTime')
         ? date('c', $entity->getChangedTime())
         : NULL,
-      'priority' => isset($entity_settings['priority']) ? $entity_settings['priority'] : NULL,
+      'priority' => $entity_settings['priority'] ?? NULL,
       'changefreq' => !empty($entity_settings['changefreq']) ? $entity_settings['changefreq'] : NULL,
       'images' => !empty($entity_settings['include_images']) && !empty($entity)
         ? $this->getEntityImageData($entity)
@@ -190,7 +219,7 @@ protected function processDataSet($data_set) {
       // Additional info useful in hooks.
       'meta' => [
         'path' => $path,
-      ]
+      ],
     ];
     if (!empty($entity)) {
       $path_data['meta']['entity_info'] = [
@@ -201,4 +230,5 @@ protected function processDataSet($data_set) {
 
     return $path_data;
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGenerator.php
index 8c90982d309b31ff2f9a93874cbecfb8352cbf15..3ad4dc79540a01dee4920c2f5c10d8ff0e05f35e 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGenerator.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGenerator.php
@@ -2,18 +2,21 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
-use Drupal\simple_sitemap\EntityHelper;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Exception\SkipElementException;
 use Drupal\simple_sitemap\Logger;
-use Drupal\simple_sitemap\Simplesitemap;
+use Drupal\simple_sitemap\Manager\EntityManager;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * Class EntityUrlGenerator
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides the entity URL generator.
  *
  * @UrlGenerator(
  *   id = "entity",
@@ -24,42 +27,69 @@
 class EntityUrlGenerator extends EntityUrlGeneratorBase {
 
   /**
+   * The UrlGenerator plugins manager.
+   *
    * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
    */
   protected $urlGeneratorManager;
 
   /**
-   * @var integer
+   * Entities per queue item.
+   *
+   * @var int
    */
   protected $entitiesPerDataset;
 
   /**
+   * The memory cache.
+   *
    * @var \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface
    */
   protected $entityMemoryCache;
 
+  /**
+   * The simple_sitemap.entity_manager service.
+   *
+   * @var \Drupal\simple_sitemap\Manager\EntityManager
+   */
+  protected $entitiesManager;
+
   /**
    * EntityUrlGenerator constructor.
+   *
    * @param array $configuration
-   * @param $plugin_id
-   * @param $plugin_definition
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
    * @param \Drupal\simple_sitemap\Logger $logger
+   *   Simple XML Sitemap logger.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
+   *   The entity type manager.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
+   * @param \Drupal\simple_sitemap\Manager\EntityManager $entities_manager
+   *   The simple_sitemap.entity_manager service.
    * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager $url_generator_manager
+   *   The UrlGenerator plugins manager.
    * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
+   *   The memory cache.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
     Logger $logger,
+    Settings $settings,
     LanguageManagerInterface $language_manager,
     EntityTypeManagerInterface $entity_type_manager,
-    EntityHelper $entityHelper,
+    EntityHelper $entity_helper,
+    EntityManager $entities_manager,
     UrlGeneratorManager $url_generator_manager,
     MemoryCacheInterface $memory_cache
   ) {
@@ -67,68 +97,75 @@ public function __construct(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $generator,
       $logger,
+      $settings,
       $language_manager,
       $entity_type_manager,
-      $entityHelper
+      $entity_helper
     );
+    $this->entitiesManager = $entities_manager;
     $this->urlGeneratorManager = $url_generator_manager;
     $this->entityMemoryCache = $memory_cache;
-    $this->entitiesPerDataset = $this->generator->getSetting('entities_per_queue_item', 50);
+    $this->entitiesPerDataset = $this->settings->get('entities_per_queue_item', 50);
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public static function create(
     ContainerInterface $container,
     array $configuration,
     $plugin_id,
-    $plugin_definition) {
+    $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
       $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings'),
       $container->get('language_manager'),
       $container->get('entity_type.manager'),
       $container->get('simple_sitemap.entity_helper'),
+      $container->get('simple_sitemap.entity_manager'),
       $container->get('plugin.manager.simple_sitemap.url_generator'),
       $container->get('entity.memory_cache')
     );
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  public function getDataSets() {
+  public function getDataSets(): array {
     $data_sets = [];
     $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
+    $all_bundle_settings = $this->entitiesManager->setVariants($this->sitemap->id())->getAllBundleSettings();
+    if (isset($all_bundle_settings[$this->sitemap->id()])) {
+      foreach ($all_bundle_settings[$this->sitemap->id()] as $entity_type_name => $bundles) {
+        if (!isset($sitemap_entity_types[$entity_type_name])) {
+          continue;
+        }
 
-    foreach ($this->generator->setVariants($this->sitemapVariant)->getBundleSettings() as $entity_type_name => $bundles) {
-      if (isset($sitemap_entity_types[$entity_type_name])) {
-
-        // Skip this entity type if another plugin is written to override its generation.
-        foreach ($this->urlGeneratorManager->getDefinitions() as $plugin) {
-          if (isset($plugin['settings']['overrides_entity_type'])
-            && $plugin['settings']['overrides_entity_type'] === $entity_type_name) {
-            continue 2;
-          }
+        if ($this->isOverwrittenForEntityType($entity_type_name)) {
+          continue;
         }
 
         $entityTypeStorage = $this->entityTypeManager->getStorage($entity_type_name);
         $keys = $sitemap_entity_types[$entity_type_name]->getKeys();
 
         foreach ($bundles as $bundle_name => $bundle_settings) {
-          if (!empty($bundle_settings['index'])) {
+          if ($bundle_settings['index']) {
             $query = $entityTypeStorage->getQuery();
 
-            if (empty($keys['id'])) {
-              $query->sort($keys['id'], 'ASC');
+            if (!empty($keys['id'])) {
+              $query->sort($keys['id']);
             }
             if (!empty($keys['bundle'])) {
               $query->condition($keys['bundle'], $bundle_name);
             }
-            if (!empty($keys['status'])) {
+            if (!empty($keys['published'])) {
+              $query->condition($keys['published'], 1);
+            }
+            elseif (!empty($keys['status'])) {
               $query->condition($keys['status'], 1);
             }
 
@@ -161,65 +198,100 @@ public function getDataSets() {
   }
 
   /**
-   * @inheritdoc
+   * Check if plugin overrides this plugin's generation for given entity type.
+   *
+   * @param string $entity_type_name
+   *   The entity type name.
+   *
+   * @return bool
+   *   TRUE if another plugin overrides and FALSE otherwise.
    */
-  protected function processDataSet($data_set) {
-    $entities = $this->entityTypeManager->getStorage($data_set['entity_type'])->loadMultiple((array) $data_set['id']);
-    if (empty($entities)) {
-      return FALSE;
+  protected function isOverwrittenForEntityType(string $entity_type_name): bool {
+    foreach ($this->urlGeneratorManager->getDefinitions() as $plugin) {
+      if (isset($plugin['settings']['overrides_entity_type'])
+        && $plugin['settings']['overrides_entity_type'] === $entity_type_name) {
+        return TRUE;
+      }
     }
 
-    $paths = [];
-    foreach ($entities as $entity) {
-      $entity_settings = $this->generator
-        ->setVariants($this->sitemapVariant)
-        ->getEntityInstanceSettings($entity->getEntityTypeId(), $entity->id());
+    return FALSE;
+  }
 
-      if (empty($entity_settings['index'])) {
+  /**
+   * {@inheritdoc}
+   */
+  protected function processDataSet($data_set): array {
+    foreach ($this->entityTypeManager->getStorage($data_set['entity_type'])->loadMultiple((array) $data_set['id']) as $entity) {
+      try {
+        $paths[] = $this->processEntity($entity);
+      }
+      catch (SkipElementException $e) {
         continue;
       }
+    }
 
-      $url_object = $entity->toUrl()->setAbsolute();
+    return $paths ?? [];
+  }
 
-      // Do not include external paths.
-      if (!$url_object->isRouted()) {
-        continue;
-      }
+  /**
+   * Processes the given entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to process.
+   *
+   * @return array
+   *   Processing result.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Core\Entity\EntityMalformedException
+   */
+  protected function processEntity(ContentEntityInterface $entity): array {
+    $entity_settings = $this->entitiesManager
+      ->setVariants($this->sitemap->id())
+      ->getEntityInstanceSettings($entity->getEntityTypeId(), $entity->id());
 
-      $paths[] = [
-        'url' => $url_object,
-        'lastmod' => method_exists($entity, 'getChangedTime')
-          ? date('c', $entity->getChangedTime())
-          : NULL,
-        'priority' => isset($entity_settings['priority']) ? $entity_settings['priority'] : NULL,
-        'changefreq' => !empty($entity_settings['changefreq']) ? $entity_settings['changefreq'] : NULL,
-        'images' => !empty($entity_settings['include_images'])
-          ? $this->getEntityImageData($entity)
-          : [],
+    if (empty($entity_settings[$this->sitemap->id()]['index'])) {
+      throw new SkipElementException();
+    }
 
-        // Additional info useful in hooks.
-        'meta' => [
-          'path' => $url_object->getInternalPath(),
-          'entity_info' => [
-            'entity_type' => $entity->getEntityTypeId(),
-            'id' => $entity->id(),
-          ],
-        ]
-      ];
+    $entity_settings = $entity_settings[$this->sitemap->id()];
+    $url_object = $entity->toUrl()->setAbsolute();
+
+    // Do not include external paths.
+    if (!$url_object->isRouted()) {
+      throw new SkipElementException();
     }
-    return $paths;
+
+    return [
+      'url' => $url_object,
+      'lastmod' => method_exists($entity, 'getChangedTime')
+        ? date('c', $entity->getChangedTime())
+        : NULL,
+      'priority' => $entity_settings['priority'] ?? NULL,
+      'changefreq' => !empty($entity_settings['changefreq']) ? $entity_settings['changefreq'] : NULL,
+      'images' => !empty($entity_settings['include_images'])
+        ? $this->getEntityImageData($entity)
+        : [],
+
+        // Additional info useful in hooks.
+      'meta' => [
+        'path' => $url_object->getInternalPath(),
+        'entity_info' => [
+          'entity_type' => $entity->getEntityTypeId(),
+          'id' => $entity->id(),
+        ],
+      ],
+    ];
   }
 
   /**
-   * @inheritdoc
+   * {@inheritdoc}
    */
-  public function generate($data_set) {
-    if (empty($path_data_sets = $this->processDataSet($data_set))) {
-      return [];
-    }
-    
+  public function generate($data_set): array {
+    $path_data_sets = $this->processDataSet($data_set);
     $url_variant_sets = [];
-    foreach ($path_data_sets as $key => $path_data) {
+    foreach ($path_data_sets as $path_data) {
       if (isset($path_data['url']) && $path_data['url'] instanceof Url) {
         $url_object = $path_data['url'];
         unset($path_data['url']);
@@ -227,7 +299,7 @@ public function generate($data_set) {
       }
     }
 
-    // Make sure to clear entity memory cache so it does not build up resulting
+    // Make sure to clear entity memory cache, so it does not build up resulting
     // in a constant increase of memory.
     // See https://www.drupal.org/project/simple_sitemap/issues/3170261 and
     // https://www.drupal.org/project/simple_sitemap/issues/3202233
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGeneratorBase.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGeneratorBase.php
index 8dd8e4281237bbc386cdc5077b13d81b832bc8e0..bc872d0cb179cc4d492f644e2493d9e4aa35c0dc 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGeneratorBase.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGeneratorBase.php
@@ -2,92 +2,108 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\simple_sitemap\Entity\EntityHelper;
+use Drupal\simple_sitemap\Exception\SkipElementException;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Url;
 use Drupal\file\Entity\File;
-use Drupal\simple_sitemap\EntityHelper;
 use Drupal\simple_sitemap\Logger;
-use Drupal\simple_sitemap\Simplesitemap;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AnonymousUserSession;
 
 /**
- * Class EntityUrlGeneratorBase
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides a base class for entity UrlGenerator plugins.
  */
 abstract class EntityUrlGeneratorBase extends UrlGeneratorBase {
 
   /**
+   * Local cache for the available language objects.
+   *
    * @var \Drupal\Core\Language\LanguageInterface[]
    */
   protected $languages;
 
   /**
+   * Default language ID.
+   *
    * @var string
    */
   protected $defaultLanguageId;
 
   /**
+   * The entity type manager.
+   *
    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
   protected $entityTypeManager;
 
   /**
-   * @var \Drupal\Core\Entity\EntityInterface|null
+   * An account implementation representing an anonymous user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
    */
   protected $anonUser;
 
   /**
-   * @var \Drupal\simple_sitemap\EntityHelper
+   * Helper class for working with entities.
+   *
+   * @var \Drupal\simple_sitemap\Entity\EntityHelper
    */
   protected $entityHelper;
 
   /**
-   * @var bool
-   */
-  protected $isMultilingualSitemap;
-
-  /**
-   * UrlGeneratorBase constructor.
+   * EntityUrlGeneratorBase constructor.
+   *
    * @param array $configuration
-   * @param $plugin_id
-   * @param $plugin_definition
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\simple_sitemap\Logger $logger
+   *   Simple XML Sitemap logger.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   * @param \Drupal\simple_sitemap\Logger $logger
-   * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
+   *   The entity type manager.
+   * @param \Drupal\simple_sitemap\Entity\EntityHelper $entity_helper
+   *   Helper class for working with entities.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
     Logger $logger,
+    Settings $settings,
     LanguageManagerInterface $language_manager,
     EntityTypeManagerInterface $entity_type_manager,
-    EntityHelper $entityHelper
+    EntityHelper $entity_helper
   ) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition, $generator, $logger);
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $logger, $settings);
     $this->languages = $language_manager->getLanguages();
     $this->defaultLanguageId = $language_manager->getDefaultLanguage()->getId();
     $this->entityTypeManager = $entity_type_manager;
     $this->anonUser = new AnonymousUserSession();
-    $this->entityHelper = $entityHelper;
-    $this->isMultilingualSitemap = SitemapGeneratorBase::isMultilingualSitemap();
+    $this->entityHelper = $entity_helper;
   }
 
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
       $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings'),
       $container->get('language_manager'),
       $container->get('entity_type.manager'),
       $container->get('simple_sitemap.entity_helper')
@@ -95,27 +111,35 @@ public static function create(ContainerInterface $container, array $configuratio
   }
 
   /**
+   * Gets the URL variants.
+   *
    * @param array $path_data
+   *   The path data.
    * @param \Drupal\Core\Url $url_object
+   *   The URL object.
+   *
    * @return array
+   *   The URL variants.
+   *
    * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
-  protected function getUrlVariants(array $path_data, Url $url_object) {
+  protected function getUrlVariants(array $path_data, Url $url_object): array {
     $url_variants = [];
 
-    if (!$this->isMultilingualSitemap || !$url_object->isRouted()) {
+    if (!$this->sitemap->isMultilingual() || !$url_object->isRouted()) {
 
-      // Not a routed URL or URL language negotiation disabled: Including only default variant.
+      // Not a routed URL or URL language negotiation disabled: Including only
+      // default variant.
       $alternate_urls = $this->getAlternateUrlsForDefaultLanguage($url_object);
     }
-    elseif ($this->settings['skip_untranslated']
+    elseif ($this->settings->get('skip_untranslated')
       && ($entity = $this->entityHelper->getEntityFromUrlObject($url_object)) instanceof ContentEntityInterface) {
 
-      /** @var ContentEntityInterface $entity */
+      /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
       $translation_languages = $entity->getTranslationLanguages();
-      if (isset($translation_languages[Language::LANGCODE_NOT_SPECIFIED])
-        || isset($translation_languages[Language::LANGCODE_NOT_APPLICABLE])) {
+      if (isset($translation_languages[LanguageInterface::LANGCODE_NOT_SPECIFIED])
+        || isset($translation_languages[LanguageInterface::LANGCODE_NOT_APPLICABLE])) {
 
         // Content entity's language is unknown: Including only default variant.
         $alternate_urls = $this->getAlternateUrlsForDefaultLanguage($url_object);
@@ -133,19 +157,24 @@ protected function getUrlVariants(array $path_data, Url $url_object) {
     foreach ($alternate_urls as $langcode => $url) {
       $url_variants[] = $path_data + [
         'langcode' => $langcode,
-          'url' => $url,
-          'alternate_urls' => $alternate_urls
-        ];
+        'url' => $url,
+        'alternate_urls' => $alternate_urls,
+      ];
     }
 
     return $url_variants;
   }
 
   /**
+   * Gets the alternate URLs for default language.
+   *
    * @param \Drupal\Core\Url $url_object
+   *   The URL object.
+   *
    * @return array
+   *   An array of alternate URLs.
    */
-  protected function getAlternateUrlsForDefaultLanguage(Url $url_object) {
+  protected function getAlternateUrlsForDefaultLanguage(Url $url_object): array {
     $alternate_urls = [];
     if ($url_object->access($this->anonUser)) {
       $alternate_urls[$this->defaultLanguageId] = $this->replaceBaseUrlWithCustom($url_object
@@ -157,16 +186,21 @@ protected function getAlternateUrlsForDefaultLanguage(Url $url_object) {
   }
 
   /**
+   * Gets the alternate URLs for translated languages.
+   *
    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to process.
    * @param \Drupal\Core\Url $url_object
+   *   The URL object.
+   *
    * @return array
+   *   An array of alternate URLs.
    */
-  protected function getAlternateUrlsForTranslatedLanguages(ContentEntityInterface $entity, Url $url_object) {
+  protected function getAlternateUrlsForTranslatedLanguages(ContentEntityInterface $entity, Url $url_object): array {
     $alternate_urls = [];
 
-    /** @var Language $language */
     foreach ($entity->getTranslationLanguages() as $language) {
-      if (!isset($this->settings['excluded_languages'][$language->getId()]) || $language->isDefault()) {
+      if (!isset($this->settings->get('excluded_languages')[$language->getId()]) || $language->isDefault()) {
         if ($entity->getTranslation($language->getId())->access('view', $this->anonUser)) {
           $alternate_urls[$language->getId()] = $this->replaceBaseUrlWithCustom($url_object
             ->setAbsolute()->setOption('language', $language)->toString()
@@ -179,14 +213,19 @@ protected function getAlternateUrlsForTranslatedLanguages(ContentEntityInterface
   }
 
   /**
+   * Gets the alternate URLs for all languages.
+   *
    * @param \Drupal\Core\Url $url_object
+   *   The URL object.
+   *
    * @return array
+   *   An array of alternate URLs.
    */
-  protected function getAlternateUrlsForAllLanguages(Url $url_object) {
+  protected function getAlternateUrlsForAllLanguages(Url $url_object): array {
     $alternate_urls = [];
     if ($url_object->access($this->anonUser)) {
       foreach ($this->languages as $language) {
-        if (!isset($this->settings['excluded_languages'][$language->getId()]) || $language->isDefault()) {
+        if (!isset($this->settings->get('excluded_languages')[$language->getId()]) || $language->isDefault()) {
           $alternate_urls[$language->getId()] = $this->replaceBaseUrlWithCustom($url_object
             ->setAbsolute()->setOption('language', $language)->toString()
           );
@@ -198,36 +237,48 @@ protected function getAlternateUrlsForAllLanguages(Url $url_object) {
   }
 
   /**
-   * @param mixed $data_set
-   * @return array
+   * {@inheritdoc}
+   *
    * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
-  public function generate($data_set) {
-    $path_data = $this->processDataSet($data_set);
-    if (isset($path_data['url']) && $path_data['url'] instanceof Url) {
-      $url_object = $path_data['url'];
-      unset($path_data['url']);
-      return $this->getUrlVariants($path_data, $url_object);
+  public function generate($data_set): array {
+    try {
+      $path_data = $this->processDataSet($data_set);
+      if (isset($path_data['url']) && $path_data['url'] instanceof Url) {
+        $url_object = $path_data['url'];
+        unset($path_data['url']);
+        return $this->getUrlVariants($path_data, $url_object);
+      }
+      return [$path_data];
+    }
+    catch (SkipElementException $e) {
+      return [];
     }
-
-    return FALSE !== $path_data ? [$path_data] : [];
   }
 
   /**
+   * Gets the image data for specified entity.
+   *
    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to process.
    *
    * @return array
+   *   The image data.
    */
-  protected function getEntityImageData(ContentEntityInterface $entity) {
+  protected function getEntityImageData(ContentEntityInterface $entity): array {
     $image_data = [];
+
+    /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
+    $file_url_generator = \Drupal::service('file_url_generator');
+
     foreach ($entity->getFieldDefinitions() as $field) {
       if ($field->getType() === 'image') {
         foreach ($entity->get($field->getName())->getValue() as $value) {
-          if (!empty($file = File::load($value['target_id']))) {
+          if (NULL !== ($file = File::load($value['target_id']))) {
             $image_data[] = [
               'path' => $this->replaceBaseUrlWithCustom(
-                file_create_url($file->getFileUri())
+                $file_url_generator->generateAbsoluteString($file->getFileUri())
               ),
               'alt' => $value['alt'],
               'title' => $value['title'],
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/SitemapIndexUrlGenerator.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/SitemapIndexUrlGenerator.php
new file mode 100644
index 0000000000000000000000000000000000000000..17772c2f1a9712772b038a18d6aab38c971e5d03
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/SitemapIndexUrlGenerator.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
+
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Exception\SkipElementException;
+
+/**
+ * Class VariantIndexUrlGenerator.
+ *
+ * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ *
+ * @UrlGenerator(
+ *   id = "index",
+ *   label = @Translation("Sitemap URL generator"),
+ *   description = @Translation("Generates sitemap URLs for a sitemap index."),
+ * )
+ */
+class SitemapIndexUrlGenerator extends UrlGeneratorBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataSets(): array {
+    return \Drupal::entityTypeManager()
+      ->getStorage('simple_sitemap')
+      ->getQuery()
+      ->sort('weight')
+      ->accessCheck(FALSE)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function processDataSet($data_set): array {
+    if (($sitemap = SimpleSitemap::load($data_set))
+      && $sitemap->status()
+      && $sitemap->getType()->getSitemapGenerator()->getPluginId() !== 'index') {
+      $url_object = $sitemap->toUrl()->setAbsolute();
+
+      return [
+        'url' => $url_object->toString(),
+        'lastmod' => date('c', $sitemap->fromPublished()->getCreated()),
+
+        // Additional info useful in hooks.
+        'meta' => [
+          'path' => $url_object->getInternalPath(),
+          'entity_info' => [
+            'entity_type' => $sitemap->getEntityTypeId(),
+            'id' => $sitemap->id(),
+          ],
+        ],
+      ];
+    }
+
+    throw new SkipElementException();
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorBase.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorBase.php
index 297696e241e187c13e2298d91fdc8f0123f2f701..df724e2a5ba11bfde30a076d9847d51d19e35077 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorBase.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorBase.php
@@ -2,115 +2,128 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SimplesitemapPluginBase;
+use Drupal\simple_sitemap\Exception\SkipElementException;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginBase;
+use Drupal\simple_sitemap\Entity\SimpleSitemapInterface;
+use Drupal\simple_sitemap\Settings;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\simple_sitemap\Logger;
-use Drupal\simple_sitemap\Simplesitemap;
 
 /**
- * Class UrlGeneratorBase
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides a base class for UrlGenerator plugins.
  */
-abstract class UrlGeneratorBase extends SimplesitemapPluginBase implements UrlGeneratorInterface {
-
-  /**
-   * @var \Drupal\simple_sitemap\Simplesitemap
-   */
-  protected $generator;
+abstract class UrlGeneratorBase extends SimpleSitemapPluginBase implements UrlGeneratorInterface {
 
   /**
+   * Simple XML Sitemap logger.
+   *
    * @var \Drupal\simple_sitemap\Logger
    */
   protected $logger;
 
   /**
-   * @var array
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
    */
   protected $settings;
 
   /**
-   * @var string
+   * The sitemap entity.
+   *
+   * @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface
    */
-  protected $sitemapVariant;
+  protected $sitemap;
 
   /**
    * UrlGeneratorBase constructor.
+   *
    * @param array $configuration
-   * @param $plugin_id
-   * @param $plugin_definition
-   * @param \Drupal\simple_sitemap\Simplesitemap $generator
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
    * @param \Drupal\simple_sitemap\Logger $logger
+   *   Simple XML Sitemap logger.
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    */
   public function __construct(
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    Simplesitemap $generator,
-    Logger $logger
+    Logger $logger,
+    Settings $settings
   ) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->generator = $generator;
     $this->logger = $logger;
+    $this->settings = $settings;
   }
 
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): SimpleSitemapPluginBase {
     return new static(
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('simple_sitemap.generator'),
-      $container->get('simple_sitemap.logger')
+      $container->get('simple_sitemap.logger'),
+      $container->get('simple_sitemap.settings')
     );
   }
 
   /**
-   * @param array $settings
-   * @return $this
+   * {@inheritdoc}
    */
-  public function setSettings(array $settings) {
-    $this->settings = $settings;
-
-    return $this;
-  }
-
-  /**
-   * @param string $sitemap_variant
-   * @return $this
-   */
-  public function setSitemapVariant($sitemap_variant) {
-    $this->sitemapVariant = $sitemap_variant;
+  public function setSitemap(SimpleSitemapInterface $sitemap): UrlGeneratorInterface {
+    $this->sitemap = $sitemap;
 
     return $this;
   }
 
   /**
+   * Replaces the base URL with custom URL from settings.
+   *
    * @param string $url
+   *   URL to process.
+   *
    * @return string
+   *   The processed URL.
    */
-  protected function replaceBaseUrlWithCustom($url) {
-    return !empty($this->settings['base_url'])
-      ? str_replace($GLOBALS['base_url'], $this->settings['base_url'], $url)
+  protected function replaceBaseUrlWithCustom(string $url): string {
+    return !empty($base_url = $this->settings->get('base_url'))
+      ? str_replace($GLOBALS['base_url'], $base_url, $url)
       : $url;
   }
 
   /**
-   * @return mixed
+   * {@inheritdoc}
    */
-  abstract public function getDataSets();
+  abstract public function getDataSets(): array;
 
   /**
-   * @param $data_set
-   * @return mixed
+   * Processes the specified dataset.
+   *
+   * @param mixed $data_set
+   *   Dataset to process.
+   *
+   * @return array
+   *   Processing result.
    */
-  abstract protected function processDataSet($data_set);
+  abstract protected function processDataSet($data_set): array;
 
   /**
-   * @param $data_set
-   * @return array
+   * {@inheritdoc}
    */
-  public function generate($data_set) {
-    $path_data = $this->processDataSet($data_set);
-
-    return FALSE !== $path_data ? [$path_data] : [];
+  public function generate($data_set): array {
+    try {
+      return [$this->processDataSet($data_set)];
+    }
+    catch (SkipElementException $e) {
+      return [];
+    }
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorInterface.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorInterface.php
index dac87e08cde70e3abbd353a148842dc963c65f9f..b012f1aa67f85112d6c58ac5f863a15ae92b01f4 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorInterface.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorInterface.php
@@ -2,17 +2,41 @@
 
 namespace Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator;
 
+use Drupal\simple_sitemap\Entity\SimpleSitemapInterface;
+use Drupal\simple_sitemap\Plugin\simple_sitemap\SimpleSitemapPluginInterface;
+
 /**
- * Interface UrlGeneratorInterface
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Provides an interface for UrlGenerator plugins.
  */
-interface UrlGeneratorInterface {
+interface UrlGeneratorInterface extends SimpleSitemapPluginInterface {
 
-  public function setSettings(array $settings);
+  /**
+   * Sets the sitemap.
+   *
+   * @param \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $sitemap
+   *   The sitemap entity to set.
+   *
+   * @return $this
+   */
+  public function setSitemap(SimpleSitemapInterface $sitemap): UrlGeneratorInterface;
 
-  public function setSitemapVariant($sitemap_variant);
+  /**
+   * Gets the datasets.
+   *
+   * @return array
+   *   The datasets.
+   */
+  public function getDataSets(): array;
 
-  public function getDataSets();
+  /**
+   * Generates URLs from specified dataset.
+   *
+   * @param mixed $data_set
+   *   The dataset to process.
+   *
+   * @return array
+   *   Generation result.
+   */
+  public function generate($data_set): array;
 
-  public function generate($data_set);
 }
diff --git a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorManager.php b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorManager.php
index 7fb08b630457bc5fe985f5d83e63dfdf529f077e..527d8b1e3db44f47d2a7c7c964ce4364cad908b2 100644
--- a/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorManager.php
+++ b/web/modules/simple_sitemap/src/Plugin/simple_sitemap/UrlGenerator/UrlGeneratorManager.php
@@ -5,18 +5,23 @@
 use Drupal\Core\Plugin\DefaultPluginManager;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\simple_sitemap\Annotation\UrlGenerator;
 
 /**
- * Class UrlGeneratorManager
- * @package Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator
+ * Manages discovery of UrlGenerator plugins.
  */
 class UrlGeneratorManager extends DefaultPluginManager {
 
   /**
    * UrlGeneratorManager constructor.
+   *
    * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
    */
   public function __construct(
     \Traversable $namespaces,
@@ -27,11 +32,12 @@ public function __construct(
       'Plugin/simple_sitemap/UrlGenerator',
       $namespaces,
       $module_handler,
-      'Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorInterface',
-      'Drupal\simple_sitemap\Annotation\UrlGenerator'
+      UrlGeneratorInterface::class,
+      UrlGenerator::class
     );
 
     $this->alterInfo('simple_sitemap_url_generators');
     $this->setCacheBackend($cache_backend, 'simple_sitemap:url_generator');
   }
+
 }
diff --git a/web/modules/simple_sitemap/src/Queue/BatchTrait.php b/web/modules/simple_sitemap/src/Queue/BatchTrait.php
index 77f5d9f7ec70fd121970b2e377a89a37d945a6f2..44a8dbbea79b4e07eee60c8c9f2028942ff7020c 100644
--- a/web/modules/simple_sitemap/src/Queue/BatchTrait.php
+++ b/web/modules/simple_sitemap/src/Queue/BatchTrait.php
@@ -4,29 +4,46 @@
 
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
+/**
+ * Provides a helper with batch callbacks.
+ */
 trait BatchTrait {
 
   use StringTranslationTrait;
 
   /**
+   * An associative array defining the batch.
+   *
    * @var array
    */
   protected $batch;
 
+  /**
+   * Message displayed if an error occurred while processing the batch.
+   *
+   * @var string
+   */
   protected static $batchErrorMessage = 'The generation failed to finish. It can be continued manually on the module\'s settings page, or via drush.';
 
   /**
+   * Adds a new batch.
+   *
    * @param string $from
+   *   The source of generation.
    * @param array|null $variants
+   *   An array of variants.
+   *
    * @return bool
+   *   TRUE if batch was added and FALSE otherwise.
    */
-  public function batchGenerateSitemap($from = self::GENERATE_TYPE_FORM, $variants = NULL) {
+  public function batchGenerate(string $from = self::GENERATE_TYPE_FORM, ?array $variants = NULL): bool {
     $this->batch = [
       'title' => $this->t('Generating XML sitemaps'),
       'init_message' => $this->t('Initializing...'),
+      // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
       'error_message' => $this->t(self::$batchErrorMessage),
-      'progress_message' => $this->t('Processing items from the queue.<br>Each sitemap variant gets published after all of its items have been processed.'),
-      'operations' => [[ __CLASS__ . '::' . 'doBatchGenerateSitemap', []]],
+      'progress_message' => $this->t('Processing items from the queue.<br>Each sitemap gets published after all of its items have been processed.'),
+      'operations' => [[__CLASS__ . '::' . 'doBatchGenerate', []]],
       'finished' => [__CLASS__, 'finishGeneration'],
     ];
 
@@ -48,26 +65,33 @@ public function batchGenerateSitemap($from = self::GENERATE_TYPE_FORM, $variants
         drush_backend_batch_process();
         return TRUE;
     }
+
     return FALSE;
   }
 
   /**
-   * @param $context
+   * Processes the batch item.
+   *
+   * @param mixed $context
+   *   The batch context.
+   *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    *
-   * @todo Variants into generateSitemap().
+   * @todo Variants into generate().
    */
-  public static function doBatchGenerateSitemap(&$context) {
+  public static function doBatchGenerate(&$context): void {
 
     /** @var \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker */
     $queue_worker = \Drupal::service('simple_sitemap.queue_worker');
 
-    $queue_worker->generateSitemap();
+    $queue_worker->generate();
     $processed_element_count = $queue_worker->getProcessedElementCount();
     $original_element_count = $queue_worker->getInitialElementCount();
 
     $context['message'] = t('@indexed out of @total total queue items have been processed.', [
-      '@indexed' => $processed_element_count, '@total' => $original_element_count]);
+      '@indexed' => $processed_element_count,
+      '@total' => $original_element_count,
+    ]);
     $context['finished'] = $original_element_count > 0 ? ($processed_element_count / $original_element_count) : 1;
   }
 
@@ -75,21 +99,27 @@ public static function doBatchGenerateSitemap(&$context) {
    * Callback function called by the batch API when all operations are finished.
    *
    * @param bool $success
+   *   Indicates whether the batch process was successful.
    * @param array $results
+   *   Results information passed from the processing callback.
    * @param array $operations
+   *   A list of the operations that had not been completed by the batch API.
    *
    * @return bool
+   *   Indicates whether the batch process was successful.
    *
    * @see https://api.drupal.org/api/drupal/core!includes!form.inc/group/batch/8
    */
-  public static function finishGeneration($success, $results, $operations) {
+  public static function finishGeneration(bool $success, array $results, array $operations): bool {
+    /** @var \Drupal\simple_sitemap\Logger $logger */
+    $logger = \Drupal::service('simple_sitemap.logger');
     if ($success) {
-      \Drupal::service('simple_sitemap.logger')
+      $logger
         ->m('The XML sitemaps have been regenerated.')
         ->log('info');
     }
     else {
-      \Drupal::service('simple_sitemap.logger')
+      $logger
         ->m(self::$batchErrorMessage)
         ->display('error', 'administer sitemap settings')
         ->log('error');
@@ -97,5 +127,5 @@ public static function finishGeneration($success, $results, $operations) {
 
     return $success;
   }
-}
 
+}
diff --git a/web/modules/simple_sitemap/src/Queue/QueueWorker.php b/web/modules/simple_sitemap/src/Queue/QueueWorker.php
index 736fefd2073ffe9fe987ec77c2d75e8fbaf5f58b..4011e92845e7b6915bd46f70ce86f2408c1ffd25 100644
--- a/web/modules/simple_sitemap/src/Queue/QueueWorker.php
+++ b/web/modules/simple_sitemap/src/Queue/QueueWorker.php
@@ -3,169 +3,198 @@
 namespace Drupal\simple_sitemap\Queue;
 
 use Drupal\Component\Utility\Timer;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Lock\LockBackendInterface;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
-use Drupal\simple_sitemap\SimplesitemapSettings;
-use Drupal\simple_sitemap\SimplesitemapManager;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Settings;
 use Drupal\Core\State\StateInterface;
 use Drupal\simple_sitemap\Logger;
 
+/**
+ * The simple_sitemap.queue_worker service.
+ */
 class QueueWorker {
 
   use BatchTrait;
 
-  const REBUILD_QUEUE_CHUNK_ITEM_SIZE = 5000;
-  const LOCK_ID = 'simple_sitemap:generation';
+  protected const REBUILD_QUEUE_CHUNK_ITEM_SIZE = 5000;
+  public const LOCK_ID = 'simple_sitemap:generation';
+  public const GENERATE_LOCK_TIMEOUT = 3600;
 
-  const GENERATE_TYPE_FORM = 'form';
-  const GENERATE_TYPE_DRUSH = 'drush';
-  const GENERATE_TYPE_CRON = 'cron';
-  const GENERATE_TYPE_BACKEND = 'backend';
-  const GENERATE_LOCK_TIMEOUT = 3600;
+  public const GENERATE_TYPE_FORM = 'form';
+  public const GENERATE_TYPE_DRUSH = 'drush';
+  public const GENERATE_TYPE_CRON = 'cron';
+  public const GENERATE_TYPE_BACKEND = 'backend';
 
   /**
-   * @var \Drupal\simple_sitemap\SimplesitemapSettings
+   * The simple_sitemap.settings service.
+   *
+   * @var \Drupal\simple_sitemap\Settings
    */
   protected $settings;
 
   /**
-   * @var \Drupal\simple_sitemap\SimplesitemapManager
-   */
-  protected $manager;
-
-  /**
+   * The state key/value store.
+   *
    * @var \Drupal\Core\State\StateInterface
    */
   protected $state;
 
   /**
-   * @var \Drupal\simple_sitemap\Queue\SimplesitemapQueue
+   * Simple XML Sitemap queue handler.
+   *
+   * @var \Drupal\simple_sitemap\Queue\SimpleSitemapQueue
    */
   protected $queue;
 
   /**
+   * Simple XML Sitemap logger.
+   *
    * @var \Drupal\simple_sitemap\Logger
    */
   protected $logger;
 
   /**
+   * The module handler service.
+   *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
    */
   protected $moduleHandler;
 
   /**
-   * @var \Drupal\Core\Lock\LockBackendInterface
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $lock;
+  protected $entityTypeManager;
 
   /**
-   * @var string|null
+   * The lock backend that should be used.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
    */
-  protected $variantProcessedNow;
+  protected $lock;
 
   /**
-   * @var string|null
+   * The sitemap entity.
+   *
+   * @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface
    */
-  protected $generatorProcessedNow;
+  protected $sitemapProcessedNow;
 
   /**
+   * The local cache of results.
+   *
    * @var array
    */
   protected $results = [];
 
   /**
+   * The local cache of processed results.
+   *
    * @var array
    */
   protected $processedResults = [];
 
   /**
+   * The local cache of processed paths.
+   *
    * @var array
    */
   protected $processedPaths = [];
 
   /**
+   * Sitemap generator settings.
+   *
    * @var array
    */
   protected $generatorSettings;
 
   /**
+   * Maximum links in a sitemap.
+   *
    * @var int|null
    */
   protected $maxLinks;
 
   /**
+   * The number of remaining elements.
+   *
    * @var int|null
    */
   protected $elementsRemaining;
 
   /**
+   * The initial number of queue items.
+   *
    * @var int|null
    */
   protected $elementsTotal;
 
   /**
    * QueueWorker constructor.
-   * @param \Drupal\simple_sitemap\SimplesitemapSettings $settings
-   * @param \Drupal\simple_sitemap\SimplesitemapManager $manager
+   *
+   * @param \Drupal\simple_sitemap\Settings $settings
+   *   The simple_sitemap.settings service.
    * @param \Drupal\Core\State\StateInterface $state
-   * @param \Drupal\simple_sitemap\Queue\SimplesitemapQueue $element_queue
+   *   The state key/value store.
+   * @param \Drupal\simple_sitemap\Queue\SimpleSitemapQueue $element_queue
+   *   Simple XML Sitemap queue handler.
    * @param \Drupal\simple_sitemap\Logger $logger
+   *   Simple XML Sitemap logger.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend that should be used.
    */
-  public function __construct(SimplesitemapSettings $settings,
-                              SimplesitemapManager $manager,
+  public function __construct(Settings $settings,
                               StateInterface $state,
-                              SimplesitemapQueue $element_queue,
+                              SimpleSitemapQueue $element_queue,
                               Logger $logger,
                               ModuleHandlerInterface $module_handler,
+                              EntityTypeManagerInterface $entity_type_manager,
                               LockBackendInterface $lock) {
     $this->settings = $settings;
-    $this->manager = $manager;
     $this->state = $state;
     $this->queue = $element_queue;
     $this->logger = $logger;
     $this->moduleHandler = $module_handler;
+    $this->entityTypeManager = $entity_type_manager;
     $this->lock = $lock;
   }
 
   /**
+   * Queues links from variants.
+   *
    * @param string[]|string|null $variants
+   *   The sitemap variants.
+   *
    * @return $this
+   *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
-  public function queue($variants = NULL) {
-    $type_definitions = $this->manager->getSitemapTypes();
-    $all_data_sets = [];
-
-    // Gather variant data of variants chosen for this rebuild.
-    $queue_variants = NULL === $variants
-      ? $this->manager->getSitemapVariants()
-      : array_filter(
-        $this->manager->getSitemapVariants(),
-        static function($name) use ($variants) { return in_array($name, (array) $variants); },
-        ARRAY_FILTER_USE_KEY
-      );
-
-    foreach ($queue_variants as $variant_name => $variant_definition) {
-      $type = $variant_definition['type'];
+  public function queue($variants = NULL): QueueWorker {
+    $variants = $variants !== NULL ? (array) $variants : NULL;
 
-      // Adding generate_sitemap operations for all data sets.
-      foreach ($type_definitions[$type]['urlGenerators'] as $url_generator_id) {
+    /** @var \Drupal\simple_sitemap\Entity\SimpleSitemap[] $sitemaps */
+    $sitemaps = $this->entityTypeManager->getStorage('simple_sitemap')->loadMultiple($variants);
 
-        $data_sets = $this->manager->getUrlGenerator($url_generator_id)
-          ->setSitemapVariant($variant_name)
-          ->getDataSets();
+    $empty_variants = array_fill_keys(array_keys($sitemaps), TRUE);
+    $all_data_sets = [];
 
-        if (!empty($data_sets)) {
-          $queue_variants[$variant_name]['data'] = TRUE;
-          foreach ($data_sets as $data_set) {
+    foreach ($sitemaps as $variant => $sitemap) {
+      if ($sitemap->isEnabled()) {
+        foreach ($sitemap->getType()->getUrlGenerators() as $url_generator_id => $url_generator) {
+          // @todo Automatically set sitemap.
+          foreach ($url_generator->setSitemap($sitemap)->getDataSets() as $data_set) {
+            unset($empty_variants[$variant]);
             $all_data_sets[] = [
               'data' => $data_set,
-              'sitemap_variant' => $variant_name,
+              'sitemap' => $variant,
               'url_generator' => $url_generator_id,
-              'sitemap_generator' => $type_definitions[$type]['sitemapGenerator'],
             ];
 
             if (count($all_data_sets) === self::REBUILD_QUEUE_CHUNK_ITEM_SIZE) {
@@ -182,18 +211,26 @@ static function($name) use ($variants) { return in_array($name, (array) $variant
     }
     $this->getQueuedElementCount(TRUE);
 
-    // Remove all sitemap instances of variants which did not yield any queue elements.
-    $this->manager->removeSitemap(array_keys(array_filter($queue_variants, static function($e) { return empty($e['data']); })));
+    // Remove all sitemap content of variants which did not yield any queue
+    // elements.
+    foreach ($empty_variants as $variant => $is_empty) {
+      $sitemaps[$variant]->deleteContent();
+    }
 
     return $this;
   }
 
   /**
+   * Deletes the queue and queues links from variants.
+   *
    * @param string[]|string|null $variants
+   *   The sitemap variants.
+   *
    * @return $this
+   *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
-  public function rebuildQueue($variants = NULL) {
+  public function rebuildQueue($variants = NULL): QueueWorker {
     if (!$this->lock->acquire(static::LOCK_ID)) {
       throw new \RuntimeException('Unable to acquire a lock for sitemap queue rebuilding');
     }
@@ -204,28 +241,43 @@ public function rebuildQueue($variants = NULL) {
     return $this;
   }
 
-  protected function queueElements($elements) {
+  /**
+   * Stores items to the queue.
+   *
+   * @param mixed $elements
+   *   Datasets to process.
+   *
+   * @throws \Exception
+   */
+  protected function queueElements($elements): void {
     $this->queue->createItems($elements);
     $this->state->set('simple_sitemap.queue_items_initial_amount', ($this->state->get('simple_sitemap.queue_items_initial_amount') + count($elements)));
   }
 
   /**
+   * Generates all sitemaps.
+   *
    * @param string $from
+   *   The source of generation.
+   *
    * @return $this
+   *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *
+   * @todo Use exception handling when skipping queue items.
    */
-  public function generateSitemap($from = self::GENERATE_TYPE_FORM) {
+  public function generate(string $from = self::GENERATE_TYPE_FORM): QueueWorker {
 
     $this->generatorSettings = [
-      'base_url' => $this->settings->getSetting('base_url', ''),
-      'xsl' => $this->settings->getSetting('xsl', TRUE),
-      'default_variant' => $this->settings->getSetting('default_variant', NULL),
-      'skip_untranslated' => $this->settings->getSetting('skip_untranslated', FALSE),
-      'remove_duplicates' => $this->settings->getSetting('remove_duplicates', TRUE),
-      'excluded_languages' => $this->settings->getSetting('excluded_languages', []),
+      'base_url' => $this->settings->get('base_url', ''),
+      'xsl' => $this->settings->get('xsl', TRUE),
+      'default_variant' => $this->settings->get('default_variant'),
+      'skip_untranslated' => $this->settings->get('skip_untranslated', FALSE),
+      'remove_duplicates' => $this->settings->get('remove_duplicates', TRUE),
+      'excluded_languages' => $this->settings->get('excluded_languages', []),
     ];
-    $this->maxLinks = $this->settings->getSetting('max_links');
-    $max_execution_time = $this->settings->getSetting('generate_duration', 10000);
+    $this->maxLinks = $this->settings->get('max_links', 2000);
+    $max_execution_time = $this->settings->get('generate_duration', 10000);
     Timer::start('simple_sitemap_generator');
 
     $this->unstashResults();
@@ -248,35 +300,45 @@ public function generateSitemap($from = self::GENERATE_TYPE_FORM) {
       }
 
       try {
-        if ($element->data['sitemap_variant'] !== $this->variantProcessedNow) {
+        if ($this->sitemapProcessedNow && !$this->sitemapProcessedNow->isEnabled()) {
+          $this->queue->deleteItem($element);
+          $this->elementsRemaining--;
+          continue;
+        }
 
-          if (NULL !== $this->variantProcessedNow) {
-            $this->generateVariantChunksFromResults(TRUE);
-            $this->publishCurrentVariant();
+        if ($this->sitemapProcessedNow === NULL || $element->data['sitemap'] !== $this->sitemapProcessedNow->id()) {
+
+          if (NULL !== $this->sitemapProcessedNow) {
+            $this->generateSitemapChunksFromResults(TRUE);
+            $this->publishCurrentSitemap();
           }
 
-          $this->variantProcessedNow = $element->data['sitemap_variant'];
-          $this->generatorProcessedNow = $element->data['sitemap_generator'];
           $this->processedPaths = [];
+          if (($this->sitemapProcessedNow = $this->entityTypeManager->getStorage('simple_sitemap')->load($element->data['sitemap'])) === NULL) {
+            $this->queue->deleteItem($element);
+            $this->elementsRemaining--;
+            continue;
+          }
         }
 
         $this->generateResultsFromElement($element);
 
         if (!empty($this->maxLinks) && count($this->results) >= $this->maxLinks) {
-          $this->generateVariantChunksFromResults();
+          $this->generateSitemapChunksFromResults();
         }
       }
       catch (\Exception $e) {
         watchdog_exception('simple_sitemap', $e);
       }
 
-      $this->queue->deleteItem($element); //todo May want to use deleteItems() instead.
+      // @todo May want to use deleteItems() instead.
+      $this->queue->deleteItem($element);
       $this->elementsRemaining--;
     }
 
     if ($this->getQueuedElementCount() === 0) {
-      $this->generateVariantChunksFromResults(TRUE);
-      $this->publishCurrentVariant();
+      $this->generateSitemapChunksFromResults(TRUE);
+      $this->publishCurrentSitemap();
     }
     else {
       $this->stashResults();
@@ -287,13 +349,14 @@ public function generateSitemap($from = self::GENERATE_TYPE_FORM) {
   }
 
   /**
-   * @param $element
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   * Generates results from the given element.
+   *
+   * @param mixed $element
+   *   Element to process.
    */
-  protected function generateResultsFromElement($element) {
-    $results = $this->manager->getUrlGenerator($element->data['url_generator'])
-      ->setSitemapVariant($this->variantProcessedNow)
-      ->setSettings($this->generatorSettings)
+  protected function generateResultsFromElement($element): void {
+    $results = $this->sitemapProcessedNow->getType()->getUrlGenerators()[$element->data['url_generator']]
+      ->setSitemap($this->sitemapProcessedNow)
       ->generate($element->data['data']);
 
     $this->removeDuplicates($results);
@@ -301,30 +364,38 @@ protected function generateResultsFromElement($element) {
   }
 
   /**
+   * Removes duplicates from results.
+   *
    * @param array $results
+   *   Results to process.
    */
-  protected function removeDuplicates(&$results) {
+  protected function removeDuplicates(array &$results): void {
     if ($this->generatorSettings['remove_duplicates'] && !empty($results)) {
-      $result = $results[key($results)];
-      if (isset($result['meta']['path'])) {
-        if (isset($this->processedPaths[$result['meta']['path']])) {
-          $results = [];
-        }
-        else {
-          $this->processedPaths[$result['meta']['path']] = TRUE;
+      foreach ($results as $key => $result) {
+        if (isset($result['url'])) {
+          $url = (string) $result['url'];
+
+          if (isset($this->processedPaths[$url])) {
+            unset($results[$key]);
+          }
+          else {
+            $this->processedPaths[$url] = TRUE;
+          }
         }
       }
     }
   }
 
   /**
+   * Generates sitemap chunks from results.
+   *
    * @param bool $complete
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   The complete flag.
    */
-  protected function generateVariantChunksFromResults($complete = FALSE) {
+  protected function generateSitemapChunksFromResults(bool $complete = FALSE): void {
     if (!empty($this->results)) {
       $processed_results = $this->results;
-      $this->moduleHandler->alter('simple_sitemap_links', $processed_results, $this->variantProcessedNow);
+      $this->moduleHandler->alter('simple_sitemap_links', $processed_results, $this->sitemapProcessedNow);
       $this->processedResults = array_merge($this->processedResults, $processed_results);
       $this->results = [];
     }
@@ -333,50 +404,49 @@ protected function generateVariantChunksFromResults($complete = FALSE) {
       return;
     }
 
-    $generator = $this->manager->getSitemapGenerator($this->generatorProcessedNow)
-      ->setSitemapVariant($this->variantProcessedNow)
-      ->setSettings($this->generatorSettings);
-
     if (!empty($this->maxLinks)) {
       foreach (array_chunk($this->processedResults, $this->maxLinks, TRUE) as $chunk_links) {
         if ($complete || count($chunk_links) === $this->maxLinks) {
-          $generator->generate($chunk_links);
+          $this->sitemapProcessedNow->addChunk($chunk_links);
           $this->processedResults = array_diff_key($this->processedResults, $chunk_links);
         }
       }
     }
     else {
-      $generator->generate($this->processedResults);
+      $this->sitemapProcessedNow->addChunk($this->processedResults);
       $this->processedResults = [];
     }
   }
 
-  protected function publishCurrentVariant() {
-    if ($this->variantProcessedNow !== NULL) {
-      $this->manager->getSitemapGenerator($this->generatorProcessedNow)
-        ->setSitemapVariant($this->variantProcessedNow)
-        ->setSettings($this->generatorSettings)
-        ->generateIndex()
-        ->publish();
+  /**
+   * Publishes the current sitemap.
+   */
+  protected function publishCurrentSitemap(): void {
+    if ($this->sitemapProcessedNow !== NULL) {
+      $this->sitemapProcessedNow->generateIndex()->publish();
     }
   }
 
+  /**
+   * Resets the local cache.
+   */
   protected function resetWorker() {
     $this->results = [];
     $this->processedPaths = [];
     $this->processedResults = [];
-    $this->variantProcessedNow = NULL;
-    $this->generatorProcessedNow = NULL;
+    $this->sitemapProcessedNow = NULL;
     $this->elementsTotal = NULL;
     $this->elementsRemaining = NULL;
   }
 
   /**
+   * Deletes a queue and every item in the queue.
+   *
    * @return $this
    */
-  public function deleteQueue() {
+  public function deleteQueue(): QueueWorker {
     $this->queue->deleteQueue();
-    SitemapGeneratorBase::purgeSitemapVariants(NULL, 'unpublished');
+    SimpleSitemap::purgeContent(NULL, SimpleSitemap::FETCH_BY_STATUS_UNPUBLISHED);
     $this->state->set('simple_sitemap.queue_items_initial_amount', 0);
     $this->state->delete('simple_sitemap.queue_stashed_results');
     $this->resetWorker();
@@ -384,10 +454,12 @@ public function deleteQueue() {
     return $this;
   }
 
-  protected function stashResults() {
+  /**
+   * Stashes the current results.
+   */
+  protected function stashResults(): void {
     $this->state->set('simple_sitemap.queue_stashed_results', [
-      'variant' => $this->variantProcessedNow,
-      'generator' => $this->generatorProcessedNow,
+      'variant' => $this->sitemapProcessedNow ? $this->sitemapProcessedNow->id() : NULL,
       'results' => $this->results,
       'processed_results' => $this->processedResults,
       'processed_paths' => $this->processedPaths,
@@ -395,18 +467,31 @@ protected function stashResults() {
     $this->resetWorker();
   }
 
-  protected function unstashResults() {
+  /**
+   * Unstashes results.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  protected function unstashResults(): void {
     if (NULL !== $results = $this->state->get('simple_sitemap.queue_stashed_results')) {
       $this->state->delete('simple_sitemap.queue_stashed_results');
       $this->results = !empty($results['results']) ? $results['results'] : [];
       $this->processedResults = !empty($results['processed_results']) ? $results['processed_results'] : [];
       $this->processedPaths = !empty($results['processed_paths']) ? $results['processed_paths'] : [];
-      $this->variantProcessedNow = $results['variant'];
-      $this->generatorProcessedNow = $results['generator'];
+      $this->sitemapProcessedNow = $results['variant']
+        ? $this->entityTypeManager->getStorage('simple_sitemap')->load($results['variant'])
+        : NULL;
     }
   }
 
-  public function getInitialElementCount() {
+  /**
+   * Gets the initial number of queue items.
+   *
+   * @return int
+   *   The initial number of queue items.
+   */
+  public function getInitialElementCount(): ?int {
     if (NULL === $this->elementsTotal) {
       $this->elementsTotal = (int) $this->state->get('simple_sitemap.queue_items_initial_amount', 0);
     }
@@ -415,10 +500,15 @@ public function getInitialElementCount() {
   }
 
   /**
+   * Retrieves the number of items in the queue.
+   *
    * @param bool $force_recount
+   *   TRUE to force the recount.
+   *
    * @return int
+   *   An integer estimate of the number of items in the queue.
    */
-  public function getQueuedElementCount($force_recount = FALSE) {
+  public function getQueuedElementCount(bool $force_recount = FALSE): ?int {
     if ($force_recount || NULL === $this->elementsRemaining) {
       $this->elementsRemaining = $this->queue->numberOfItems();
     }
@@ -427,18 +517,24 @@ public function getQueuedElementCount($force_recount = FALSE) {
   }
 
   /**
+   * Gets the number of stashed results.
+   *
    * @return int
+   *   The number of stashed results.
    */
-  public function getStashedResultCount() {
+  public function getStashedResultCount(): int {
     $results = $this->state->get('simple_sitemap.queue_stashed_results', []);
     return (!empty($results['results']) ? count($results['results']) : 0)
       + (!empty($results['processed_results']) ? count($results['processed_results']) : 0);
   }
 
   /**
+   * Gets the number of processed elements.
+   *
    * @return int
+   *   the number of processed elements.
    */
-  public function getProcessedElementCount() {
+  public function getProcessedElementCount(): ?int {
     $initial = $this->getInitialElementCount();
     $remaining = $this->getQueuedElementCount();
 
@@ -446,10 +542,13 @@ public function getProcessedElementCount() {
   }
 
   /**
+   * Determines whether the generation is in progress.
+   *
    * @return bool
+   *   TRUE if generation is in progress and FALSE otherwise.
    */
-  public function generationInProgress() {
+  public function generationInProgress(): bool {
     return 0 < ($this->getQueuedElementCount() + $this->getStashedResultCount());
   }
-}
 
+}
diff --git a/web/modules/simple_sitemap/src/Queue/SimplesitemapQueue.php b/web/modules/simple_sitemap/src/Queue/SimpleSitemapQueue.php
similarity index 68%
rename from web/modules/simple_sitemap/src/Queue/SimplesitemapQueue.php
rename to web/modules/simple_sitemap/src/Queue/SimpleSitemapQueue.php
index 1d741da93add571b5a4907ff7197eb90a50a6190..0d2fd1e3c974c9520fd64b2a02e98ee112982ba6 100644
--- a/web/modules/simple_sitemap/src/Queue/SimplesitemapQueue.php
+++ b/web/modules/simple_sitemap/src/Queue/SimpleSitemapQueue.php
@@ -4,32 +4,37 @@
 
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Queue\DatabaseQueue;
-use Drupal\Component\Datetime\Time;
+use Drupal\Component\Datetime\TimeInterface;
 
 /**
- * Class SimplesitemapQueue
- * @package Drupal\simple_sitemap\Queue
+ * Defines a Simple XML Sitemap queue handler.
  */
-class SimplesitemapQueue extends DatabaseQueue {
+class SimpleSitemapQueue extends DatabaseQueue {
 
   /**
-   * @var \Drupal\Component\Datetime\Time
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
    */
   protected $time;
 
   /**
-   * SimplesitemapQueue constructor.
-   * @param $name
+   * SimpleSitemapQueue constructor.
+   *
+   * @param string $name
+   *   The name of the queue.
    * @param \Drupal\Core\Database\Connection $connection
-   * @param \Drupal\Component\Datetime\Time $time
+   *   The Connection object containing the key-value tables.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
    */
-  public function __construct($name, Connection $connection, Time $time) {
+  public function __construct($name, Connection $connection, TimeInterface $time) {
     parent::__construct($name, $connection);
     $this->time = $time;
   }
 
   /**
-   * Overrides \Drupal\Core\Queue\DatabaseQueue::claimItem().
+   * {@inheritdoc}
    *
    * Unlike \Drupal\Core\Queue\DatabaseQueue::claimItem(), this method provides
    * a default lease time of 0 (no expiration) instead of 30. This allows the
@@ -59,9 +64,11 @@ public function claimItem($lease_time = 0) {
    *   - item_id: the unique ID returned from createItem().
    *   - created: timestamp when the item was put into the queue.
    *
+   * @throws \Exception
+   *
    * @see \Drupal\Core\Queue\QueueInterface::claimItem
    */
-  public function yieldItem() {
+  public function yieldItem(): \Generator {
     try {
       $query = $this->connection->query('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', [':name' => $this->name]);
       while ($item = $query->fetchObject()) {
@@ -74,6 +81,16 @@ public function yieldItem() {
     }
   }
 
+  /**
+   * Adds a queue items and store it directly to the queue.
+   *
+   * @param mixed $data_sets
+   *   Datasets to process.
+   *
+   * @return int|bool
+   *   A unique ID if the item was successfully created and was (best effort)
+   *   added to the queue, otherwise FALSE.
+   */
   public function createItems($data_sets) {
     $try_again = FALSE;
     try {
@@ -95,11 +112,21 @@ public function createItems($data_sets) {
     return $id;
   }
 
+  /**
+   * Adds a queue items and store it directly to the queue.
+   *
+   * @param mixed $data_sets
+   *   Datasets to process.
+   *
+   * @return int|bool
+   *   A unique ID if the item was successfully created and was (best effort)
+   *   added to the queue, otherwise FALSE.
+   */
   protected function doCreateItems($data_sets) {
     $query = $this->connection->insert(static::TABLE_NAME)
       ->fields(['name', 'data', 'created']);
 
-    foreach ($data_sets as $i => $data) {
+    foreach ($data_sets as $data) {
       $query->values([
         $this->name,
         serialize($data),
@@ -110,7 +137,13 @@ protected function doCreateItems($data_sets) {
     return $query->execute();
   }
 
-  public function deleteItems($item_ids) {
+  /**
+   * Deletes a finished items from the queue.
+   *
+   * @param mixed $item_ids
+   *   Item IDs to delete.
+   */
+  public function deleteItems($item_ids): void {
     try {
       $this->connection->delete(static::TABLE_NAME)
         ->condition('item_id', $item_ids, 'IN')
diff --git a/web/modules/simple_sitemap/src/Settings.php b/web/modules/simple_sitemap/src/Settings.php
new file mode 100644
index 0000000000000000000000000000000000000000..bcf65ec98f6dbd5a011694a2c067dc1f3e1ea840
--- /dev/null
+++ b/web/modules/simple_sitemap/src/Settings.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\simple_sitemap;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+
+/**
+ * The simple_sitemap.settings service.
+ */
+class Settings {
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * Settings constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory) {
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * Returns a specific setting or a default value if setting does not exist.
+   *
+   * @param string $name
+   *   Name of the setting, like 'max_links'.
+   * @param mixed $default
+   *   Value to be returned if the setting does not exist in the configuration.
+   *
+   * @return mixed
+   *   The current setting from configuration or a default value.
+   */
+  public function get(string $name, $default = NULL) {
+    $setting = $this->configFactory
+      ->get('simple_sitemap.settings')
+      ->get($name);
+
+    return $setting ?? $default;
+  }
+
+  /**
+   * Returns a specific setting (without config overrides) or a default value
+   * if setting does not exist.
+   *
+   * @param string $name
+   *   Name of the setting, like 'max_links'.
+   * @param mixed $default
+   *   Value to be returned if the setting does not exist in the configuration.
+   *
+   * @return mixed
+   *   The current setting from configuration or a default value.
+   *
+   * @see https://www.drupal.org/project/simple_sitemap/issues/3359679.
+   */
+  public function getEditable(string $name, $default = NULL) {
+    $setting = $this->configFactory
+      ->getEditable('simple_sitemap.settings')
+      ->get($name);
+
+    return $setting ?? $default;
+  }
+
+  /**
+   * Returns all settings.
+   *
+   * @return mixed
+   *   Sitemap settings.
+   */
+  public function getAll() {
+    return $this->configFactory
+      ->get('simple_sitemap.settings')
+      ->get();
+  }
+
+  /**
+   * Stores a specific sitemap setting in configuration.
+   *
+   * @param string $name
+   *   Setting name, like 'max_links'.
+   * @param mixed $setting
+   *   The setting to be saved.
+   *
+   * @return $this
+   */
+  public function save(string $name, $setting): Settings {
+    $this->configFactory->getEditable('simple_sitemap.settings')
+      ->set($name, $setting)->save();
+
+    return $this;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/SimpleSitemapListBuilder.php b/web/modules/simple_sitemap/src/SimpleSitemapListBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..fe4cfc9d42a1aff6692b88b550c561e079f1cf9a
--- /dev/null
+++ b/web/modules/simple_sitemap/src/SimpleSitemapListBuilder.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\simple_sitemap;
+
+use Drupal\Core\Config\Entity\DraggableListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
+use Drupal\simple_sitemap\Form\StatusForm;
+
+/**
+ * Defines a class to build a listing of sitemap entities.
+ */
+class SimpleSitemapListBuilder extends DraggableListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'simple_sitemap_overview_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['name'] = $this->t('Sitemap');
+    $header['type'] = $this->t('Type');
+    $header['status'] = $this->t('Status');
+    $header['count'] = $this->t('Link count');
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['name']['#markup'] = '<span title="' . $entity->get('description') . '">' . $entity->label() . '</span>';
+    $row['type']['#markup'] = '<span title="' . $entity->getType()->get('description') . '">' . $entity->getType()->label() . '</span>';
+    $row['status']['#markup'] = $this->t('disabled');
+    $row['count']['#markup'] = '';
+
+    if ($entity->isEnabled()) {
+      $row['status']['#markup'] = $this->t('pending');
+
+      /** @var \Drupal\simple_sitemap\Entity\SimpleSitemapInterface $entity */
+      if ($entity->fromPublishedAndUnpublished()->getChunkCount()) {
+        switch ($entity->contentStatus()) {
+
+          case SimpleSitemap::SITEMAP_UNPUBLISHED:
+            $row['status']['#markup'] = $this->t('generating');
+            break;
+
+          case SimpleSitemap::SITEMAP_PUBLISHED:
+          case SimpleSitemap::SITEMAP_PUBLISHED_GENERATING:
+            $row['name']['#markup'] = '<a title ="' . $entity->get('description')
+              . '" href="' . $entity->toUrl()->toString() . '" target="_blank">'
+              . $entity->label() . '</a>';
+
+            $created = \Drupal::service('date.formatter')->format($entity->fromPublished()->getCreated());
+            $row['status']['#markup'] = $entity->contentStatus() === SimpleSitemap::SITEMAP_PUBLISHED
+              ? $this->t('published on @time', ['@time' => $created])
+              : $this->t('published on @time, regenerating', ['@time' => $created]);
+
+            $row['count']['#markup'] = $entity->fromPublished()->getLinkCount();
+
+            break;
+        }
+      }
+    }
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOperations(EntityInterface $entity): array {
+    return [
+      ['title' => $this->t('Edit'), 'url' => $entity->toUrl('edit-form')],
+      ['title' => $this->t('Delete'), 'url' => $entity->toUrl('delete-form')],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildForm($form, $form_state);
+    $form = \Drupal::formBuilder()->getForm(StatusForm::class) + $form;
+    $form['entities']['#empty'] = $this->t('No sitemaps have been defined yet. <a href="@url">Add a new one</a>.', [
+      '@url' => Url::fromRoute('simple_sitemap.add')->toString(),
+    ]);
+
+    return $form;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/SimpleSitemapTypeListBuilder.php b/web/modules/simple_sitemap/src/SimpleSitemapTypeListBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..66c1b927374bb21f68ace86b5509411421f318af
--- /dev/null
+++ b/web/modules/simple_sitemap/src/SimpleSitemapTypeListBuilder.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\simple_sitemap;
+
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Url;
+
+/**
+ * Defines a class to build a listing of sitemap type entities.
+ */
+class SimpleSitemapTypeListBuilder extends ConfigEntityListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['label'] = $this->t('Sitemap type');
+    $header['description'] = $this->t('Description');
+    $header['sitemap_generator'] = $this->t('Sitemap generator');
+    $header['url_generators'] = $this->t('URL generators');
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['label'] = $entity->label();
+    $row['description'] = (string) $entity->get('description');
+    $row['sitemap_generator'] = $entity->getSitemapGenerator()->label();
+    $row['url_generators']['data']['#markup'] = '';
+    foreach ($entity->getUrlGenerators() as $generator) {
+      $row['url_generators']['data']['#markup'] .= '<div>' . $generator->label() . '</div>';
+    }
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOperations(EntityInterface $entity): array {
+    return [
+      ['title' => $this->t('Edit'), 'url' => $entity->toUrl('edit-form')],
+      ['title' => $this->t('Delete'), 'url' => $entity->toUrl('delete-form')],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    $build = parent::render();
+    $build['table']['#empty'] = $this->t('No sitemap types have been defined yet. <a href="@url">Add a new one</a>.', [
+      '@url' => Url::fromRoute('simple_sitemap_type.add')->toString(),
+    ]);
+
+    return $build;
+  }
+
+}
diff --git a/web/modules/simple_sitemap/src/Simplesitemap.php b/web/modules/simple_sitemap/src/Simplesitemap.php
deleted file mode 100644
index c86abcfda626353cbb0ec51e37f9d80ed29f4f66..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/Simplesitemap.php
+++ /dev/null
@@ -1,1000 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap;
-
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Lock\LockBackendInterface;
-use Drupal\simple_sitemap\Queue\QueueWorker;
-use Drupal\Core\Path\PathValidator;
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Datetime\DateFormatter;
-use Drupal\Component\Datetime\Time;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
-
-/**
- * Class Simplesitemap
- * @package Drupal\simple_sitemap
- */
-class Simplesitemap {
-
-  /**
-   * @var \Drupal\simple_sitemap\EntityHelper
-   */
-  protected $entityHelper;
-
-  /**
-   * @var \Drupal\simple_sitemap\SimplesitemapSettings
-   */
-  protected $settings;
-
-  /**
-   * @var \Drupal\simple_sitemap\SimplesitemapManager
-   */
-  protected $manager;
-
-  /**
-   * @var \Drupal\Core\Config\ConfigFactory
-   */
-  protected $configFactory;
-
-  /**
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $db;
-
-  /**
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * @var \Drupal\Core\Path\PathValidator
-   */
-  protected $pathValidator;
-
-  /**
-   * @var \Drupal\Core\Datetime\DateFormatter
-   */
-  protected $dateFormatter;
-
-  /**
-   * @var \Drupal\Component\Datetime\Time
-   */
-  protected $time;
-
-  /**
-   * @var \Drupal\simple_sitemap\Queue\QueueWorker
-   */
-  protected $queueWorker;
-
-  /**
-   * @var array
-   */
-  protected $variants;
-
-  /**
-   * @var \Drupal\Core\Lock\LockBackendInterface
-   */
-  protected $lock;
-
-  /**
-   * @var \Drupal\simple_sitemap\Logger
-   */
-  protected $logger;
-
-  /**
-   * @var array
-   */
-  protected static $allowedLinkSettings = [
-    'entity' => ['index', 'priority', 'changefreq', 'include_images'],
-    'custom' => ['priority', 'changefreq'],
-  ];
-
-  /**
-   * @var array
-   */
-  protected static $linkSettingDefaults = [
-    'index' => FALSE,
-    'priority' => '0.5',
-    'changefreq' => '',
-    'include_images' => FALSE,
-  ];
-
-  /**
-   * Simplesitemap constructor.
-   * @param \Drupal\simple_sitemap\EntityHelper $entity_helper
-   * @param \Drupal\simple_sitemap\SimplesitemapSettings $settings
-   * @param \Drupal\simple_sitemap\SimplesitemapManager $manager
-   * @param \Drupal\Core\Config\ConfigFactory $config_factory
-   * @param \Drupal\Core\Database\Connection $database
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   * @param \Drupal\Core\Path\PathValidator $path_validator
-   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
-   * @param \Drupal\Component\Datetime\Time $time
-   * @param \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker
-   * @param \Drupal\Core\Lock\LockBackendInterface $lock
-   * @param \Drupal\simple_sitemap\Logger $logger
-   */
-  public function __construct(
-    EntityHelper $entity_helper,
-    SimplesitemapSettings $settings,
-    SimplesitemapManager $manager,
-    ConfigFactory $config_factory,
-    Connection $database,
-    EntityTypeManagerInterface $entity_type_manager,
-    PathValidator $path_validator,
-    DateFormatter $date_formatter,
-    Time $time,
-    QueueWorker $queue_worker,
-    LockBackendInterface $lock = NULL,
-    Logger $logger = NULL
-  ) {
-    $this->entityHelper = $entity_helper;
-    $this->settings = $settings;
-    $this->manager = $manager;
-    $this->configFactory = $config_factory;
-    $this->db = $database;
-    $this->entityTypeManager = $entity_type_manager;
-    $this->pathValidator = $path_validator;
-    $this->dateFormatter = $date_formatter;
-    $this->time = $time;
-    $this->queueWorker = $queue_worker;
-    if ($lock === NULL) {
-      @trigger_error('Calling Simplesitemap::__construct() without the $lock argument is deprecated in simple_sitemap:3.9. The $lock argument will be required in simple_sitemap:3.10.', E_USER_DEPRECATED);
-      $lock = \Drupal::service('lock');
-    }
-    $this->lock = $lock;
-    if ($logger === NULL) {
-      @trigger_error('Calling Simplesitemap::__construct() without the $logger argument is deprecated in simple_sitemap:3.9. The $logger argument will be required in simple_sitemap:3.10.', E_USER_DEPRECATED);
-      $logger = \Drupal::service('simple_sitemap.logger');
-    }
-    $this->logger = $logger;
-  }
-
-  /**
-   * Returns a specific sitemap setting or a default value if setting does not
-   * exist.
-   *
-   * @param string $name
-   *  Name of the setting, like 'max_links'.
-   *
-   * @param mixed $default
-   *  Value to be returned if the setting does not exist in the configuration.
-   *
-   * @return mixed
-   *  The current setting from configuration or a default value.
-   */
-  public function getSetting($name, $default = FALSE) {
-    return $this->settings->getSetting($name, $default);
-  }
-
-  /**
-   * Stores a specific sitemap setting in configuration.
-   *
-   * @param string $name
-   *  Setting name, like 'max_links'.
-   *
-   * @param mixed $setting
-   *  The setting to be saved.
-   *
-   * @return $this
-   */
-  public function saveSetting($name, $setting) {
-    $this->settings->saveSetting($name, $setting);
-
-    return $this;
-  }
-
-  /**
-   * @return \Drupal\simple_sitemap\Queue\QueueWorker
-   */
-  public function getQueueWorker() {
-    return $this->queueWorker;
-  }
-
-  /**
-   * @return \Drupal\simple_sitemap\SimplesitemapManager
-   */
-  public function getSitemapManager() {
-    return $this->manager;
-  }
-
-  /**
-   * @param array|string|true|null $variants
-   *  array: Array of variants to be set.
-   *  string: A particular variant to be set.
-   *  null: Default variant will be set.
-   *  true: All existing variants will be set.
-   *
-   * @return $this
-   *
-   * @todo Check if variants exist and throw exception.
-   */
-  public function setVariants($variants = NULL) {
-    if (NULL === $variants) {
-      $this->variants = !empty($default_variant = $this->getSetting('default_variant', ''))
-        ? [$default_variant]
-        : [];
-    }
-    elseif ($variants === TRUE) {
-      $this->variants = array_keys(
-        $this->manager->getSitemapVariants(NULL, FALSE));
-    }
-    else {
-      $this->variants = (array) $variants;
-    }
-
-    return $this;
-  }
-
-  /**
-   * Gets the currently set variants, the default variant, or all variants.
-   *
-   * @param bool $default_get_all
-   *  If true and no variants are set, all variants are returned. If false and
-   *  no variants are set, only the default variant is returned.
-   *
-   * @return array
-   */
-  protected function getVariants($default_get_all = TRUE) {
-    if (NULL === $this->variants) {
-      $this->setVariants($default_get_all ? TRUE : NULL);
-    }
-
-    return $this->variants;
-  }
-
-  /**
-   * Returns a sitemap variant, its index, or its requested chunk.
-   *
-   * @param int|null $delta
-   *  Optional delta of the chunk.
-   *
-   * @return string|false
-   *  If no chunk delta is provided, either the sitemap variant is returned,
-   *  or its index in case of a chunked sitemap.
-   *  If a chunk delta is provided, the relevant chunk is returned.
-   *  Returns false if the sitemap variant is not retrievable from the database.
-   */
-  public function getSitemap($delta = NULL) {
-    $chunk_info = $this->fetchSitemapVariantInfo();
-
-    if (empty($delta) || !isset($chunk_info[$delta])) {
-
-      if (isset($chunk_info[SitemapGeneratorBase::INDEX_DELTA])) {
-        // Return sitemap index if one exists.
-        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
-          ->sitemap_string;
-      }
-
-      // Return sitemap chunk if there is only one chunk.
-      return isset($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA])
-        ? $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA]->id)
-          ->sitemap_string
-        : FALSE;
-    }
-
-    // Return specific sitemap chunk.
-    return $this->fetchSitemapChunk($chunk_info[$delta]->id)->sitemap_string;
-  }
-
-  /**
-   * Fetches info about all published sitemap variants and their chunks.
-   *
-   * @return array
-   *  An array containing all published sitemap chunk IDs, deltas and creation
-   *  timestamps keyed by the currently set variants, or in case of only one
-   *  variant set the above keyed by sitemap delta.
-   */
-  protected function fetchSitemapVariantInfo() {
-    if (!empty($this->getVariants())) {
-      $result = $this->db->select('simple_sitemap', 's')
-        ->fields('s', ['id', 'delta', 'sitemap_created', 'type'])
-        ->condition('s.status', 1)
-        ->condition('s.type', $this->getVariants(), 'IN')
-        ->execute();
-
-      return count($this->getVariants()) > 1
-        ? $result->fetchAllAssoc('type')
-        : $result->fetchAllAssoc('delta');
-    }
-
-    return [];
-  }
-
-  /**
-   * Fetches a single sitemap chunk by ID.
-   *
-   * @param int $id
-   *   The chunk ID.
-   *
-   * @return object
-   *   A sitemap chunk object.
-   */
-  protected function fetchSitemapChunk($id) {
-    return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
-      [':id' => $id])->fetchObject();
-  }
-
-  /**
-   * Removes sitemap instances for the currently set variants.
-   *
-   * @return $this
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function removeSitemap() {
-    $this->manager->removeSitemap($this->getVariants(FALSE));
-
-    return $this;
-  }
-
-  /**
-   * Generates all sitemaps.
-   *
-   * @param string $from
-   *  Can be 'form', 'drush', 'cron' and 'backend'.
-   *
-   * @return $this
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function generateSitemap($from = QueueWorker::GENERATE_TYPE_FORM) {
-    if (!$this->lock->lockMayBeAvailable(QueueWorker::LOCK_ID)) {
-      $this->logger->m('Unable to acquire a lock for sitemap generation.')->log('error')->display('error');
-      return $this;
-    }
-    switch ($from) {
-      case QueueWorker::GENERATE_TYPE_FORM:
-      case QueueWorker::GENERATE_TYPE_DRUSH;
-        $this->queueWorker->batchGenerateSitemap($from);
-        break;
-
-      case QueueWorker::GENERATE_TYPE_CRON:
-      case QueueWorker::GENERATE_TYPE_BACKEND:
-        $this->queueWorker->generateSitemap($from);
-        break;
-    }
-
-    return $this;
-  }
-
-  /**
-   * Queues links from currently set variants.
-   *
-   * @return $this
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function queue() {
-    $this->queueWorker->queue($this->getVariants());
-
-    return $this;
-  }
-
-  /**
-   * Deletes the queue and queues links from currently set variants.
-   *
-   * @return $this
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function rebuildQueue() {
-    if (!$this->lock->lockMayBeAvailable(QueueWorker::LOCK_ID)) {
-      $this->logger->m('Unable to acquire a lock for sitemap generation.')->log('error')->display('error');
-      return $this;
-    }
-    $this->queueWorker->rebuildQueue($this->getVariants());
-
-    return $this;
-  }
-
-  /**
-   * Returns a 'time ago' string of last timestamp generation.
-   *
-   * @param string|null $variant
-   *
-   * @return string|array|false
-   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
-   */
-/*  public function getGeneratedAgo() {
-    $chunks = $this->fetchSitemapVariantInfo();
-    return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
-      ? $this->dateFormatter
-        ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
-            ->sitemap_created)
-      : FALSE;
-  }*/
-
-  /**
-   * Enables sitemap support for an entity type. Enabled entity types show
-   * sitemap settings on their bundle setting forms. If an enabled entity type
-   * features bundles (e.g. 'node'), it needs to be set up with
-   * setBundleSettings() as well.
-   *
-   * @param string $entity_type_id
-   *  Entity type id like 'node'.
-   *
-   * @return $this
-   */
-  public function enableEntityType($entity_type_id) {
-    $enabled_entity_types = $this->getSetting('enabled_entity_types');
-    if (!in_array($entity_type_id, $enabled_entity_types)) {
-      $enabled_entity_types[] = $entity_type_id;
-      $this->saveSetting('enabled_entity_types', $enabled_entity_types);
-    }
-
-    return $this;
-  }
-
-  /**
-   * Disables sitemap support for an entity type. Disabling support for an
-   * entity type deletes its sitemap settings permanently and removes sitemap
-   * settings from entity forms.
-   *
-   * @param string $entity_type_id
-   *
-   * @return $this
-   */
-  public function disableEntityType($entity_type_id) {
-
-    // Updating settings.
-    $enabled_entity_types = $this->getSetting('enabled_entity_types');
-    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
-      unset ($enabled_entity_types[$key]);
-      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
-    }
-
-    // Deleting inclusion settings.
-    $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
-    foreach ($config_names as $config_name) {
-      $config_name_parts = explode('.', $config_name);
-      if ($config_name_parts[3] === $entity_type_id) {
-        $this->configFactory->getEditable($config_name)->delete();
-      }
-    }
-
-    // Deleting entity overrides.
-    $this->setVariants(TRUE)->removeEntityInstanceSettings($entity_type_id);
-
-    return $this;
-  }
-
-  /**
-   * Sets settings for bundle or non-bundle entity types. This is done for the
-   * currently set variant.
-   *
-   * Note that this method takes only the first set variant into account. See todo.
-   *
-   * @param $entity_type_id
-   * @param null $bundle_name
-   * @param array $settings
-   *
-   * @return $this
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   *
-   * @todo multiple variants
-   */
-  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return $this;
-    }
-
-    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
-
-    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
-      $settings = array_merge($old_settings, $settings);
-    }
-    self::supplementDefaultSettings('entity', $settings);
-
-    if ($settings != $old_settings) {
-
-      // Save new bundle settings to configuration.
-      $bundle_settings = $this->configFactory
-        ->getEditable("simple_sitemap.bundle_settings.$variants[0].$entity_type_id.$bundle_name");
-      foreach ($settings as $setting_key => $setting) {
-        $bundle_settings->set($setting_key, $setting);
-      }
-      $bundle_settings->save();
-
-      if (empty($entity_ids = $this->entityHelper->getEntityInstanceIds($entity_type_id, $bundle_name))) {
-        return $this;
-      }
-
-      // Delete all entity overrides in case bundle indexation is disabled.
-      if (empty($settings['index'])) {
-        $this->removeEntityInstanceSettings($entity_type_id, $entity_ids);
-
-        return $this;
-      }
-
-      // Delete entity overrides which are identical to new bundle settings.
-      // todo Enclose into some sensible method.
-      $query = $this->db->select('simple_sitemap_entity_overrides', 'o')
-        ->fields('o', ['id', 'inclusion_settings'])
-        ->condition('o.entity_type', $entity_type_id)
-        ->condition('o.type', $variants[0]);
-      if (!empty($entity_ids)) {
-        $query->condition('o.entity_id', $entity_ids, 'IN');
-      }
-
-      $delete_instances = [];
-      foreach ($query->execute()->fetchAll() as $result) {
-        $delete = TRUE;
-        $instance_settings = unserialize($result->inclusion_settings);
-        foreach ($instance_settings as $setting_key => $instance_setting) {
-          if ($instance_setting != $settings[$setting_key]) {
-            $delete = FALSE;
-            break;
-          }
-        }
-        if ($delete) {
-          $delete_instances[] = $result->id;
-        }
-      }
-      if (!empty($delete_instances)) {
-
-        // todo Use removeEntityInstanceSettings() instead.
-        $this->db->delete('simple_sitemap_entity_overrides')
-          ->condition('id', $delete_instances, 'IN')
-          ->execute();
-      }
-    }
-
-    return $this;
-  }
-
-  /**
-   * Gets settings for bundle or non-bundle entity types. This is done for the
-   * currently set variants.
-   *
-   * @param string|null $entity_type_id
-   *  Limit the result set to a specific entity type.
-   *
-   * @param string|null $bundle_name
-   *  Limit the result set to a specific bundle name.
-   *
-   * @param bool $supplement_defaults
-   *  Supplements the result set with default bundle settings.
-   *
-   * @param bool $multiple_variants
-   *  If true, returns an array of results keyed by variant name, otherwise it
-   *  returns the result set for the first variant only.
-   *
-   * @return array|false
-   *  Array of settings or array of settings keyed by variant name. False if
-   *  entity type does not exist.
-   */
-  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL, $supplement_defaults = TRUE, $multiple_variants = FALSE) {
-    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
-    $all_bundle_settings = [];
-
-    foreach ($variants = $this->getVariants(FALSE) as $variant) {
-      if (NULL !== $entity_type_id) {
-        $bundle_settings = $this->configFactory
-          ->get("simple_sitemap.bundle_settings.$variant.$entity_type_id.$bundle_name")
-          ->get();
-
-        if (empty($bundle_settings) && $supplement_defaults) {
-          self::supplementDefaultSettings('entity', $bundle_settings);
-        }
-      }
-      else {
-        $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$variant.");
-        $bundle_settings = [];
-        foreach ($config_names as $config_name) {
-          $config_name_parts = explode('.', $config_name);
-          $bundle_settings[$config_name_parts[3]][$config_name_parts[4]] = $this->configFactory->get($config_name)->get();
-        }
-
-        // Supplement default bundle settings for all bundles not found in simple_sitemap.bundle_settings.*.* configuration.
-        if ($supplement_defaults) {
-          foreach ($this->entityHelper->getSupportedEntityTypes() as $type_id => $type_definition) {
-            foreach($this->entityHelper->getBundleInfo($type_id) as $bundle => $bundle_definition) {
-              if (!isset($bundle_settings[$type_id][$bundle])) {
-                self::supplementDefaultSettings('entity', $bundle_settings[$type_id][$bundle]);
-              }
-            }
-          }
-        }
-      }
-
-      if ($multiple_variants) {
-        $all_bundle_settings[$variant] = $bundle_settings;
-      }
-      else {
-        return $bundle_settings;
-      }
-    }
-
-    return $all_bundle_settings;
-  }
-
-  /**
-   * Removes settings for bundle or a non-bundle entity types. This is done for
-   * the currently set variants.
-   *
-   * @param string|null $entity_type_id
-   *  Limit the removal to a specific entity type.
-   *
-   * @param string|null $bundle_name
-   *  Limit the removal to a specific bundle name.
-   *
-   * @return $this
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   */
-  public function removeBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return $this;
-    }
-
-    if (NULL !== $entity_type_id) {
-      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
-
-      foreach ($variants as $variant) {
-        $this->configFactory
-          ->getEditable("simple_sitemap.bundle_settings.$variant.$entity_type_id.$bundle_name")->delete();
-      }
-
-      if (!empty($entity_ids = $this->entityHelper->getEntityInstanceIds($entity_type_id, $bundle_name))) {
-        $this->removeEntityInstanceSettings($entity_type_id, $entity_ids);
-      }
-    }
-    else {
-      foreach ($variants as $variant) {
-        $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$variant.");
-        foreach ($config_names as $config_name) {
-          $this->configFactory->getEditable($config_name)->delete();
-        }
-      }
-      $this->removeEntityInstanceSettings();
-    }
-
-    return $this;
-  }
-
-  /**
-   * Supplements all missing link setting with default values.
-   *
-   * @param string $type
-   *  Can be 'entity' or 'custom'.
-   *
-   * @param array &$settings
-   * @param array $overrides
-   */
-  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
-    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
-      if (!isset($settings[$allowed_link_setting])
-        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
-        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
-          ? $overrides[$allowed_link_setting]
-          : self::$linkSettingDefaults[$allowed_link_setting];
-      }
-    }
-  }
-
-  /**
-   * Overrides sitemap settings for a single entity for the currently set
-   * variants.
-   *
-   * @param string $entity_type_id
-   * @param string $id
-   * @param array $settings
-   *
-   * @return $this
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   */
-  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return $this;
-    }
-
-    if (empty($entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id))) {
-      // todo exception
-      return $this;
-    }
-
-    $all_bundle_settings = $this->getBundleSettings(
-      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity), TRUE, TRUE
-    );
-
-    foreach ($all_bundle_settings as $variant => $bundle_settings) {
-      if (!empty($bundle_settings)) {
-
-        // Check if overrides are different from bundle setting before saving.
-        $override = FALSE;
-        foreach ($settings as $key => $setting) {
-          if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
-            $override = TRUE;
-            break;
-          }
-        }
-
-        // Save overrides for this entity if something is different.
-        if ($override) {
-          $this->db->merge('simple_sitemap_entity_overrides')
-            ->keys([
-              'type' => $variant,
-              'entity_type' => $entity_type_id,
-              'entity_id' => $id])
-            ->fields([
-              'type' => $variant,
-              'entity_type' => $entity_type_id,
-              'entity_id' => $id,
-              'inclusion_settings' => serialize(array_merge($bundle_settings, $settings))])
-            ->execute();
-        }
-        // Else unset override.
-        else {
-          $this->removeEntityInstanceSettings($entity_type_id, $id);
-        }
-      }
-    }
-
-    return $this;
-  }
-
-  /**
-   * Gets sitemap settings for an entity instance which overrides bundle
-   * settings, or gets bundle settings, if they are not overridden. This is
-   * done for the currently set variant.
-   * Please note, this method takes only the first set
-   * variant into account. See todo.
-   *
-   * @param string $entity_type_id
-   * @param string $id
-   *
-   * @return array|false
-   *  Array of entity instance settings or the settings of its bundle. False if
-   *  entity type or variant does not exist.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   *
-   * @todo multiple variants
-   * @todo: May want to use Simplesitemap::supplementDefaultSettings('entity', $settings) inside here instead of calling it everywhere this method is called.
-   */
-  public function getEntityInstanceSettings($entity_type_id, $id) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return FALSE;
-    }
-
-    $results = $this->db->select('simple_sitemap_entity_overrides', 'o')
-      ->fields('o', ['inclusion_settings'])
-      ->condition('o.type', $variants[0])
-      ->condition('o.entity_type', $entity_type_id)
-      ->condition('o.entity_id', $id)
-      ->execute()
-      ->fetchField();
-
-    if (!empty($results)) {
-      return unserialize($results);
-    }
-
-    if (empty($entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id))) {
-      return FALSE;
-    }
-
-    return $this->getBundleSettings(
-      $entity_type_id,
-      $this->entityHelper->getEntityInstanceBundleName($entity)
-    );
-  }
-
-  /**
-   * Removes sitemap settings for entities that override bundle settings. This
-   * is done for the currently set variants.
-   *
-   * @param string|null $entity_type_id
-   *  Limits the removal to a certain entity type.
-   *
-   * @param string|null $entity_ids
-   *  Limits the removal to entities with certain IDs.
-   *
-   * @return $this
-   */
-  public function removeEntityInstanceSettings($entity_type_id = NULL, $entity_ids = NULL) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return $this;
-    }
-
-    $query = $this->db->delete('simple_sitemap_entity_overrides')
-      ->condition('type', $variants, 'IN');
-
-    if (NULL !== $entity_type_id) {
-      $query->condition('entity_type', $entity_type_id);
-
-      if (NULL !== $entity_ids) {
-        $query->condition('entity_id', (array) $entity_ids, 'IN');
-      }
-    }
-
-    $query->execute();
-
-    return $this;
-  }
-
-  /**
-   * Checks if an entity bundle (or a non-bundle entity type) is set to be
-   * indexed for any of the currently set variants.
-   *
-   * @param string $entity_type_id
-   * @param string|null $bundle_name
-   *
-   * @return bool
-   */
-  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
-    foreach ($this->getBundleSettings($entity_type_id, $bundle_name, FALSE, TRUE) as $settings) {
-      if (!empty($settings['index'])) {
-        return TRUE;
-      }
-    }
-
-    return FALSE;
-  }
-
-  /**
-   * Checks if an entity type is enabled in the sitemap settings.
-   *
-   * @param string $entity_type_id
-   *
-   * @return bool
-   */
-  public function entityTypeIsEnabled($entity_type_id) {
-    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
-  }
-
-  /**
-   * Stores a custom path along with its settings to configuration for the
-   * currently set variants.
-   *
-   * @param string $path
-   *
-   * @param array $settings
-   *  Settings that are not provided are supplemented by defaults.
-   *
-   * @return $this
-   *
-   * @todo Validate $settings and throw exceptions
-   */
-  public function addCustomLink($path, $settings = []) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return $this;
-    }
-
-    if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($path)) {
-      // todo: log error.
-      return $this;
-    }
-    if ($path[0] !== '/') {
-      // todo: log error.
-      return $this;
-    }
-
-    $variant_links = $this->getCustomLinks(NULL, FALSE, TRUE);
-    foreach ($variants as $variant) {
-      $links = [];
-      $link_key = 0;
-      if (isset($variant_links[$variant])) {
-        $links = $variant_links[$variant];
-        $link_key = count($links);
-        foreach ($links as $key => $link) {
-          if ($link['path'] === $path) {
-            $link_key = $key;
-            break;
-          }
-        }
-      }
-
-      $links[$link_key] = ['path' => $path] + $settings;
-      $this->configFactory->getEditable("simple_sitemap.custom_links.$variant")
-        ->set('links', $links)->save();
-    }
-
-    return $this;
-  }
-
-  /**
-   * Gets custom link settings for the currently set variants.
-   *
-   * @param string|null $path
-   *  Limits the result set by an internal path.
-   *
-   * @param bool $supplement_defaults
-   *  Supplements the result set with default custom link settings.
-   *
-   * @param bool $multiple_variants
-   *  If true, returns an array of results keyed by variant name, otherwise it
-   *  returns the result set for the first variant only.
-   *
-   * @return array|mixed|null
-   */
-  public function getCustomLinks($path = NULL, $supplement_defaults = TRUE, $multiple_variants = FALSE) {
-    $all_custom_links = [];
-    foreach ($variants = $this->getVariants(FALSE) as $variant) {
-      $custom_links = $this->configFactory
-        ->get("simple_sitemap.custom_links.$variant")
-        ->get('links');
-
-      $custom_links = !empty($custom_links) ? $custom_links : [];
-
-      if (!empty($custom_links) && $path !== NULL) {
-        foreach ($custom_links as $key => $link) {
-          if ($link['path'] !== $path) {
-            unset($custom_links[$key]);
-          }
-        }
-      }
-
-      if (!empty($custom_links) && $supplement_defaults) {
-        foreach ($custom_links as $i => $link_settings) {
-          self::supplementDefaultSettings('custom', $link_settings);
-          $custom_links[$i] = $link_settings;
-        }
-      }
-
-      $custom_links = $path !== NULL && !empty($custom_links)
-        ? array_values($custom_links)[0]
-        : array_values($custom_links);
-
-
-      if (!empty($custom_links)) {
-        if ($multiple_variants) {
-          $all_custom_links[$variant] = $custom_links;
-        }
-        else {
-          return $custom_links;
-        }
-      }
-    }
-
-    return $all_custom_links;
-  }
-
-  /**
-   * Removes custom links from currently set variants.
-   *
-   * @param array|null $paths
-   *  Limits the removal to certain paths.
-   *
-   * @return $this
-   */
-  public function removeCustomLinks($paths = NULL) {
-    if (empty($variants = $this->getVariants(FALSE))) {
-      return $this;
-    }
-
-    if (NULL === $paths) {
-      foreach ($variants as $variant) {
-        $this->configFactory
-          ->getEditable("simple_sitemap.custom_links.$variant")->delete();
-      }
-    }
-    else {
-      $variant_links = $this->getCustomLinks(NULL, FALSE, TRUE);
-      foreach ($variant_links as $variant => $links) {
-        $custom_links = $links;
-        $save = FALSE;
-        foreach ((array) $paths  as $path) {
-          foreach ($custom_links as $key => $link) {
-            if ($link['path'] === $path) {
-              unset($custom_links[$key]);
-              $save = TRUE;
-              break 2;
-            }
-          }
-        }
-        if ($save) {
-          $this->configFactory->getEditable("simple_sitemap.custom_links.$variant")
-            ->set('links', array_values($custom_links))->save();
-        }
-      }
-    }
-
-    return $this;
-  }
-}
diff --git a/web/modules/simple_sitemap/src/SimplesitemapManager.php b/web/modules/simple_sitemap/src/SimplesitemapManager.php
deleted file mode 100644
index a2fe3f50e5184afd1e38f9b7f9043d933d9181d8..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/SimplesitemapManager.php
+++ /dev/null
@@ -1,309 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap;
-
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Database\Connection;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeBase;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeManager;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorBase;
-use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager;
-
-/**
- * Class SimplesitemapManager
- * @package Drupal\simple_sitemap
- */
-class SimplesitemapManager {
-
-  const DEFAULT_SITEMAP_TYPE = 'default_hreflang';
-
-  /**
-   * @var \Drupal\Core\Config\ConfigFactory
-   */
-  protected $configFactory;
-
-  /**
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $db;
-
-  /**
-   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeManager
-   */
-  protected $sitemapTypeManager;
-
-  /**
-   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
-   */
-  protected $urlGeneratorManager;
-
-  /**
-   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
-   */
-  protected $sitemapGeneratorManager;
-
-  /**
-   * @var \Drupal\simple_sitemap\SimplesitemapSettings
-   */
-  protected $settings;
-
-  /**
-   * @var SitemapTypeBase[] $sitemapTypes
-   */
-  protected $sitemapTypes = [];
-
-  /**
-   * @var UrlGeneratorBase[] $urlGenerators
-   */
-  protected $urlGenerators = [];
-
-  /**
-   * @var SitemapGeneratorBase[] $sitemapGenerators
-   */
-  protected $sitemapGenerators = [];
-
-  /**
-   * SimplesitemapManager constructor.
-   * @param \Drupal\Core\Config\ConfigFactory $config_factory
-   * @param \Drupal\Core\Database\Connection $database
-   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeManager $sitemap_type_manager
-   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager $url_generator_manager
-   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager $sitemap_generator_manager
-   * @param \Drupal\simple_sitemap\SimplesitemapSettings $settings
-   */
-  public function __construct(
-    ConfigFactory $config_factory,
-    Connection $database,
-    SitemapTypeManager $sitemap_type_manager,
-    UrlGeneratorManager $url_generator_manager,
-    SitemapGeneratorManager $sitemap_generator_manager,
-    SimplesitemapSettings $settings
-  ) {
-    $this->configFactory = $config_factory;
-    $this->db = $database;
-    $this->sitemapTypeManager = $sitemap_type_manager;
-    $this->urlGeneratorManager = $url_generator_manager;
-    $this->sitemapGeneratorManager = $sitemap_generator_manager;
-    $this->settings = $settings;
-  }
-
-  /**
-   * @param string $sitemap_generator_id
-   * @return \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function getSitemapGenerator($sitemap_generator_id) {
-    if (!isset($this->sitemapGenerators[$sitemap_generator_id])) {
-      $this->sitemapGenerators[$sitemap_generator_id]
-        = $this->sitemapGeneratorManager->createInstance($sitemap_generator_id);
-    }
-
-    return $this->sitemapGenerators[$sitemap_generator_id];
-  }
-
-  /**
-   * @param string $url_generator_id
-   * @return \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorBase
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function getUrlGenerator($url_generator_id) {
-    if (!isset($this->urlGenerators[$url_generator_id])) {
-      $this->urlGenerators[$url_generator_id]
-        = $this->urlGeneratorManager->createInstance($url_generator_id);
-    }
-
-    return $this->urlGenerators[$url_generator_id];
-  }
-
-  /**
-   * @return \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapType\SitemapTypeBase[]
-   */
-  public function getSitemapTypes() {
-    if (empty($this->sitemapTypes)) {
-      $this->sitemapTypes = $this->sitemapTypeManager->getDefinitions();
-    }
-
-    return $this->sitemapTypes;
-  }
-
-  /**
-   * @param string|null $sitemap_type
-   * @param bool $attach_type_info
-   * @return array
-   */
-  public function getSitemapVariants($sitemap_type = NULL, $attach_type_info = TRUE) {
-    if (NULL === $sitemap_type) {
-      $variants_by_type = [];
-      foreach ($this->configFactory->listAll('simple_sitemap.variants.') as $config_name) {
-        $variants = !empty($variants = $this->configFactory->get($config_name)->get('variants')) ? $variants : [];
-        $variants = $attach_type_info ? $this->attachSitemapTypeToVariants($variants, explode('.', $config_name)[2]) : $variants;
-        $variants_by_type[] = $variants;
-      }
-      $variants = array_merge([], ...$variants_by_type);
-    }
-    else {
-      $variants = !empty($variants = $this->configFactory->get("simple_sitemap.variants.$sitemap_type")->get('variants')) ? $variants : [];
-      $variants = $attach_type_info ? $this->attachSitemapTypeToVariants($variants, $sitemap_type) : $variants;
-    }
-
-    // Sort variants by weight.
-    $variant_weights = array_column($variants, 'weight');
-    array_multisort($variant_weights, SORT_ASC, $variants);
-
-    return $variants;
-  }
-
-  /**
-   * @param array $variants
-   * @param string $type
-   * @return array
-   */
-  protected function attachSitemapTypeToVariants(array $variants, $type) {
-    return array_map(static function($variant) use ($type) { return $variant + ['type' => $type]; }, $variants);
-  }
-
-  /**
-   * @param string $name
-   * @param array $definition
-   * @return $this
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function addSitemapVariant($name, $definition = []) {
-    $all_variants = $this->getSitemapVariants();
-    if (isset($all_variants[$name])) {
-      $old_variant = $all_variants[$name];
-      if (!empty($definition['type']) && $old_variant['type'] !== $definition['type']) {
-        $this->removeSitemapVariants($name);
-        unset($old_variant);
-      }
-      else {
-        unset($old_variant['type']);
-      }
-    }
-
-    if (!isset($old_variant) && empty($definition['label'])) {
-      $definition['label'] = (string) $name;
-    }
-
-    if (!isset($old_variant) && empty($definition['type'])) {
-      $definition['type'] = self::DEFAULT_SITEMAP_TYPE;
-    }
-
-    if (isset($definition['weight'])) {
-      $definition['weight'] = (int) $definition['weight'];
-    }
-    elseif (!isset($old_variant)) {
-      $definition['weight'] = 0;
-    }
-
-    if (isset($old_variant)) {
-      $definition += $old_variant;
-    }
-
-    $variants = array_merge($this->getSitemapVariants($definition['type'], FALSE), [$name => ['label' => $definition['label'], 'weight' => $definition['weight']]]);
-    $this->configFactory->getEditable('simple_sitemap.variants.' . $definition['type'])
-      ->set('variants', $variants)
-      ->save();
-
-    return $this;
-  }
-
-  /**
-   * @param null|array|string $variant_names
-   *  Limit removal by specific variants.
-   *
-   * @return $this
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function removeSitemap($variant_names = NULL) {
-    if (NULL === $variant_names || !empty((array) $variant_names)) {
-      $saved_variants = $this->getSitemapVariants();
-      $remove_variants = NULL === $variant_names
-        ? $saved_variants
-        : array_intersect_key($saved_variants, array_flip((array) $variant_names));
-
-      if (!empty($remove_variants)) {
-        $type_definitions = $this->getSitemapTypes();
-        foreach ($remove_variants as $variant_name => $variant_definition) {
-          $this->getSitemapGenerator($type_definitions[$variant_definition['type']]['sitemapGenerator'])
-            ->setSitemapVariant($variant_name)
-            ->remove();
-        }
-      }
-    }
-
-    return $this;
-  }
-
-  /**
-   * @param null|array|string $variant_names
-   *  Limit removal by specific variants.
-   *
-   * @return $this
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function removeSitemapVariants($variant_names = NULL) {
-    if (NULL === $variant_names || !empty((array) $variant_names)) {
-
-      // Remove sitemap instances.
-      $this->removeSitemap($variant_names);
-
-      if (NULL === $variant_names) {
-        // Remove all variants and their bundle settings.
-        foreach(['variants', 'bundle_settings', 'custom_links'] as $config_name_part) {
-          foreach ($this->configFactory->listAll("simple_sitemap.$config_name_part.") as $config_name) {
-            $this->configFactory->getEditable($config_name)->delete();
-          }
-        }
-      }
-      else {
-        // Remove bundle settings for specific variants.
-        foreach ((array) $variant_names as $variant_name) {
-          foreach ($this->configFactory->listAll("simple_sitemap.bundle_settings.$variant_name.") as $config_name) {
-            $this->configFactory->getEditable($config_name)->delete();
-          }
-        }
-
-        // Remove custom links for specific variants.
-        foreach ((array) $variant_names as $variant_name) {
-          foreach ($this->configFactory->listAll("simple_sitemap.custom_links.$variant_name") as $config_name) {
-            $this->configFactory->getEditable($config_name)->delete();
-          }
-        }
-
-        // Remove specific variants from configuration.
-        $remove_variants = [];
-        $variants = $this->getSitemapVariants();
-        foreach ((array) $variant_names as $variant_name) {
-          if (isset($variants[$variant_name])) {
-            $remove_variants[$variants[$variant_name]['type']][$variant_name] = $variant_name;
-          }
-        }
-        foreach ($remove_variants as $type => $variants_per_type) {
-          $this->configFactory->getEditable("simple_sitemap.variants.$type")
-            ->set('variants', array_diff_key($this->getSitemapVariants($type, FALSE), $variants_per_type))
-            ->save();
-        }
-      }
-
-      // Remove bundle setting overrides for entities.
-      $query = $this->db->delete('simple_sitemap_entity_overrides');
-      if (NULL !== $variant_names) {
-        $query->condition('type', (array) $variant_names, 'IN');
-      }
-      $query->execute();
-
-      // Remove default variant setting.
-      if (NULL === $variant_names
-        || in_array($this->settings->getSetting('default_variant', ''), (array) $variant_names)) {
-        $this->settings->saveSetting('default_variant', '');
-      }
-    }
-
-    return $this;
-  }
-}
diff --git a/web/modules/simple_sitemap/src/SimplesitemapSettings.php b/web/modules/simple_sitemap/src/SimplesitemapSettings.php
deleted file mode 100644
index 1654604c4b3dd748365cb5376145885c28f73d6b..0000000000000000000000000000000000000000
--- a/web/modules/simple_sitemap/src/SimplesitemapSettings.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-namespace Drupal\simple_sitemap;
-
-use Drupal\Core\Config\ConfigFactory;
-
-/**
- * Class SimplesitemapSettings
- * @package Drupal\simple_sitemap
- */
-class SimplesitemapSettings {
-
-  /**
-   * @var \Drupal\Core\Config\ConfigFactory
-   */
-  protected $configFactory;
-
-
-  /**
-   * SimplesitemapSettings constructor.
-   * @param \Drupal\Core\Config\ConfigFactory $config_factory
-   */
-  public function __construct(ConfigFactory $config_factory) {
-    $this->configFactory = $config_factory;
-  }
-
-  /**
-   * Returns a specific sitemap setting or a default value if setting does not
-   * exist.
-   *
-   * @param string $name
-   *  Name of the setting, like 'max_links'.
-   *
-   * @param mixed $default
-   *  Value to be returned if the setting does not exist in the configuration.
-   *
-   * @return mixed
-   *  The current setting from configuration or a default value.
-   */
-  public function getSetting($name, $default = FALSE) {
-    $setting = $this->configFactory
-      ->get('simple_sitemap.settings')
-      ->get($name);
-
-    return NULL !== $setting ? $setting : $default;
-  }
-
-  public function getSettings() {
-    return $this->configFactory
-      ->get('simple_sitemap.settings')
-      ->get();
-  }
-
-  /**
-   * Stores a specific sitemap setting in configuration.
-   *
-   * @param string $name
-   *  Setting name, like 'max_links'.
-   * @param mixed $setting
-   *  The setting to be saved.
-   *
-   * @return $this
-   */
-  public function saveSetting($name, $setting) {
-    $this->configFactory->getEditable('simple_sitemap.settings')
-      ->set($name, $setting)->save();
-
-    return $this;
-  }
-}
diff --git a/web/modules/simple_sitemap/tests/scripts/performance_test.php b/web/modules/simple_sitemap/tests/scripts/performance_test.php
index bae01b8d2a1160d8f413856cea89ef810615c4bf..23200ed7f2265c84cc3e81949cce17cb7768a42f 100644
--- a/web/modules/simple_sitemap/tests/scripts/performance_test.php
+++ b/web/modules/simple_sitemap/tests/scripts/performance_test.php
@@ -1,5 +1,7 @@
 <?php
 
+// @codingStandardsIgnoreFile
+
 use Drupal\Component\Utility\Timer;
 use Drupal\Core\Batch\BatchBuilder;
 use Drupal\Core\Database\Database;
@@ -72,10 +74,12 @@ public function createNodeType() {
     ]);
     $node_type->save();
     node_add_body_field($node_type);
-    \Drupal::service('simple_sitemap.generator')
-      ->setBundleSettings('node', 'simple_sitemap_performance_test', [
+
+    /** @var \Drupal\simple_sitemap\Manager\Generator $generator */
+    $generator = \Drupal::service('simple_sitemap.generator');
+    $generator->entityManager()->setBundleSettings('node', 'simple_sitemap_performance_test', [
         'index' => TRUE,
-      ]);
+    ]);
   }
 
   public function createNode() {
@@ -92,7 +96,7 @@ public function runGenerate($count_queries = FALSE) {
     $batch = new BatchBuilder();
     $relative_path_to_script = (new Filesystem())->makePathRelative(__DIR__, \Drupal::root()) . basename(__FILE__);
     $batch->setFile($relative_path_to_script);
-    $batch->addOperation(__CLASS__ . '::' . 'doBatchGenerateSitemap', [$count_queries]);
+    $batch->addOperation(__CLASS__ . '::' . 'doBatchGenerate', [$count_queries]);
     $batch->setFinishCallback([BatchTrait::class, 'finishGeneration']);
 
     // Start drush batch process.
@@ -118,13 +122,13 @@ public function runGenerate($count_queries = FALSE) {
    * @param $context
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
-  public static function doBatchGenerateSitemap($count_queries = FALSE, &$context) {
+  public static function doBatchGenerate($count_queries = FALSE, &$context) {
     if ($count_queries) {
       $query_logger = Database::startLog('simple_sitemap');
     }
     // Passes a special object in to $context that outputs every time
     // $context['message'] is set.
-    BatchTrait::doBatchGenerateSitemap($context);
+    BatchTrait::doBatchGenerate($context);
     if ($count_queries) {
       $context['message'] = "Query count: " . count($query_logger->get('simple_sitemap'));
     }
diff --git a/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTest.php b/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTest.php
index bdce5d2ea5264e5e05035e5519c74189ab43c196..636ca7c6a25d31a6ad2f9f2887bf2187063f9eab 100644
--- a/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTest.php
+++ b/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTest.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Url;
 use Drupal\node\Entity\Node;
+use Drupal\simple_sitemap\Entity\SimpleSitemap;
 use Drupal\simple_sitemap\Queue\QueueWorker;
 
 /**
@@ -21,7 +22,7 @@ class SimplesitemapTest extends SimplesitemapTestBase {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testInitialGeneration() {
-    $this->generator->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('urlset');
     $this->assertSession()->responseContains(
@@ -31,6 +32,77 @@ public function testInitialGeneration() {
     $this->assertSession()->responseContains('daily');
   }
 
+  /**
+   * Tests if a disabled sitemap returns a 404 and has no chunks.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function testDisableSitemap() {
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->drupalGet($this->defaultSitemapUrl);
+    $this->assertSession()->statusCodeEquals(200);
+    $sitemap = SimpleSitemap::load('default');
+    $sitemap->disable()->save();
+    $this->assertEmpty($sitemap->fromPublishedAndUnpublished()->getChunkCount());
+    $this->drupalGet($this->defaultSitemapUrl);
+    $this->assertSession()->statusCodeEquals(404);
+  }
+
+  /**
+   * Tests if a deleted sitemap returns a 404.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function testDeleteSitemap() {
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->drupalGet($this->defaultSitemapUrl);
+    $this->assertSession()->statusCodeEquals(200);
+    $sitemap = SimpleSitemap::load('default');
+    $sitemap->delete();
+    $this->drupalGet($this->defaultSitemapUrl);
+    $this->assertSession()->statusCodeEquals(404);
+  }
+
+  /**
+   * Tests if a sitemap with no links returns a 404 and has no chunks.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function testEmptySitemap() {
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->drupalGet($this->defaultSitemapUrl);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->assertEmpty(SimpleSitemap::load('default')->fromPublishedAndUnpublished()->getChunkCount());
+    $this->drupalGet($this->defaultSitemapUrl);
+    $this->assertSession()->statusCodeEquals(404);
+  }
+
+  /**
+   * Test cached sitemap.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   * @throws \Behat\Mink\Exception\ExpectationException
+   */
+  public function testCachedSitemap() {
+    $this->generator->customLinkManager()->add(
+      '/node/' . $this->node->id(),
+      ['priority' => 0.2, 'changefreq' => 'monthly']
+    );
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
+
+    $this->drupalGet($this->defaultSitemapUrl);
+    $assert = $this->assertSession();
+    $assert->statusCodeEquals(200);
+    $assert->responseHeaderContains('X-Drupal-Cache-Tags', 'sitemap');
+  }
+
   /**
    * Test custom link.
    *
@@ -38,10 +110,11 @@ public function testInitialGeneration() {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testAddCustomLink() {
-    $this->generator->addCustomLink(
+    $this->generator->customLinkManager()->add(
       '/node/' . $this->node->id(),
       ['priority' => 0.2, 'changefreq' => 'monthly']
-    )->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    );
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -55,14 +128,15 @@ public function testAddCustomLink() {
       '/node/' . $this->node->id() . ' 0.2 monthly'
     );
 
-    $this->generator->addCustomLink(
+    $this->generator->customLinkManager()->add(
       '/node/' . $this->node->id(),
       ['changefreq' => 'yearly']
-    )->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    );
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet('admin/config/search/simplesitemap/custom');
     $this->assertSession()->pageTextContains(
-      '/node/' . $this->node->id() . ' yearly'
+      '/node/' . $this->node->id() . ' 0.5 yearly'
     );
   }
 
@@ -73,9 +147,9 @@ public function testAddCustomLink() {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testAddCustomLinkDefaults() {
-    $this->generator->removeCustomLinks()
-      ->addCustomLink('/node/' . $this->node->id())
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->customLinkManager()
+      ->remove()->add('/node/' . $this->node->id());
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -84,12 +158,12 @@ public function testAddCustomLinkDefaults() {
   }
 
   /**
-   * Tests locks
+   * Tests locks.
    */
   public function testLocking() {
-    $this->generator->removeCustomLinks()
-      ->addCustomLink('/node/' . $this->node->id())
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->customLinkManager()
+      ->remove()->add('/node/' . $this->node->id());
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
     $this->drupalLogin($this->createUser(['administer sitemap settings']));
 
     $this->drupalGet('/admin/config/search/simplesitemap/settings');
@@ -114,16 +188,17 @@ public function testLocking() {
   public function testRemoveCustomLinks() {
 
     // Test removing one custom path from the sitemap.
-    $this->generator->addCustomLink('/node/' . $this->node->id())
-      ->removeCustomLinks('/node/' . $this->node->id())
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->customLinkManager()
+      ->add('/node/' . $this->node->id())
+      ->remove('/node/' . $this->node->id());
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseNotContains('node/' . $this->node->id());
 
     // Test removing all custom paths from the sitemap.
-    $this->generator->removeCustomLinks()
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseNotContains(
@@ -140,27 +215,27 @@ public function testRemoveCustomLinks() {
    * @todo Add form tests
    */
   public function testSetBundleSettings() {
-    $this->assertFalse($this->generator->bundleIsIndexed('node', 'page'));
+    $this->assertFalse($this->generator->entityManager()->bundleIsIndexed('node', 'page'));
 
     // Index new bundle.
-    $this->generator->removeCustomLinks()
-      ->setBundleSettings('node', 'page', [
-        'index' => TRUE,
-        'priority' => 0.5,
-        'changefreq' => 'hourly',
-      ])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->customLinkManager()->remove();
+    $this->generator->entityManager()->setBundleSettings('node', 'page', [
+      'index' => TRUE,
+      'priority' => 0.5,
+      'changefreq' => 'hourly',
+    ]);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
     $this->assertSession()->responseContains('0.5');
     $this->assertSession()->responseContains('hourly');
 
-    $this->assertTrue($this->generator->bundleIsIndexed('node', 'page'));
+    $this->assertTrue($this->generator->entityManager()->bundleIsIndexed('node', 'page'));
 
     // Only change bundle priority.
-    $this->generator->setBundleSettings('node', 'page', ['priority' => 0.9])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page', ['priority' => 0.9]);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -168,11 +243,8 @@ public function testSetBundleSettings() {
     $this->assertSession()->responseContains('0.9');
 
     // Only change bundle changefreq.
-    $this->generator->setBundleSettings(
-      'node',
-      'page',
-      ['changefreq' => 'daily']
-    )->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page', ['changefreq' => 'daily']);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -180,8 +252,8 @@ public function testSetBundleSettings() {
     $this->assertSession()->responseContains('daily');
 
     // Remove changefreq setting.
-    $this->generator->setBundleSettings('node', 'page', ['changefreq' => ''])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page', ['changefreq' => '']);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -192,19 +264,20 @@ public function testSetBundleSettings() {
     $this->drupalCreateContentType(['type' => 'blog']);
 
     $node3 = $this->createNode(['title' => 'Node3', 'type' => 'blog']);
-    $this->generator->setBundleSettings('node', 'page', ['index' => TRUE])
-      ->setBundleSettings('node', 'blog', ['index' => TRUE])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()
+      ->setBundleSettings('node', 'page', ['index' => TRUE])
+      ->setBundleSettings('node', 'blog', ['index' => TRUE]);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
     $this->assertSession()->responseContains('node/' . $node3->id());
 
     // Set bundle 'index' setting to false.
-    $this->generator
+    $this->generator->entityManager()
       ->setBundleSettings('node', 'page', ['index' => FALSE])
-      ->setBundleSettings('node', 'blog', ['index' => FALSE])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+      ->setBundleSettings('node', 'blog', ['index' => FALSE]);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
 
@@ -219,9 +292,9 @@ public function testSetBundleSettings() {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testSetBundleSettingsDefaults() {
-    $this->generator->setBundleSettings('node', 'page')
-      ->removeCustomLinks()
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -238,32 +311,23 @@ public function testSetBundleSettingsDefaults() {
    * @throws \Drupal\Core\Entity\EntityStorageException
    */
   public function testLinkCount() {
-    $this->generator->setBundleSettings('node', 'page')
-      ->removeCustomLinks()
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalLogin($this->createUser(['administer sitemap settings']));
     $this->drupalGet('admin/config/search/simplesitemap');
-    $link_count_elements = $this->xpath('//*[@id="simple-sitemap-sitemaps-form"]//table/tbody/tr/td[3]');
+    $link_count_elements = $this->xpath('//*[@id="simple-sitemap-status-form"]//table/tbody/tr/td[4]');
     $this->assertSame('2', $link_count_elements[0]->getText());
 
     $this->createNode(['title' => 'Another node', 'type' => 'page']);
-    $this->generator->setBundleSettings('node', 'page')
-      ->removeCustomLinks()
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
     $this->drupalLogin($this->createUser(['administer sitemap settings']));
     $this->drupalGet('admin/config/search/simplesitemap');
-    $link_count_elements = $this->xpath('//*[@id="simple-sitemap-sitemaps-form"]//table/tbody/tr/td[3]');
+    $link_count_elements = $this->xpath('//*[@id="simple-sitemap-status-form"]//table/tbody/tr/td[4]');
     $this->assertSame('3', $link_count_elements[0]->getText());
-
-    // Pretend that we've just run the simple_sitemap_update_8305() update on a
-    // site with existing sitemaps.
-    \Drupal::database()->update('simple_sitemap')
-      ->fields(['link_count' => 0])
-      ->execute();
-    $this->drupalGet('admin/config/search/simplesitemap');
-    $link_count_elements = $this->xpath('//*[@id="simple-sitemap-sitemaps-form"]//table/tbody/tr/td[3]');
-    $this->assertSame('unavailable', $link_count_elements[0]->getText());
   }
 
   /**
@@ -274,25 +338,24 @@ public function testLinkCount() {
    */
   public function testLastmod() {
     // Entity links should have 'lastmod'.
-    $this->generator->setBundleSettings('node', 'page')
-      ->removeCustomLinks()
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('lastmod');
 
     // Entity custom links should have 'lastmod'.
-    $this->generator->setBundleSettings('node', 'page', ['index' => FALSE])
-      ->addCustomLink('/node/' . $this->node->id())
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page', ['index' => FALSE]);
+    $this->generator->customLinkManager()->add('/node/' . $this->node->id());
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('lastmod');
 
     // Non-entity custom links should not have 'lastmod'.
-    $this->generator->removeCustomLinks()
-      ->addCustomLink('/')
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->customLinkManager()->remove()->add('/');
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseNotContains('lastmod');
@@ -304,16 +367,25 @@ public function testLastmod() {
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
   public function testRemoveDuplicatesSetting() {
-    $this->generator->setBundleSettings('node', 'page')
-      ->addCustomLink('/node/1')
-      ->saveSetting('remove_duplicates', TRUE)
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+
+    $this->generator->customLinkManager()
+      ->add('/node/1')
+      ->add('/node/2?foo=bar');
+
+    $this->generator->saveSetting('remove_duplicates', TRUE)
+      ->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
+
+    // Make sure the duplicate custom link is not included.
     $this->assertUniqueTextWorkaround('node/' . $this->node->id());
 
+    // Make sure a duplicate path with a different query is included.
+    $this->assertNoUniqueTextWorkaround('node/' . $this->node2->id());
+
     $this->generator->saveSetting('remove_duplicates', FALSE)
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+      ->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertNoUniqueTextWorkaround('node/' . $this->node->id());
@@ -326,10 +398,10 @@ public function testRemoveDuplicatesSetting() {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testMaxLinksSetting() {
-    $this->generator->setBundleSettings('node', 'page')
-      ->saveSetting('max_links', 1)
-      ->removeCustomLinks()
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->customLinkManager()->remove();
+    $this->generator->saveSetting('max_links', 1)
+      ->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('sitemap.xml?page=1');
@@ -346,9 +418,7 @@ public function testMaxLinksSetting() {
     $this->assertSession()->responseNotContains('node/' . $this->node->id());
   }
 
-  /**
-   * @todo testGenerateDurationSetting
-   */
+  // phpcs:ignore @todo testGenerateDurationSetting
 
   /**
    * Test setting the base URL.
@@ -357,16 +427,16 @@ public function testMaxLinksSetting() {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testBaseUrlSetting() {
-    $this->generator->setBundleSettings('node', 'page')
-      ->saveSetting('base_url', 'http://base_url_test')
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->saveSetting('base_url', 'http://base_url_test')
+      ->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('http://base_url_test');
 
     // Set base URL in the sitemap index.
     $this->generator->saveSetting('max_links', 1)
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+      ->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('http://base_url_test/sitemap.xml?page=1');
@@ -378,14 +448,18 @@ public function testBaseUrlSetting() {
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    * @throws \Behat\Mink\Exception\ExpectationException
    *
-   * @todo: Use form testing instead of responseContains().
+   * @todo Use form testing instead of responseContains().
    */
   public function testSetEntityInstanceSettings() {
-    $this->generator->setBundleSettings('node', 'page')
-      ->removeCustomLinks()
-      ->setEntityInstanceSettings('node', $this->node->id(), ['priority' => 0.1, 'changefreq' => 'never'])
-      ->setEntityInstanceSettings('node', $this->node2->id(), ['index' => FALSE])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()
+      ->setBundleSettings('node', 'page')
+      ->setEntityInstanceSettings('node', $this->node->id(), [
+        'priority' => 0.1,
+        'changefreq' => 'never',
+      ])
+      ->setEntityInstanceSettings('node', $this->node2->id(), ['index' => FALSE]);
+    $this->generator->customLinkManager()->remove();
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     // Test sitemap result.
     $this->drupalGet($this->defaultSitemapUrl);
@@ -405,8 +479,11 @@ public function testSetEntityInstanceSettings() {
     // Test database changes.
     $this->assertEquals(1, $this->getOverridesCount('node', $this->node->id()));
 
-    $this->generator->setBundleSettings('node', 'page', ['priority' => 0.1, 'changefreq' => 'never'])
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page', [
+      'priority' => 0.1,
+      'changefreq' => 'never',
+    ]);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     // Test sitemap result.
     $this->drupalGet($this->defaultSitemapUrl);
@@ -427,14 +504,14 @@ public function testSetEntityInstanceSettings() {
 
     // Assert that creating a new content type doesn't remove the overrides.
     $this->drupalGet('node/' . $this->node->id() . '/edit');
-    $this->submitForm(['index_default_node_settings' => 0], 'Save');
+    $this->submitForm(['simple_sitemap[default][index]' => 0], 'Save');
     $this->assertEquals(1, $this->getOverridesCount('node', $this->node->id()));
     // Create a new content type.
     $this->drupalGet('admin/structure/types/add');
     $this->submitForm([
       'name' => 'simple_sitemap_type',
       'type' => 'simple_sitemap_type',
-      'index_default_node_settings' => 0,
+      'simple_sitemap[default][index]' => 0,
     ], 'Save content type');
     // The entity override from the other content type should not be affected.
     $this->assertEquals(1, $this->getOverridesCount('node', $this->node->id()));
@@ -452,15 +529,18 @@ public function testSetEntityInstanceSettings() {
    *   The entity type ID.
    * @param string $entity_id
    *   The entity ID.
+   * @param string|array $variant
+   *   A particular variant or an array of variants.
    *
    * @return int
    *   The number of overrides for the given entity type ID and entity ID.
    */
-  protected function getOverridesCount($entity_type_id, $entity_id) {
+  protected function getOverridesCount($entity_type_id, $entity_id, $variant = 'default') {
     return $this->database->select('simple_sitemap_entity_overrides', 'o')
       ->fields('o', ['inclusion_settings'])
       ->condition('o.entity_type', $entity_type_id)
       ->condition('o.entity_id', $entity_id)
+      ->condition('o.type', $variant)
       ->countQuery()
       ->execute()
       ->fetchField();
@@ -471,7 +551,7 @@ protected function getOverridesCount($entity_type_id, $entity_id) {
    */
   public function testNewEntityWithIdSet() {
     $new_node = Node::create([
-      'nid' => mt_rand(5, 10),
+      'nid' => random_int(5, 10),
       'type' => 'page',
     ]);
     // Assert that the form does not break if an entity has an id but is not
@@ -485,28 +565,25 @@ public function testNewEntityWithIdSet() {
    */
   public function testAtomicEntityIndexation() {
     $user_id = $this->privilegedUser->id();
-    $this->generator->setBundleSettings('user')
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('user');
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseNotContains('user/' . $user_id);
 
     user_role_grant_permissions('anonymous', ['access user profiles']);
-    drupal_flush_all_caches(); //todo Not pretty.
 
-    $this->generator->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    // @todo Not pretty.
+    drupal_flush_all_caches();
+
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('user/' . $user_id);
   }
 
-  /**
-   * @todo Test indexing menu.
-   */
-
-  /**
-   * @todo Test deleting a bundle.
-   */
+  // phpcs:ignore @todo Test indexing menu.
+  // phpcs:ignore @todo Test deleting a bundle.
 
   /**
    * Test disabling sitemap support for an entity type.
@@ -515,19 +592,20 @@ public function testAtomicEntityIndexation() {
    * @throws \Behat\Mink\Exception\ExpectationException
    */
   public function testDisableEntityType() {
-    $this->generator->setBundleSettings('node', 'page')
+    $this->generator->entityManager()
+      ->setBundleSettings('node', 'page')
       ->disableEntityType('node');
 
     $this->drupalLogin($this->privilegedUser);
     $this->drupalGet('admin/structure/types/manage/page');
     $this->assertSession()->pageTextNotContains('Simple XML Sitemap');
 
-    $this->generator->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseNotContains('node/' . $this->node->id());
 
-    $this->assertFalse($this->generator->entityTypeIsEnabled('node'));
+    $this->assertFalse($this->generator->entityManager()->entityTypeIsEnabled('node'));
   }
 
   /**
@@ -539,7 +617,8 @@ public function testDisableEntityType() {
    * @todo Test admin/config/search/simplesitemap/entities form.
    */
   public function testEnableEntityType() {
-    $this->generator->disableEntityType('node')
+    $this->generator->entityManager()
+      ->disableEntityType('node')
       ->enableEntityType('node')
       ->setBundleSettings('node', 'page');
 
@@ -547,17 +626,15 @@ public function testEnableEntityType() {
     $this->drupalGet('admin/structure/types/manage/page');
     $this->assertSession()->pageTextContains('Simple XML Sitemap');
 
-    $this->generator->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
 
-    $this->assertTrue($this->generator->entityTypeIsEnabled('node'));
+    $this->assertTrue($this->generator->entityManager()->entityTypeIsEnabled('node'));
   }
 
-  /**
-   * @todo testSitemapLanguages
-   */
+  // phpcs:ignore @todo testSitemapLanguages.
 
   /**
    * Test adding and removing sitemap variants.
@@ -568,14 +645,13 @@ public function testEnableEntityType() {
   public function testSitemapVariants() {
 
     // Test adding a variant.
-    $this->generator->getSitemapManager()->addSitemapVariant('test');
+    SimpleSitemap::create(['id' => 'test', 'type' => 'default_hreflang'])->save();
 
-    $this->generator
-      ->setBundleSettings('node', 'page')
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
-    $variants = $this->generator->getSitemapManager()->getSitemapVariants();
-    $this->assertArrayHasKey('test', $variants);
+    $sitemaps = SimpleSitemap::loadMultiple();
+    $this->assertArrayHasKey('test', $sitemaps);
 
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
@@ -584,28 +660,24 @@ public function testSitemapVariants() {
     $this->drupalGet('test/sitemap.xml');
     $this->assertSession()->responseNotContains('node/' . $this->node->id());
 
-    $this->generator
-      ->setVariants('test')
-      ->setBundleSettings('node', 'page')
-      ->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+    $this->generator->entityManager()->setBundleSettings('node', 'page');
+    $this->generator->setVariants('test')->generate(QueueWorker::GENERATE_TYPE_BACKEND);
 
     // Test if bundle settings have been set for correct variant.
     $this->drupalGet($this->defaultSitemapUrl);
     $this->assertSession()->responseContains('node/' . $this->node->id());
 
-    $this->generator->getSitemapManager()->removeSitemapVariants('test');
+    SimpleSitemap::load('test')->delete();
 
-    $variants = $this->generator->getSitemapManager()->getSitemapVariants();
-    $this->assertArrayNotHasKey('test', $variants);
+    $sitemaps = SimpleSitemap::loadMultiple();
+    $this->assertArrayNotHasKey('test', $sitemaps);
 
     // Test if sitemap has been removed along with the variant.
     $this->drupalGet('test/sitemap.xml');
     $this->assertSession()->statusCodeEquals(404);
   }
 
-  /**
-   * @todo Test removeSitemap().
-   */
+  // phpcs:ignore @todo Test removeSitemap().
 
   /**
    * Test cases for ::testGenerationResume.
@@ -620,7 +692,10 @@ public function generationResumeProvider() {
   }
 
   /**
+   * Test resuming sitemap generation.
+   *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
+   * @throws \Drupal\Core\Entity\EntityStorageException
    *
    * @dataProvider generationResumeProvider
    */
@@ -635,28 +710,32 @@ public function testGenerationResume($element_count, $generate_duration, $max_li
       $this->createNode(['title' => 'node-' . $i, 'type' => 'blog']);
     }
 
+    $this->generator->entityManager()->setBundleSettings('node', 'blog');
+    $this->generator->customLinkManager()->remove();
     $this->generator
-      ->removeCustomLinks()
       ->saveSetting('generate_duration', $generate_duration)
       ->saveSetting('max_links', $max_links)
       ->saveSetting('skip_untranslated', FALSE)
-      ->setBundleSettings('node', 'blog');
+      ->saveSetting('remove_duplicates', FALSE);
 
-    $queue = $this->generator->getQueueWorker()->rebuildQueue();
+    $this->generator->rebuildQueue();
     $generate_count = 0;
-    while ($queue->generationInProgress()) {
+    /** @var \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker */
+    $queue_worker = \Drupal::service('simple_sitemap.queue_worker');
+    while ($queue_worker->generationInProgress()) {
       $generate_count++;
-      $this->generator->generateSitemap(QueueWorker::GENERATE_TYPE_BACKEND);
+      $this->generator->generate(QueueWorker::GENERATE_TYPE_BACKEND);
     }
 
     // Test if sitemap generation has been resumed when time limit is very low.
     $this->assertTrue($generate_duration > $element_count || $generate_count > 1, 'This assertion tests if the sitemap generation is split up into batches due to a low generation time limit setting. The failing of this assertion can mean that the sitemap was wrongfully generated in one go, but it can also mean that the assumed low time setting is still high enough for a one pass generation.');
 
     // Test if correct number of sitemaps have been created.
-    $chunks = $this->database->query('SELECT id FROM {simple_sitemap} WHERE delta != 0 AND status = 1');
-    $chunks->allowRowCount = TRUE;
-    $chunk_count = $chunks->rowCount();
-    $this->assertSame($chunk_count, $expected_sitemap_count);
+    $chunk_count = $this->database->select('simple_sitemap')
+      ->condition('delta', 0, '<>')
+      ->condition('status', TRUE)
+      ->countQuery()->execute()->fetchField();
+    $this->assertEquals((int) $chunk_count, $expected_sitemap_count);
 
     // Test if index has been created when necessary.
     $index = $this->database->query('SELECT id FROM {simple_sitemap} WHERE delta = 0 AND status = 1')
@@ -682,4 +761,3 @@ public function testHrefLangRemoval() {
   }
 
 }
-
diff --git a/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTestBase.php b/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTestBase.php
index c3fe6c193b45af9768d45e0c830c6aba99b42a2f..cb6b19217b766cf01467986d991d20dc9e804670 100644
--- a/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTestBase.php
+++ b/web/modules/simple_sitemap/tests/src/Functional/SimplesitemapTestBase.php
@@ -27,7 +27,7 @@ abstract class SimplesitemapTestBase extends BrowserTestBase {
   /**
    * Simple sitemap generator.
    *
-   * @var \Drupal\simple_sitemap\Simplesitemap
+   * @var \Drupal\simple_sitemap\Manager\Generator
    */
   protected $generator;
 
@@ -59,26 +59,24 @@ abstract class SimplesitemapTestBase extends BrowserTestBase {
    */
   protected $node2;
 
-  protected $defaultSitemapUrl = 'sitemap.xml';
-
   /**
-   * Use the testing profile.
+   * The default sitemap URL.
    *
    * @var string
    */
-  protected $profile = 'testing';
+  protected $defaultSitemapUrl = 'sitemap.xml';
 
   /**
-   * Use the classy theme.
+   * Use the stable9 theme.
    *
    * @var string
    */
-  protected $defaultTheme = 'classy';
+  protected $defaultTheme = 'stable9';
 
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $this->generator = $this->container->get('simple_sitemap.generator');
@@ -127,6 +125,14 @@ protected function assertNoUniqueTextWorkaround($text) {
     $this->assertGreaterThan(1, $nr_found);
   }
 
+  /**
+   * Helper function to create languages.
+   *
+   * @param array|string $langcodes
+   *   An array of language codes or the single language code.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
   protected function addLanguages($langcodes = 'de') {
     foreach ((array) $langcodes as $langcode) {
       ConfigurableLanguage::createFromLangcode($langcode)
diff --git a/web/modules/simple_sitemap/xsl/simple_sitemap.xsl b/web/modules/simple_sitemap/xsl/simple_sitemap.xsl
index b1f5def9adfdff23c0b0e7c824828421cfd6845b..c87d41cc52aad2e43a84555fbf02c85f1529c6f5 100644
--- a/web/modules/simple_sitemap/xsl/simple_sitemap.xsl
+++ b/web/modules/simple_sitemap/xsl/simple_sitemap.xsl
@@ -18,20 +18,22 @@
         <link href="[xsl-css]" type="text/css" rel="stylesheet"/>
       </head>
       <body>
-        <h1>[title]</h1>
-
-        <xsl:choose>
-          <xsl:when test="//sitemap:url">
-            <xsl:call-template name="sitemapTable"/>
-          </xsl:when>
-          <xsl:otherwise>
-            <xsl:call-template name="sitemapIndexTable"/>
-          </xsl:otherwise>
-        </xsl:choose>
-
-        <div id="footer">
+        <header role="banner">
+          <h1>[title]</h1>
+        </header>
+        <main role="main">
+          <xsl:choose>
+            <xsl:when test="//sitemap:url">
+              <xsl:call-template name="sitemapTable"/>
+            </xsl:when>
+            <xsl:otherwise>
+              <xsl:call-template name="sitemapIndexTable"/>
+            </xsl:otherwise>
+          </xsl:choose>
+        </main>
+        <footer role="contentinfo" id="footer">
           <p>[generated-by]</p>
-        </div>
+        </footer>
       </body>
     </html>
   </xsl:template>
diff --git a/web/modules/simple_sitemap/xsl/simple_sitemap.xsl.css b/web/modules/simple_sitemap/xsl/simple_sitemap.xsl.css
index 69c94893a2f320e654bff82d2e1dcbdfebf43bef..f47cfb6dcdcafe953f812b8d0fcff9f235764849 100644
--- a/web/modules/simple_sitemap/xsl/simple_sitemap.xsl.css
+++ b/web/modules/simple_sitemap/xsl/simple_sitemap.xsl.css
@@ -1,7 +1,6 @@
 body {
   background-color: #fff;
   font-family: Verdana, sans-serif;
-  font-size: 10pt;
 }
 
 h1 {
@@ -11,7 +10,6 @@ h1 {
 table.sitemap {
   background-color: #cdcdcd;
   margin: 10px 0 15px;
-  font-size: 8pt;
   width: 100%;
   text-align: left;
 }
@@ -20,7 +18,6 @@ table.sitemap thead tr th,
 table.sitemap tfoot tr th {
   background-color: #e6eeee;
   border: 1px solid #fff;
-  font-size: 8pt;
   padding: 3px;
 }
 
@@ -54,13 +51,13 @@ table.sitemap thead tr .tablesorter-headerDesc {
 
 table.sitemap thead tr .tablesorter-headerAsc .tablesorter-header-inner:after {
   content: '\25b2';
-  position:absolute;
+  position: absolute;
   right: 0;
 }
 
 table.sitemap thead tr .tablesorter-headerDesc .tablesorter-header-inner:after {
   content: '\25bc';
-  position:absolute;
+  position: absolute;
   right: 0;
 }