SimpleSitemapViews.php in Simple XML sitemap 4.x
Same filename and directory in other branches
Namespace
Drupal\simple_sitemap_viewsFile
modules/simple_sitemap_views/src/SimpleSitemapViews.phpView source
<?php
namespace Drupal\simple_sitemap_views;
use Drupal\simple_sitemap\Entity\SimpleSitemap;
use Drupal\simple_sitemap_views\Plugin\views\display_extender\SimpleSitemapDisplayExtender;
use Drupal\Core\Database\Query\ConditionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\views\ViewEntityInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Database\Database;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
/**
* Class to manage sitemap data for views.
*/
class SimpleSitemapViews {
/**
* Separator between arguments.
*/
public const ARGUMENT_SEPARATOR = '/';
/**
* Views display extender plugin ID.
*/
protected const PLUGIN_ID = 'simple_sitemap_display_extender';
/**
* View entities storage.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
*/
protected $viewStorage;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The queue factory.
*
* @var \Drupal\Core\Queue\QueueFactory
*/
protected $queueFactory;
/**
* The current active database's master connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* SimpleSitemapViews constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Queue\QueueFactory $queue_factory
* 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(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, QueueFactory $queue_factory, Connection $database) {
$this->viewStorage = $entity_type_manager
->getStorage('view');
$this->configFactory = $config_factory;
$this->queueFactory = $queue_factory;
$this->database = $database;
}
/**
* Checks that views support is enabled.
*
* @return bool
* Returns TRUE if support is enabled, and FALSE otherwise.
*/
public function isEnabled() : bool {
// Support enabled when views display extender is enabled.
$enabled = Views::getEnabledDisplayExtenders();
return isset($enabled[self::PLUGIN_ID]);
}
/**
* Enables sitemap support for views.
*/
public function enable() : void {
$config = $this->configFactory
->getEditable('views.settings');
$display_extenders = $config
->get('display_extenders') ?: [];
// Enable views display extender plugin.
$display_extenders[self::PLUGIN_ID] = self::PLUGIN_ID;
$config
->set('display_extenders', $display_extenders);
$config
->save();
}
/**
* Disables sitemap support for views.
*/
public function disable() : void {
$config = $this->configFactory
->getEditable('views.settings');
$display_extenders = $config
->get('display_extenders') ?: [];
// Disable views display extender plugin.
unset($display_extenders[self::PLUGIN_ID]);
$config
->set('display_extenders', $display_extenders);
$config
->save();
// Clear the table with indexed arguments.
// Clear the garbage collection queue.
$this
->removeArgumentsFromIndex();
$queue = $this->queueFactory
->get('simple_sitemap.views.garbage_collector');
$queue
->deleteQueue();
}
/**
* Gets the display extender.
*
* @param \Drupal\views\ViewExecutable $view
* A view executable instance.
* @param string|null $display_id
* The display id. If empty uses the current display.
*
* @return \Drupal\simple_sitemap_views\Plugin\views\display_extender\SimpleSitemapDisplayExtender|null
* The display extender.
*/
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 = $extenders[self::PLUGIN_ID] ?? NULL;
if ($extender instanceof SimpleSitemapDisplayExtender) {
return $extender;
}
return NULL;
}
/**
* Gets the sitemap settings for view display.
*
* @param \Drupal\views\ViewExecutable $view
* A view executable instance.
* @param string $variant
* The name of the sitemap variant.
* @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, string $variant, ?string $display_id = NULL) : ?array {
$extender = $this
->getDisplayExtender($view, $display_id);
// Retrieve the sitemap settings from the extender.
if ($extender && $extender
->hasSitemapSettings()) {
$settings = $extender
->getSitemapSettings($variant);
if ($settings['index']) {
return $settings;
}
}
return NULL;
}
/**
* Gets the indexable arguments for view display.
*
* @param \Drupal\views\ViewExecutable $view
* A view executable instance.
* @param string $variant
* The name of the sitemap variant.
* @param string|null $display_id
* The display id. If empty uses the current display.
*
* @return array
* Indexable arguments identifiers.
*/
public function getIndexableArguments(ViewExecutable $view, string $variant, ?string $display_id = NULL) : array {
$settings = $this
->getSitemapSettings($view, $variant, $display_id);
$indexable_arguments = [];
// Find indexable arguments.
if ($settings) {
$arguments = array_keys($view->display_handler
->getHandlers('argument'));
$bits = explode('/', $view
->getPath());
$arg_index = 0;
// Required arguments.
foreach ($bits as $bit) {
if ($bit == '%' || strpos($bit, '%') === 0) {
$indexable_arguments[] = isset($arguments[$arg_index]) ? $arguments[$arg_index] : $bit;
$arg_index++;
}
}
if (!empty($settings['arguments'])) {
if ($arg_index > 0) {
$arguments = array_slice($arguments, $arg_index);
}
// Optional arguments.
foreach ($arguments as $argument_id) {
if (empty($settings['arguments'][$argument_id])) {
break;
}
$indexable_arguments[] = $argument_id;
}
}
}
return $indexable_arguments;
}
/**
* Adds view arguments to the index.
*
* @param \Drupal\views\ViewExecutable $view
* A view executable instance.
* @param array $args
* Array of arguments to add to the index.
* @param string|null $display_id
* The display id. If empty uses the current display.
*
* @return bool
* TRUE if the arguments are added to the index, FALSE otherwise.
*
* @throws \Exception
*/
public function addArgumentsToIndex(ViewExecutable $view, array $args, ?string $display_id = NULL) : bool {
foreach (SimpleSitemap::loadMultiple() as $variant) {
if ($this
->addArgumentsToIndexByVariant($view, $variant
->id(), $args, $display_id)) {
return TRUE;
}
}
return FALSE;
}
/**
* Adds view arguments to the index by the sitemap variant.
*
* @param \Drupal\views\ViewExecutable $view
* A view executable instance.
* @param string $variant
* The name of the sitemap variant.
* @param array $args
* Array of arguments to add to the index.
* @param string|null $display_id
* The display id. If empty uses the current display.
*
* @return bool
* TRUE if the arguments are added to the index, FALSE otherwise.
*
* @throws \Exception
*/
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)) {
return FALSE;
}
// Check that indexing of at least one argument is enabled.
$indexable_arguments = $this
->getIndexableArguments($view, $variant);
if (empty($indexable_arguments)) {
return FALSE;
}
// 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)) {
return FALSE;
}
// Check that the current number of rows in the index does not
// exceed the specified number.
$condition = Database::getConnection()
->condition('AND');
$condition
->condition('view_id', $view
->id());
$condition
->condition('display_id', $view->current_display);
$settings = $this
->getSitemapSettings($view, $variant);
$max_links = is_numeric($settings['max_links']) ? $settings['max_links'] : 0;
if ($max_links > 0 && $this
->getArgumentsFromIndexCount($condition) >= $max_links) {
return FALSE;
}
// Convert the set of identifiers and a set of values to string.
$args_ids = $this
->convertArgumentsArrayToString($args_ids);
$args_values = $this
->convertArgumentsArrayToString($args);
$condition
->condition('arguments_ids', $args_ids);
$condition
->condition('arguments_values', $args_values);
// Check that this set of arguments has not yet been indexed.
if ($this
->getArgumentsFromIndexCount($condition)) {
return FALSE;
}
// Check that the view result is not empty for this set of arguments.
$params = array_merge([
$view
->id(),
$view->current_display,
], $args);
$view_result = call_user_func_array('views_get_view_result', $params);
if (empty($view_result)) {
return FALSE;
}
// Add a set of arguments to the index.
$options = [
'return' => Database::RETURN_AFFECTED,
];
$query = $this->database
->insert('simple_sitemap_views', $options);
$query
->fields([
'view_id' => $view
->id(),
'display_id' => $view->current_display,
'arguments_ids' => $args_ids,
'arguments_values' => $args_values,
]);
return (bool) $query
->execute();
}
/**
* Get arguments from index.
*
* @param \Drupal\Core\Database\Query\ConditionInterface|null $condition
* The query conditions.
* @param int|null $limit
* The number of records to return from the result set. If NULL, returns
* all records.
* @param bool $convert
* Defaults to FALSE. If TRUE, the argument string will be converted
* to an array.
*
* @return array
* An array with information about the indexed arguments.
*/
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 ($condition !== NULL) {
$query
->condition($condition);
}
if ($limit !== NULL) {
$query
->range(0, $limit);
}
$rows = $query
->execute()
->fetchAll();
$arguments = [];
foreach ($rows as $row) {
$arguments[$row->id] = [
'view_id' => $row->view_id,
'display_id' => $row->display_id,
'arguments' => $convert ? $this
->convertArgumentsStringToArray($row->arguments) : $row->arguments,
];
}
return $arguments;
}
/**
* Get the number of rows in the index.
*
* @param \Drupal\Core\Database\Query\ConditionInterface|null $condition
* The query conditions.
*
* @return int
* The number of rows.
*/
public function getArgumentsFromIndexCount(?ConditionInterface $condition = NULL) : int {
$query = $this->database
->select('simple_sitemap_views', 'ssv');
if ($condition !== NULL) {
$query
->condition($condition);
}
return $query
->countQuery()
->execute()
->fetchField();
}
/**
* Returns the ID of the record in the index for the specified position.
*
* @param int $position
* Position of the record.
* @param \Drupal\Core\Database\Query\ConditionInterface|null $condition
* The query conditions.
*
* @return int|bool
* The ID of the record, or FALSE if there is no specified position.
*/
public function getIndexIdByPosition(int $position, ?ConditionInterface $condition = NULL) {
$query = $this->database
->select('simple_sitemap_views', 'ssv');
$query
->addField('ssv', 'id');
if ($condition !== NULL) {
$query
->condition($condition);
}
$query
->orderBy('id');
$query
->range($position - 1, 1);
return $query
->execute()
->fetchField();
}
/**
* Remove arguments from index.
*
* @param \Drupal\Core\Database\Query\ConditionInterface|null $condition
* The query conditions.
*/
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');
}
else {
// Otherwise, use the DELETE query.
$query = $this->database
->delete('simple_sitemap_views');
$query
->condition($condition);
}
$query
->execute();
}
/**
* Returns an array of view displays that use the route.
*
* @param \Drupal\views\ViewEntityInterface $view_entity
* The config entity in which the view is stored.
*
* @return array
* Array of display identifiers.
*/
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, TRUE);
};
return array_keys(array_filter($view_entity
->get('display'), $filter_callback));
}
/**
* Returns an array of executable views whose current display is indexable.
*
* @return \Drupal\views\ViewExecutable[]
* An array of ViewExecutable instances.
*/
public function getIndexableViews() : array {
// Check that views support is enabled.
if (!$this
->isEnabled()) {
return [];
}
// Load views with display plugins that use the route.
$query = $this->viewStorage
->getQuery();
$query
->condition('status', TRUE);
$query
->condition("display.*.display_plugin", $this
->getRouterDisplayPluginIds(), 'IN');
$view_ids = $query
->execute();
// If there are no such views, then return an empty array.
if (empty($view_ids)) {
return [];
}
$indexable_views = [];
/** @var \Drupal\views\ViewEntityInterface $view_entity */
foreach ($this->viewStorage
->loadMultiple($view_ids) as $view_entity) {
foreach ($this
->getRouterDisplayIds($view_entity) as $display_id) {
$view = Views::executableFactory()
->get($view_entity);
// Ensure the display was correctly set.
if (!$view
->setDisplay($display_id)) {
$view
->destroy();
continue;
}
// Check that the display is enabled and indexed.
if ($view->display_handler
->isEnabled() && $this
->getIndexableVariants($view)) {
$indexable_views[] = $view;
}
}
}
return $indexable_views;
}
/**
* Returns an array of indexable sitemap variants 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.
*/
public function getIndexableVariants(ViewExecutable $view, ?string $display_id = NULL) : array {
// Ensure the display was correctly set.
if (!$view
->setDisplay($display_id)) {
return [];
}
$variants = SimpleSitemap::loadMultiple();
foreach ($variants as $variant_id => $variant) {
if (!$this
->getSitemapSettings($view, $variant_id)) {
unset($variants[$variant_id]);
}
}
return $variants;
}
/**
* Creates tasks in the garbage collection queue.
*/
public function executeGarbageCollection() {
// The task queue of garbage collection in the arguments index.
$queue = $this->queueFactory
->get('simple_sitemap.views.garbage_collector');
// Check that the queue is empty.
if ($queue
->numberOfItems()) {
return;
}
// Get identifiers of indexed views.
$query = $this->database
->select('simple_sitemap_views', 'ssv');
$query
->addField('ssv', 'view_id');
$query
->distinct();
$result = $query
->execute()
->fetchCol();
// Create a garbage collection tasks.
foreach ($result as $view_id) {
$queue
->createItem([
'view_id' => $view_id,
]);
}
}
/**
* Get variations for string representation of arguments.
*
* @param array $args
* Array of arguments.
*
* @return array
* Array of variations of the string representation of arguments.
*/
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;
}
/**
* Converts an array of arguments to a string.
*
* @param array $args
* Array of arguments to convert.
*
* @return string
* A string representation of the arguments.
*/
protected function convertArgumentsArrayToString(array $args) : string {
return implode(self::ARGUMENT_SEPARATOR, $args);
}
/**
* Converts a string with arguments to an array.
*
* @param string $args
* A string representation of the arguments to convert.
*
* @return array
* Array of arguments.
*/
protected function convertArgumentsStringToArray($args) : array {
return explode(self::ARGUMENT_SEPARATOR, $args);
}
/**
* Get all display plugins that use the route.
*
* @return array
* An array with plugin identifiers.
*/
protected function getRouterDisplayPluginIds() : array {
static $plugin_ids = [];
if (empty($plugin_ids)) {
$display_plugins = Views::pluginManager('display')
->getDefinitions();
// Get all display plugins that use the route.
foreach ($display_plugins as $plugin_id => $definition) {
if (!empty($definition['uses_route'])) {
$plugin_ids[$plugin_id] = $plugin_id;
}
}
}
return $plugin_ids;
}
}
Classes
Name | Description |
---|---|
SimpleSitemapViews | Class to manage sitemap data for views. |