diff --git a/src/Form/XmlSitemapEntitiesSettingsForm.php b/src/Form/XmlSitemapEntitiesSettingsForm.php index 8603fc4a2aadcbd359516f3bd58cc2dee0f51c6f..377697a6f288ab181c05e4eedb4223127503f113 100644 --- a/src/Form/XmlSitemapEntitiesSettingsForm.php +++ b/src/Form/XmlSitemapEntitiesSettingsForm.php @@ -2,15 +2,15 @@ namespace Drupal\xmlsitemap\Form; -use Drupal\Core\Entity\EntityTypeBundleInfoInterface; -use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Entity\ContentEntityTypeInterface; +use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\State\StateInterface; /** * Configure what entities will be included in sitemap. @@ -88,40 +88,41 @@ class XmlSitemapEntitiesSettingsForm extends ConfigFormBase { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $entity_types = $this->entityTypeManager->getDefinitions(); - $labels = []; - $default = []; - $bundles = $this->entityTypeBundleInfo->getAllBundleInfo(); - - foreach ($entity_types as $entity_type_id => $entity_type) { - if (!$entity_type instanceof ContentEntityTypeInterface || !isset($bundles[$entity_type_id])) { - continue; - } + $form = parent::buildForm($form, $form_state); - $labels[$entity_type_id] = $entity_type->getLabel() ?: $entity_type_id; - } + // Create the list of possible entity types. + /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */ + $entity_types = array_filter($this->entityTypeManager->getDefinitions(), 'xmlsitemap_is_entity_type_supported'); + // Create the list of options as well as the default values based on which + // entity types have enabled configuration already. + $labels = array_map(function (EntityTypeInterface $entityType) { + return $entityType->getLabel(); + }, $entity_types); asort($labels); - - $form['#labels'] = $labels; + $defaults = array_keys(array_filter(array_map(function (EntityTypeInterface $entityType) { + return xmlsitemap_link_entity_check_enabled($entityType->id()); + }, $entity_types))); $form['entity_types'] = [ '#title' => $this->t('Custom sitemap entities settings'), '#type' => 'checkboxes', '#options' => $labels, - '#default_value' => $default, + '#default_value' => $defaults, ]; $form['settings'] = ['#tree' => TRUE]; foreach ($labels as $entity_type_id => $label) { $entity_type = $entity_types[$entity_type_id]; + $bundle_label = $entity_type->getBundleLabel() ?: $label; + $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); $form['settings'][$entity_type_id] = [ '#type' => 'container', '#entity_type' => $entity_type_id, - '#bundle_label' => $entity_type->getBundleLabel() ? $entity_type->getBundleLabel() : $label, - '#title' => $entity_type->getBundleLabel() ? $entity_type->getBundleLabel() : $label, + '#bundle_label' => $bundle_label, + '#title' => $bundle_label, '#states' => [ 'visible' => [ ':input[name="entity_types[' . $entity_type_id . ']"]' => ['checked' => TRUE], @@ -134,7 +135,7 @@ class XmlSitemapEntitiesSettingsForm extends ConfigFormBase { '#default_value' => [], '#header' => [ [ - 'data' => $entity_type->getBundleLabel() ? $entity_type->getBundleLabel() : $label, + 'data' => $bundle_label, 'class' => ['bundle'], ], [ @@ -142,11 +143,11 @@ class XmlSitemapEntitiesSettingsForm extends ConfigFormBase { 'class' => ['operations'], ], ], - '#empty' => $this->t('No content available.'), ], + '#access' => !empty($bundles), ]; - foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) { + foreach ($bundles as $bundle => $bundle_info) { $form['settings'][$entity_type_id][$bundle]['settings'] = [ '#type' => 'item', '#label' => $bundle_info['label'], @@ -171,15 +172,8 @@ class XmlSitemapEntitiesSettingsForm extends ConfigFormBase { ], ]; $form['settings'][$entity_type_id]['types']['#default_value'][$bundle] = xmlsitemap_link_bundle_check_enabled($entity_type_id, $bundle); - - if (xmlsitemap_link_bundle_check_enabled($entity_type_id, $bundle)) { - $default[$entity_type_id] = $entity_type_id; - } } } - $form['entity_types']['#default_value'] = $default; - $form = parent::buildForm($form, $form_state); - $form['actions']['submit']['#value'] = $this->t('Save'); return $form; } diff --git a/tests/src/Functional/XmlSitemapUserFunctionalTest.php b/tests/src/Functional/XmlSitemapUserFunctionalTest.php index b095dd4c7586191a7d4f1b48fc1236f2f2e64c5d..7a44b75ed46f48c5e2055b81a3c5d778d6d48a44 100644 --- a/tests/src/Functional/XmlSitemapUserFunctionalTest.php +++ b/tests/src/Functional/XmlSitemapUserFunctionalTest.php @@ -72,7 +72,7 @@ class XmlSitemapUserFunctionalTest extends XmlSitemapTestBase { $this->assertSession()->fieldExists('xmlsitemap[priority]'); $this->assertSession()->fieldExists('xmlsitemap[changefreq]'); - $this->drupalGet('user/' . $this->normal_user->id() . '/edit'); + $this->drupalGet('user/' . $this->normal_user->id() . '/edit'); $this->assertSession()->fieldExists('xmlsitemap[status]'); $this->assertSession()->fieldExists('xmlsitemap[priority]'); $this->assertSession()->fieldExists('xmlsitemap[changefreq]'); diff --git a/xmlsitemap.api.php b/xmlsitemap.api.php index a469acc4e945f26555745ea120e285059b059712..019b83be11c86667aa55b1ab28e8d81d48a729c0 100644 --- a/xmlsitemap.api.php +++ b/xmlsitemap.api.php @@ -21,19 +21,14 @@ function hook_xmlsitemap_link_info() { return [ 'mymodule' => [ - 'label' => 'My module', - 'base table' => 'mymodule', - 'entity keys' => [ - // Primary ID key on {base table}. - 'id' => 'myid', - // Subtype key on {base table}. - 'bundle' => 'mysubtype', - ], - 'path callback' => 'mymodule_path', + 'label' => 'My module items', + // If your items can be grouped into unique "bundles", add the following + // information. 'bundle label' => t('Subtype name'), 'bundles' => [ 'mysubtype1' => [ 'label' => t('My subtype 1'), + // If your bundles have an administrative UI, list it. 'admin' => [ 'real path' => 'admin/settings/mymodule/mysubtype1/edit', 'access arguments' => ['administer mymodule'], @@ -47,11 +42,11 @@ function hook_xmlsitemap_link_info() { 'xmlsitemap' => [ // Callback function to take an array of IDs and save them as sitemap // links. - 'process callback' => '', + 'process callback' => 'mymodule_xmlsitemap_process_links', // Callback function used in batch API for rebuilding all links. - 'rebuild callback' => '', + 'rebuild callback' => 'mymodule_xmlsitemap_rebuild_links', // Callback function called from the XML sitemap settings page. - 'settings callback' => '', + 'settings callback' => 'mymodule_xmlsitemap_settings', ], ], ]; diff --git a/xmlsitemap.module b/xmlsitemap.module index 323f3e62490aded7f3f61fa2f30a59861d73da42..98915cbcc9f1b9501a0df9ebd7956297ad34e4ac 100644 --- a/xmlsitemap.module +++ b/xmlsitemap.module @@ -20,6 +20,7 @@ use Drupal\Core\Entity\ContentEntityFormInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityTypeInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; @@ -632,6 +633,74 @@ function _xmlsitemap_delete_recursive($path, $delete_root = FALSE) { return $filesystem->delete($path); } +/** + * Implements hook_entity_type_build(). + */ +function xmlsitemap_entity_type_build(array &$entity_types) { + // Mark some specific core entity types as not supported by XML sitemap. + // If a site wants to undo this, they may use hook_entity_type_alter(). + $unsupported_types = [ + // Custom blocks. + 'block_content', + // Comments. + 'comment', + // Shortcut items. + 'shortcut', + // Custom Token module. + // @see https://www.drupal.org/project/token_custom/issues/3150038 + 'token_custom', + ]; + + /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ + foreach ($unsupported_types as $entity_type_id) { + if (isset($entity_types[$entity_type_id])) { + $entity_types[$entity_type_id]->set('xmlsitemap', FALSE); + } + } +} + +/** + * Determines if an entity type can be listed in the XML sitemap as links. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return bool + * TRUE if the entity type can be used, or FALSE otherwise. + */ +function xmlsitemap_is_entity_type_supported(EntityTypeInterface $entity_type) { + // If the XML sitemap status in the entity type annotation has been set then + // return that first. This will allow modules to bypass the logic below if + // needed. + $status = $entity_type->get('xmlsitemap'); + if ($status !== NULL) { + return $status; + } + + // Skip if the entity type is not a content entity type. + if (!($entity_type instanceof ContentEntityTypeInterface)) { + return FALSE; + } + + // Skip if the entity type is internal (and not considered public). + if ($entity_type->isInternal()) { + return FALSE; + } + + // Skip if the entity type does not have a canonical URL. + if (!$entity_type->hasLinkTemplate('canonical') && !$entity_type->getUriCallback()) { + return FALSE; + } + + // Skip if the entity type as a bundle entity type but does not yet have + // any bundles created. + if ($entity_type->getBundleEntityType() && !\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id())) { + return FALSE; + } + + return TRUE; +} + /** * Returns information about supported sitemap link types. * @@ -654,9 +723,7 @@ function xmlsitemap_get_link_info($type = NULL, $reset = FALSE) { if ($reset) { $link_info = NULL; - foreach (\Drupal::languageManager()->getLanguages() as $lang) { - \Drupal::cache()->delete('xmlsitemap:link_info:' . $lang->getId()); - } + \Drupal::service('cache_tags.invalidator')->invalidateTags(['xmlsitemap']); } if (!isset($link_info)) { @@ -667,34 +734,36 @@ function xmlsitemap_get_link_info($type = NULL, $reset = FALSE) { else { $link_info = []; $entity_types = \Drupal::entityTypeManager()->getDefinitions(); + foreach ($entity_types as $key => $entity_type) { + if (!xmlsitemap_is_entity_type_supported($entity_type)) { + continue; + } + $link_info[$key] = [ 'label' => $entity_type->getLabel(), 'type' => $entity_type->id(), 'base table' => $entity_type->getBaseTable(), 'bundles' => \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id()), + 'bundle label' => $entity_type->getBundleLabel(), + 'entity keys' => [ + 'id' => $entity_type->getKey('id'), + 'bundle' => $entity_type->getKey('bundle'), + ], ]; - $uri_callback = $entity_type->getUriCallback(); - if (empty($uri_callback) || !isset($entity_type->xmlsitemap)) { - // Remove any non URL-able or XML sitemap un-supported entities. - } - foreach (\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id()) as $bundle) { - if (!isset($bundle['xmlsitemap'])) { - // Remove any un-supported entity bundles. - } - } } + $link_info = array_merge($link_info, \Drupal::moduleHandler()->invokeAll('xmlsitemap_link_info')); foreach ($link_info as $key => &$info) { $info += [ 'type' => $key, 'base table' => FALSE, 'bundles' => [], - 'xmlsitemap' => [ - 'process callback' => 'xmlsitemap_xmlsitemap_process_entity_links', - ], ]; - if (!isset($info['xmlsitemap']['rebuild callback']) && !empty($info['base table']) && $entity_types[$key]->getKey('id')) { + if (!isset($info['xmlsitemap']['process callback']) && !empty($info['entity keys'])) { + $info['xmlsitemap']['process callback'] = 'xmlsitemap_xmlsitemap_process_entity_links'; + } + if (!isset($info['xmlsitemap']['rebuild callback']) && !empty($info['entity keys']['id'])) { $info['xmlsitemap']['rebuild callback'] = 'xmlsitemap_rebuild_batch_fetch'; } foreach ($info['bundles'] as $bundle => &$bundle_info) { @@ -705,9 +774,32 @@ function xmlsitemap_get_link_info($type = NULL, $reset = FALSE) { } } \Drupal::moduleHandler()->alter('xmlsitemap_link_info', $link_info); - ksort($link_info); + + // Sort the entity types by label. + uasort($link_info, function ($a, $b) { + // Put frontpage first. + if ($a['type'] === 'frontpage') { + return -1; + } + if ($b['type'] === 'frontpage') { + return 1; + } + return strnatcmp($a['label'], $b['label']); + }); + // Cache by language since this info contains translated strings. - \Drupal::cache()->set($cid, $link_info, Cache::PERMANENT, ['xmlsitemap']); + // Also include entity type tags since this is tied to entity and bundle + // information. + \Drupal::cache()->set( + $cid, + $link_info, + Cache::PERMANENT, + [ + 'xmlsitemap', + 'entity_types', + 'entity_bundles', + ] + ); } } @@ -1550,22 +1642,15 @@ function xmlsitemap_form_alter(array &$form, FormStateInterface $form_state, $fo function xmlsitemap_xmlsitemap_index_links($limit) { $entity_type_manager = \Drupal::entityTypeManager(); $entity_types = $entity_type_manager->getDefinitions(); - $bundles = \Drupal::service('entity_type.bundle.info')->getAllBundleInfo(); foreach ($entity_types as $entity_type_id => $entity_type) { // Don't try to loop over non-content entity types or with no bundle // defined. - if (!$entity_type instanceof ContentEntityTypeInterface || !isset($bundles[$entity_type_id])) { + if (!xmlsitemap_is_entity_type_supported($entity_type)) { continue; } - $entity_bundles = []; - foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) { - if (!xmlsitemap_link_bundle_check_enabled($entity_type_id, $bundle)) { - continue; - } - $entity_bundles[] = $bundle; - } - if (empty($entity_bundles)) { + $bundles = xmlsitemap_get_link_type_enabled_bundles($entity_type_id); + if (empty($bundles)) { continue; } @@ -1574,7 +1659,7 @@ function xmlsitemap_xmlsitemap_index_links($limit) { $query->range(0, $limit); if ($entity_type->hasKey('bundle')) { $bundle_key = $entity_type->getKey('bundle'); - $query->condition($bundle_key, $entity_bundles, 'IN'); + $query->condition($bundle_key, $bundles, 'IN'); } $ids = $query->execute(); diff --git a/xmlsitemap.post_update.php b/xmlsitemap.post_update.php new file mode 100644 index 0000000000000000000000000000000000000000..2c0ca51df6c5a68fe764e9fd54dbbb398601ea13 --- /dev/null +++ b/xmlsitemap.post_update.php @@ -0,0 +1,15 @@ +