Unverified Commit 72069f8e authored by Dave Reid's avatar Dave Reid Committed by Dave Reid

Issue #3079398 by Dave Reid, rp7: Fixed logic for which entity types can be...

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.
parent 6d458e79
......@@ -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;
}
......
......@@ -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]');
......
......@@ -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',
],
],
];
......
......@@ -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();
......
<?php
/**
* @file
* Post update functions for XML Sitemap.
*/
/**
* Force cache to be cleared with new hook_entity_type_build().
*
* @see https://www.drupal.org/project/xmlsitemap/issues/3079398
*/
function xmlsitemap_post_update_entity_type_build_hook() {
// Empty post-update hook.
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment