From 72069f8ecda683de9d7c2c62af0aee5fa96279b9 Mon Sep 17 00:00:00 2001 From: davereid Date: Tue, 9 Jun 2020 22:39:57 -0500 Subject: [PATCH] Issue #3079398 by Dave Reid, rp7: Fixed logic for which entity types can be supported by XML sitemap. Remote entities without base tables are now supported. Entity types such as Custom Blocks, Comment, and Shortcut that should not have been supported are no longer configurable. --- src/Form/XmlSitemapEntitiesSettingsForm.php | 54 +++---- .../XmlSitemapUserFunctionalTest.php | 2 +- xmlsitemap.api.php | 19 +-- xmlsitemap.module | 143 ++++++++++++++---- xmlsitemap.post_update.php | 15 ++ 5 files changed, 161 insertions(+), 72 deletions(-) create mode 100644 xmlsitemap.post_update.php diff --git a/src/Form/XmlSitemapEntitiesSettingsForm.php b/src/Form/XmlSitemapEntitiesSettingsForm.php index 8603fc4a..377697a6 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 b095dd4c..7a44b75e 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 a469acc4..019b83be 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 323f3e62..98915cbc 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 00000000..2c0ca51d --- /dev/null +++ b/xmlsitemap.post_update.php @@ -0,0 +1,15 @@ +