Commit 3442174e authored by gbyte.co's avatar gbyte.co

Making sitemap work with potentially all content entities and removing plugin architecture.

parent 4ce4b620
......@@ -23,9 +23,8 @@ content:
* menu links
* users
* custom links
The above functionalities are implemented as Drupal 8 plugins and it is easy to
add support to custom entity types through implementing your own plugins.
* commerce products
* possibly other contributed entities
To learn about XML sitemaps, see https://en.wikipedia.org/wiki/Sitemaps.
......@@ -57,7 +56,7 @@ https://en.wikipedia.org/wiki/Sitemaps to learn more about this parameter.
Inclusion and priority settings of bundles can be overridden on a per-entity
basis. Just head over to a bundle instance edit form (node/1/edit) and override
the bundle settings there. ATM this only works for nodes and taxonomy terms.
the bundle settings there.
If you wish for the sitemap to reflect the new configuration instantly, check
'Regenerate sitemap after clicking save'. This setting only appears if a change
......@@ -94,18 +93,6 @@ It is possible to hook into link generation by implementing
hook_simple_sitemap_links_alter(&$links){} in a custom module and altering the
link array.
Need to support a non-core entity type? In case you would like to support an
entity type provided by a contrib module:
* Create a feature request in simple_sitemap's issue queue and submit a plugin
(as patch), or wait until a patch is submitted by the community or a
maintainer.
* As soon as a patch is found working, create an issue in the contrib module's
queue and submit a slightly altered patch created against that module.
* Link the contrib module's issue on the simple_sitemap issue created earlier.
HOW CAN YOU CONTRIBUTE?
-----------------------
......
......@@ -28,7 +28,7 @@ function simple_sitemap_form_alter(&$form, $form_state, $form_id) {
$f = new Form($form, $form_state, $form_id);
// Do not alter the form if it is irrelevant to sitemap generation.
if (empty($f->entityType))
if (empty($f->entityCategory))
return;
$sitemap = \Drupal::service('simple_sitemap.generator');
......@@ -37,7 +37,7 @@ function simple_sitemap_form_alter(&$form, $form_state, $form_id) {
$entity_types = $sitemap->getConfig('entity_types');
// Do not alter the form, if sitemap is disabled for entity type.
if ($f->entityType == 'bundle_instance' && empty($entity_types[$f->entityTypeId][$f->bundleName]['index']))
if ($f->entityCategory == 'instance' && empty($entity_types[$f->entityTypeId][$f->bundleName]['index']))
return;
// Setting default form values.
......@@ -51,10 +51,10 @@ function simple_sitemap_form_alter(&$form, $form_state, $form_id) {
}
// Overwriting defaults if settings found for entity.
if ($f->entityType == 'bundle_instance') {
if (isset($entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->entityId]['index'])) {
$index = $entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->entityId]['index'];
$priority = $entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->entityId]['priority'];
if ($f->entityCategory == 'instance') {
if (isset($entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->instanceId]['index'])) {
$index = $entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->instanceId]['index'];
$priority = $entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->instanceId]['priority'];
}
}
......@@ -62,7 +62,7 @@ function simple_sitemap_form_alter(&$form, $form_state, $form_id) {
'#type' => 'details',
'#group' => isset($form['additional_settings']) ? 'additional_settings' : 'advanced',
'#title' => t('Simple XML sitemap'),
'#description' => $f->entityType == 'bundle_instance' ? t('Settings for this specific entity can be overridden here.') : '',
'#description' => $f->entityCategory == 'instance' ? t('Settings for this specific entity can be overridden here.') : '',
);
// Attach some js magic to forms.
......@@ -76,22 +76,22 @@ function simple_sitemap_form_alter(&$form, $form_state, $form_id) {
'#type' => 'radios',
'#default_value' => $index,
'#options' => [
0 => $f->entityType == 'bundle_instance' ? t('Do not index this entity') : t('Do not index entities of this type'),
1 => $f->entityType == 'bundle_instance' ? t('Index this entity') : t('Index entities of this type'),
0 => $f->entityCategory == 'instance' ? t('Do not index this entity') : t('Do not index entities of this type'),
1 => $f->entityCategory == 'instance' ? t('Index this entity') : t('Index entities of this type'),
]
);
if ($f->entityType == 'bundle_instance' && isset($bundle_index)) {
if ($f->entityCategory == 'instance' && isset($bundle_index)) {
$form['simple_sitemap']['simple_sitemap_index_content']['#options'][$bundle_index] .= ' <em>(' . t('Default') . ')</em>';
}
$form['simple_sitemap']['simple_sitemap_priority'] = array(
'#type' => 'select',
'#title' => t('Priority'),
'#description' => $f->entityType == 'bundle_instance' ? t('The priority this entity will have in the eyes of search engine bots.') : t('The priority entities of this bundle will have in the eyes of search engine bots.'),
'#description' => $f->entityCategory == 'instance' ? t('The priority this entity will have in the eyes of search engine bots.') : t('The priority entities of this bundle will have in the eyes of search engine bots.'),
'#default_value' => $priority,
'#options' => Form::getPrioritySelectValues(),
);
if ($f->entityType == 'bundle_instance' && isset($bundle_priority)) {
if ($f->entityCategory == 'instance' && isset($bundle_priority)) {
$form['simple_sitemap']['simple_sitemap_priority']['#options'][(string)$bundle_priority] .= ' (' . t('Default') . ')';
}
......@@ -139,7 +139,7 @@ function simple_sitemap_entity_form_submit($form, &$form_state) {
// Get current entity type sitemap settings.
$entity_types = $sitemap->getConfig('entity_types');
switch ($f->entityType) {
switch ($f->entityCategory) {
case 'custom':
case 'bundle':
......@@ -148,18 +148,18 @@ function simple_sitemap_entity_form_submit($form, &$form_state) {
$entity_types[$f->entityTypeId][$f->bundleName]['priority'] = $values['simple_sitemap_priority'];
break;
case 'bundle_instance':
$f->entityId = !empty($f->entityId) ? $f->entityId : Form::getNewEntityId($form_state);
case 'instance':
$f->instanceId = !empty($f->instanceId) ? $f->instanceId : Form::getNewEntityId($form_state);
// Delete overrides if they are identical to bundle settings.
if ($values['simple_sitemap_index_content'] == $entity_types[$f->entityTypeId][$f->bundleName]['index']
&& $values['simple_sitemap_priority'] == $entity_types[$f->entityTypeId][$f->bundleName]['priority']) {
unset($entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->entityId]);
unset($entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->instanceId]);
}
// Else save overrides for this entity.
else {
$entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->entityId]['index'] = $values['simple_sitemap_index_content'];
$entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->entityId]['priority'] = $values['simple_sitemap_priority'];
$entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->instanceId]['index'] = $values['simple_sitemap_index_content'];
$entity_types[$f->entityTypeId][$f->bundleName]['entities'][$f->instanceId]['priority'] = $values['simple_sitemap_priority'];
}
break;
}
......
services:
plugin.manager.simple_sitemap:
class: Drupal\simple_sitemap\SimplesitemapManager
parent: default_plugin_manager
simple_sitemap.generator:
class: Drupal\simple_sitemap\Simplesitemap
arguments: ['@config.factory']
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\Annotation\LinkGenerator.
*/
namespace Drupal\simple_sitemap\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a LinkGenerator item annotation object.
*
* @see \Drupal\simple_sitemap\Plugin\SimplesitemapManager
* @see plugin_api
*
* @Annotation
*/
class LinkGenerator extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
}
This diff is collapsed.
......@@ -15,12 +15,11 @@ class Form {
const PRIORITY_HIGHEST = 10;
const PRIORITY_DIVIDER = 10;
public $entityType;
public $entityCategory;
public $entityTypeId;
public $bundleName;
public $entityId;
public $instanceId;
private $plugins;
private $formState;
private $formId;
......@@ -30,37 +29,33 @@ class Form {
function __construct(&$form, $form_state, $form_id) {
$this->formId = $form_id;
$this->formState = $form_state;
$this->entityCategory = NULL;
// Get all simple_sitemap plugins.
$manager = \Drupal::service('plugin.manager.simple_sitemap');
$this->plugins = $manager->getDefinitions();
// First look for a plugin declaring usage of this form, if this fails,
// check if this is a bundle, or bundle instance form and gather the form
// entity's sitemap settings from the database.
if (!$this->getEntityDataFromCustomPlugin()) {
$this->getEntityDataFromForm();
if (!$this->getEntityDataFromFormId()) {
$this->getEntityDataFromFormEntity();
}
}
/**
* Checks if a plugin defines this form to be used for its entity settings and
* sets the entity settings and collects those settings.
* This is a temporary solution to be able to set sitemap settings
* for all 'user' entities on the form 'user_admin_settings', as there is no
* general setting page where non-bundle entity types can be set up.
*
* @return bool
* TRUE if there is a plugin declaring usage of this form, FALSE otherwise.
* @todo: Remove this and create a general setting page for all entity types.
*/
private function getEntityDataFromCustomPlugin() {
foreach($this->plugins as $plugin) {
if (isset($plugin['form_id']) && $plugin['form_id'] === $this->formId) {
$this->entityType = 'custom';
$this->entityTypeId = $plugin['id'];
$this->bundleName = $plugin['id'];
private function getEntityDataFromFormId() {
switch ($this->formId) {
case 'user_admin_settings':
$this->entityCategory = 'bundle';
$this->entityTypeId = 'user';
$this->bundleName = 'user';
$this->instanceId = NULL;
return TRUE;
}
default:
return FALSE;
}
return FALSE;
}
/**
......@@ -70,29 +65,51 @@ class Form {
* @return bool
* TRUE if this is a bundle or bundle instance form, FALSE otherwise.
*/
private function getEntityDataFromForm() {
private function getEntityDataFromFormEntity() {
$form_entity = $this->getFormEntity();
if ($form_entity !== FALSE) {
$entity_type = $form_entity->getEntityType();
if (!empty($entity_type->getBundleEntityType())) {
$this->entityType = 'bundle_instance';
$this->entityTypeId = $entity_type->getBundleEntityType();
$this->bundleName = $form_entity->bundle();
$this->entityId = $form_entity->id();
return TRUE;
$form_entity_type = $form_entity->getEntityType();
$entity_type_id = $form_entity->getEntityTypeId(); //todo: Change to $form_entity_type->id()?
$sitemap_entity_types = Simplesitemap::getSitemapEntityTypes();
$bundle_entity_type = $form_entity_type->getBundleEntityType();
$entity_bundle = $form_entity->bundle();
if (isset($sitemap_entity_types[$entity_type_id])) {
$this->entityCategory = 'instance';
}
else {
$entity_type_id = $form_entity->getEntityTypeId();
if (isset($this->plugins[$entity_type_id])) {
if (!isset($this->plugins[$entity_type_id]['form_id'])
|| $this->plugins[$entity_type_id]['form_id'] === $this->formId) {
$this->entityType = 'bundle';
$this->entityTypeId = $entity_type_id;
$this->bundleName = $form_entity->id();
return TRUE;
foreach ($sitemap_entity_types as $sitemap_entity) {
if ($sitemap_entity->getBundleEntityType() == $entity_type_id) {
$this->entityCategory = 'bundle';
break;
}
}
}
// Menu fixes.
if (is_null($this->entityCategory) && $entity_type_id == 'menu') {
$this->entityCategory = 'bundle';
}
if ($entity_type_id == 'menu_link_content') {
$bundle_entity_type = 'menu';
}
switch ($this->entityCategory) {
case 'bundle':
$this->entityTypeId = $form_entity->getEntityTypeId();
$this->bundleName = $form_entity->id();
$this->instanceId = NULL;
break;
case 'instance':
$this->entityTypeId = !empty($bundle_entity_type) ? $bundle_entity_type : $entity_bundle;
$this->bundleName = $entity_bundle == 'menu_link_content' && method_exists($form_entity, 'getMenuName') ? $form_entity->getMenuName() : $entity_bundle; // menu fix
$this->instanceId = $form_entity->id();
break;
default:
return FALSE;
}
return TRUE;
}
return FALSE;
}
......
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\LinkGeneratorBase.
*/
namespace Drupal\simple_sitemap;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A link generator plugin base.
*/
abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInterface, ContainerFactoryPluginInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* LinkGeneratorBase constructor.
*
* @param array $configuration
* The plugin configuration
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Core\Database\Connection $database
* The database connection.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('database'));
}
}
<?php
/**
* @file
* Contains Drupal\simple_sitemap\LinkGeneratorInterface.
*/
namespace Drupal\simple_sitemap;
use Drupal\Component\Plugin\PluginInspectionInterface;
/**
* Defines an interface for simple_sitemap plugins.
*/
interface LinkGeneratorInterface extends PluginInspectionInterface {
/**
* Get metadata about the entities that the link generator is providing.
*
* @return array
* An array of information about fields provided by the query.
*/
public function getQueryInfo();
/**
* Get a non-executed query for entities of a specific bundle type.
*
* @param string $bundle
* The bundle to query for.
*
* @return \Drupal\Core\Database\Query\SelectInterface
* A query ready for execution.
*/
public function getQuery($bundle);
}
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator\CommerceProductType.
*
* Plugin for commerce product entity link generation.
*/
namespace Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator;
use Drupal\simple_sitemap\LinkGeneratorBase;
/**
* CommerceProductType class.
*
* @LinkGenerator(
* id = "commerce_product_type",
* entity_type_name = "commerce_product"
* )
*/
class CommerceProductType extends LinkGeneratorBase {
/**
* {@inheritdoc}
*/
public function getQueryInfo() {
return array(
'field_info' => array(
'entity_id' => 'product_id',
'lastmod' => 'changed',
),
);
}
/**
* {@inheritdoc}
*/
public function getQuery($bundle) {
return $this->database->select('commerce_product_field_data', 'p')
->fields('p', array('product_id', 'changed'))
->condition('type', $bundle)
->condition('status', 1);
}
}
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator\Menu.
*
* Plugin for menu entity link generation.
*/
namespace Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator;
use Drupal\simple_sitemap\LinkGeneratorBase;
/**
* Menu class.
*
* @LinkGenerator(
* id = "menu"
* )
*/
class Menu extends LinkGeneratorBase {
/**
* {@inheritdoc}
*/
function getQueryInfo() {
return array(
'field_info' => array(
'entity_id' => 'mlid',
'route_name' => 'route_name',
'route_parameters' => 'route_parameters',
'options' => 'options',
),
);
}
/**
* {@inheritdoc}
*/
public function getQuery($bundle) {
return $this->database->select('menu_tree', 'm')
->fields('m', array('mlid', 'route_name', 'route_parameters', 'options'))
->condition('menu_name', $bundle)
->condition('enabled', 1)
->condition('route_name', '', '!=');
}
}
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator\NodeType.
*
* Plugin for node entity link generation.
*/
namespace Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator;
use Drupal\simple_sitemap\LinkGeneratorBase;
/**
* NodeType class.
*
* @LinkGenerator(
* id = "node_type",
* entity_type_name = "node"
* )
*/
class NodeType extends LinkGeneratorBase {
/**
* {@inheritdoc}
*/
public function getQueryInfo() {
return array(
'field_info' => array(
'entity_id' => 'nid',
'lastmod' => 'changed',
),
);
}
/**
* {@inheritdoc}
*/
public function getQuery($bundle) {
return $this->database->select('node_field_data', 'n')
->fields('n', array('nid', 'changed'))
->condition('type', $bundle)
->condition('status', 1);
}
}
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator\TaxonomyVocabulary.
*
* Plugin for taxonomy term entity link generation.
*/
namespace Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator;
use Drupal\simple_sitemap\LinkGeneratorBase;
/**
* TaxonomyVocabulary class.
*
* @LinkGenerator(
* id = "taxonomy_vocabulary",
* entity_type_name = "taxonomy_term"
* )
*/
class TaxonomyVocabulary extends LinkGeneratorBase {
/**
* {@inheritdoc}
*/
public function getQueryInfo() {
return array(
'field_info' => array(
'entity_id' => 'tid',
'lastmod' => 'changed',
),
);
}
/**
* {@inheritdoc}
*/
public function getQuery($bundle) {
return $this->database->select('taxonomy_term_field_data', 't')
->fields('t', array('tid', 'changed'))
->condition('vid', $bundle);
}
}
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator\User.
*
* Plugin for user link generation.
*/
namespace Drupal\simple_sitemap\Plugin\simple_sitemap\LinkGenerator;
use Drupal\simple_sitemap\LinkGeneratorBase;
/**
* User class.
*
* @LinkGenerator(
* id = "user",
* entity_type_name = "user",
* form_id = "user_admin_settings"
* )
*/
class User extends LinkGeneratorBase {
/**
* {@inheritdoc}
*/
public function getQueryInfo() {
return array(
'field_info' => array(
'entity_id' => 'uid',
'lastmod' => 'changed',
),
);
}
/**
* {@inheritdoc}
*/
public function getQuery($bundle) {
return $this->database->select('users_field_data', 'u')
->fields('u', array('uid', 'changed'))
->condition('status', 1);
}
}
......@@ -7,6 +7,7 @@
namespace Drupal\simple_sitemap;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
/**
* Simplesitemap class.
......@@ -157,4 +158,22 @@ class Simplesitemap {
public static function getDefaultLangId() {
return \Drupal::languageManager()->getDefaultLanguage()->getId();
}
/**
* Returns objects of entity types that can be indexed by the sitemap.
*
* @return array
* Objects of entity types that can be indexed by the sitemap.
*/
public static function getSitemapEntityTypes() {
$entity_types = \Drupal::entityTypeManager()->getDefinitions();
foreach ($entity_types as $entity_type_id => $entity_type) {
if (!$entity_type instanceof ContentEntityTypeInterface || !method_exists($entity_type, 'getBundleEntityType')) {
unset($entity_types[$entity_type_id]);
continue;
}
}
return $entity_types;
}
}
<?php
/**
* @file
* Contains \Drupal\simple_sitemap\SimplesitemapManager.
*/
namespace Drupal\simple_sitemap;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Simplesitemap plugin manager.
*/
class SimplesitemapManager extends DefaultPluginManager {
/**
* Constructs an SimplesitemapManager object.
*
* @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 to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/simple_sitemap/LinkGenerator', $namespaces, $module_handler, 'Drupal\simple_sitemap\LinkGeneratorInterface', 'Drupal\simple_sitemap\Annotation\LinkGenerator');
$this->alterInfo('simple_sitemap_link_generators_info');
$this->setCacheBackend($cache_backend, 'simple_sitemap_link_generators');
}
}
......@@ -65,24 +65,31 @@ class SitemapGenerator {
* @return array $operations.
*/
private function batchAddEntityTypePaths() {
$operations = [];
$manager = \Drupal::service('plugin.manager.simple_sitemap');
$plugins = $manager->getDefinitions();
$operations = array();
// Menu fix.
if (isset($this->entityTypes['menu'])) {
$this->entityTypes['menu_link_content'] = $this->entityTypes['menu'];
unset ($this->entityTypes['menu']);
}
// Let all simple_sitemap plugins add their links to the sitemap.
foreach ($plugins as $link_generator_plugin) {
if (isset($this->entityTypes[$link_generator_plugin['id']])) {
$instance = $manager->createInstance($link_generator_plugin['id']);
foreach($this->entityTypes[$link_generator_plugin['id']] as $bundle => $bundle_settings) {
$entity_types = Simplesitemap::getSitemapEntityTypes();
foreach($entity_types as $entity_type_name => $entity_type) {
$bundle_entity_type_name = !empty($entity_type->getBundleEntityType()) ? $entity_type->getBundleEntityType() : $entity_type->id();
if (isset($this->entityTypes[$bundle_entity_type_name])) {
foreach($this->entityTypes[$bundle_entity_type_name] as $bundle_name => $bundle_settings) {
if ($bundle_settings['index']) {
$operation['query']['query'] = $instance->getQuery($bundle);
$operation['query']['field_info'] = $instance->getQueryInfo()['field_info'];
$operation['entity_info']['bundle_settings'] = $bundle_settings;
$operation['entity_info']['bundle_name'] = $bundle;
$operation['entity_info']['bundle_entity_type'] = $link_generator_plugin['id'];
$operation['entity_info']['entity_type_name'] = !empty