Commit 6dd60103 authored by gbyte.co's avatar gbyte.co Committed by Pawel G

Further plugins, improving the handling of custom paths, adding some more...

Further plugins, improving the handling of custom paths, adding some more error handling, various fixes.
parent ec73ef16
administer sitemap settings: administer sitemap settings:
title: 'Administer sitemap settings' title: 'Administer sitemap settings'
description: 'Administer the sitemap settings and generate sitemap on demand.' description: 'Administer the sitemap settings, alter inclusion and priority of content and generate the sitemap on demand.'
restrict access: false
add custom links:
title: 'Add custom links'
description: 'Add any Drupal internal paths to the xml sitemap.'
restrict access: false restrict access: false
...@@ -26,4 +26,4 @@ simplesitemap.settings_custom: ...@@ -26,4 +26,4 @@ simplesitemap.settings_custom:
_form: '\Drupal\simplesitemap\Form\SimplesitemapCustomLinksForm' _form: '\Drupal\simplesitemap\Form\SimplesitemapCustomLinksForm'
_title: 'Simple XML Sitemap Settings' _title: 'Simple XML Sitemap Settings'
requirements: requirements:
_permission: 'add custom links' _permission: 'administer sitemap settings'
...@@ -8,14 +8,10 @@ ...@@ -8,14 +8,10 @@
namespace Drupal\simplesitemap; namespace Drupal\simplesitemap;
use Drupal\Core\Url;
use \Drupal\user\Entity\User;
/** /**
* CustomLinkGenerator class. * CustomLinkGenerator class.
*/ */
class CustomLinkGenerator { class CustomLinkGenerator extends LinkGeneratorBase {
const ANONYMOUS_USER_ID = 0;
/** /**
* Returns an array of all urls of the custom paths. * Returns an array of all urls of the custom paths.
...@@ -25,18 +21,13 @@ class CustomLinkGenerator { ...@@ -25,18 +21,13 @@ class CustomLinkGenerator {
* @return array $urls * @return array $urls
* *
*/ */
public function get_custom_paths($custom_paths) { public function get_paths($custom_paths) {
$anonymous_account = User::load(self::ANONYMOUS_USER_ID);
$paths = array(); $paths = array();
foreach($custom_paths as $i => $custom_path) { foreach($custom_paths as $i => $custom_path) {
if (!isset($custom_path['index']) || $custom_path['index']) { if (FALSE !== $path_data = $this->get_multilang_urls_from_user_input($custom_path['path'])) {
$url_object = Url::fromUserInput($custom_path['path'], array()); $paths[$i]['path_data'] = $path_data;
if ($url_object->access($anonymous_account)) { $paths[$i]['priority'] = isset($custom_path['priority']) ? $custom_path['priority'] : NULL;
$paths[$i]['path'] = $url_object->getInternalPath(); $paths[$i]['lastmod'] = NULL; //todo: implement lastmod
$paths[$i]['options'] = $url_object->getOptions();
$paths[$i]['priority'] = isset($custom_path['priority']) ? $custom_path['priority'] : NULL;
$paths[$i]['lastmod'] = NULL; //todo: implement lastmod
}
} }
} }
return $paths; return $paths;
......
...@@ -8,52 +8,87 @@ namespace Drupal\simplesitemap; ...@@ -8,52 +8,87 @@ namespace Drupal\simplesitemap;
use Drupal\Component\Plugin\PluginBase; use Drupal\Component\Plugin\PluginBase;
use \Drupal\user\Entity\User; use \Drupal\user\Entity\User;
use Drupal\Core\Url;
abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInterface { abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInterface {
private $entity_paths = array(); private $entity_paths = array();
private $current_entity_type; private $current_entity_type;
private $anonymous_account; private $anonymous_account;
protected $languages;
protected $default_language_id;
const PLUGIN_ERROR_MESSAGE = "The simplesitemap @plugin plugin has been omitted, as it does not return the required numeric array of path data sets. Each data sets must contain the required path element and optionally other elements, like lastmod."; const PLUGIN_ERROR_MESSAGE = "The simplesitemap @plugin plugin has been omitted, as it does not return the required numeric array of path data sets. Each data sets must contain the required path element (relative path string or Drupal\\Core\\Url object) and optionally other elements, like lastmod.";
const PATH_DOES_NOT_EXIST = "The path @faulty_path has been omitted from the XML sitemap, as it does not exist.";
const ANONYMOUS_USER_ID = 0; const ANONYMOUS_USER_ID = 0;
function __construct() {
$this->anonymous_account = User::load(self::ANONYMOUS_USER_ID);
$this->languages = \Drupal::languageManager()->getLanguages();
$this->default_language_id = \Drupal::languageManager()->getDefaultLanguage()->getId();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function get_entity_paths($entity_type, $bundles) { public function get_entity_paths($entity_type, $bundles) {
$this->current_entity_type = $entity_type; $this->current_entity_type = $entity_type;
$this->anonymous_account = User::load(self::ANONYMOUS_USER_ID);
$i = 0; $i = 0;
foreach($bundles as $bundle => $bundle_settings) { foreach($bundles as $bundle => $bundle_settings) {
if (!$bundle_settings['index']) if (!$bundle_settings['index'])
continue; continue;
$paths = $this->get_entity_bundle_paths($bundle); $paths = $this->get_paths($bundle);
if (!is_array($paths)) { // Some error catching. if (!is_array($paths)) { // Some error catching.
$this->register_error(self::PLUGIN_ERROR_MESSAGE); $this->register_error(self::PLUGIN_ERROR_MESSAGE, array('@plugin' => $this->current_entity_type));
return $this->entity_paths; return $this->entity_paths;
} }
foreach($paths as $id => $path) { foreach($paths as $id => $path) {
if (!isset($path['path']) || !is_string($path['path'])) { // Error catching; careful, path can be empty.
$this->register_error(self::PLUGIN_ERROR_MESSAGE); if (isset($path['path_data']) && (isset($path['path_data']['path']) || !$path['path_data'])) {
if ($path['path_data'] !== FALSE) {
// If URLs have not been created yet by the plugin, use the 'path' to create the URLs now.
if (empty($path['path_data']['urls'])) {
$path['path_data'] = $this->get_multilang_urls_from_user_input($path['path_data']['path']);
}
}
// If the plugin provided path does not exist, skip this path.
if (!$path['path_data'])
continue;
}
// If the returned data from the plugin is wrong, skip the plugin and register an error.
else {
$this->register_error(self::PLUGIN_ERROR_MESSAGE, array('@plugin' => $this->current_entity_type));
return $this->entity_paths; return $this->entity_paths;
} }
// Adding path, its options and the resulting URLs returned by the plugin.
$this->entity_paths[$i]['path_data'] = $path['path_data'];
// Adding priority if returned by the plugin.
$this->entity_paths[$i]['priority'] = !empty($path['priority'])
? $path['priority'] : $bundle_settings['priority'];
$this->entity_paths[$i]['path'] = $path['path']; // Adding lastmod if returned by the plugin.
$this->entity_paths[$i]['options'] = !empty($path['options']) ? $path['options'] : array(); $this->entity_paths[$i]['lastmod'] = isset($path['lastmod']) && is_numeric($path['lastmod'])
$this->entity_paths[$i]['priority'] = !empty($path['priority']) ? $path['priority'] : $bundle_settings['priority']; ? date_iso8601($path['lastmod']) : NULL;
$this->entity_paths[$i]['lastmod'] = isset($path['lastmod']) && is_numeric($path['lastmod']) ? date_iso8601($path['lastmod']) : NULL;
$i++; $i++;
} }
} }
return $this->entity_paths; return $this->entity_paths;
} }
private function register_error($message) { /**
$message = str_replace('@plugin', $this->current_entity_type, t($message)); * Logs and displays an error.
*
* @param $message
* Untranslated message.
* @param array $substitutions (optional)
* Substitutions (placeholder => substitution) which will replace placeholders
* with strings.
*/
protected function register_error($message, $substitutions = array()) {
$message = strtr(t($message), $substitutions);
\Drupal::logger('simplesitemap')->notice($message); \Drupal::logger('simplesitemap')->notice($message);
drupal_set_message($message, 'error'); drupal_set_message($message, 'error');
} }
...@@ -65,22 +100,112 @@ abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInte ...@@ -65,22 +100,112 @@ abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInte
* Machine name of the bundle, eg. 'page'. * Machine name of the bundle, eg. 'page'.
* *
* @return array $paths * @return array $paths
* A numeric array of Drupal internal path data sets containing the path and * A numeric array of Drupal internal path data sets containing the internal
* optionally lastmod and link options: * path, url objects for every language (optional), lastmod (optional) and
* array( * priority (optional):
* 'path' => 'drupal/internal/path/to/content', // required *
* 'options' => array( // optional * array(
* 'query' => array('page' => '1'), * 0 => array(
* 'fragment' => 'my-anchor-id', * 'path_data' => array(
* 'path' => '/relative/path/to/drupal/page',
* 'urls' => array( //optional
* 'en' => Drupal\Core\Url,
* 'de' => Drupal\Core\Url,
* ),
* ), * ),
* 'priority' => 0.5 // optional
* 'lastmod' => '1234567890' // optional: content changed unix date * 'lastmod' => '1234567890' // optional: content changed unix date
* ) * ),
* )
* *
* @abstract * @abstract
*/ */
abstract function get_entity_bundle_paths($bundle); abstract function get_paths($bundle);
/**
* Checks if anonymous users have access to a given path.
*
* @param \Drupal\Core\Url object
*
* @return bool
* TRUE if anonymous users have access to path, FALSE if they do not.
*/
protected function access($url_object) { protected function access($url_object) {
return $url_object->access($this->anonymous_account); //todo: Add error checking. return $url_object->access($this->anonymous_account); //todo: Add error checking.
} }
/**
* Wrapper function for Drupal\Core\Url::fromRoute.
* Returns url data for every language.
*
* @param $route_name
* @param $route_parameters
* @param $options
* @see https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Url.php/function/Url%3A%3AfromRoute/8
*
* @return array
* Returns an array containing the internal path, url objects for every language,
* url options and access information.
*/
protected function get_multilang_urls_from_route($route_name, $route_parameters = array(), $options = array()) {
$options['absolute'] = empty($options['absolute']) ? TRUE : $options['absolute'];
$url_object = Url::fromRoute($route_name, $route_parameters, $options);
$urls = array();
foreach($this->languages as $language) {
if ($language->getId() === $this->default_language_id) {
$urls[$this->default_language_id] = $url_object->toString();
}
else {
$options['language'] = $language;
$urls[$language->getId()] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
}
}
return array(
'path' => $url_object->getInternalPath(),
'urls' => $urls,
'options' => $url_object->getOptions(),
'access' => $this->access($url_object),
);
}
/**
* Wrapper function for Drupal\Core\Url::fromUserInput.
* Returns url data for every language.
*
* @param $user_input
* @param $options
* @see https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Url.php/function/Url%3A%3AfromUserInput/8
*
* @return array or FALSE
* Returns an array containing the internal path, url objects for every language,
* url options and access information. Returns FALSE if path does not exist.
*/
protected function get_multilang_urls_from_user_input($user_input, $options = array()) {
$user_input = $user_input[0] === '/' ? $user_input : '/' . $user_input;
if (!\Drupal::service('path.validator')->isValid($user_input)) {
$this->register_error(self::PATH_DOES_NOT_EXIST, array('@faulty_path' => $user_input));
return FALSE;
}
$options['absolute'] = empty($options['absolute']) ? TRUE : $options['absolute'];
$url_object = Url::fromUserInput($user_input, $options);
$urls = array();
foreach($this->languages as $language) {
if ($language->getId() === $this->default_language_id) {
$urls[$this->default_language_id] = $url_object->toString();
}
else {
$options['language'] = $language;
$urls[$language->getId()] = Url::fromUserInput($user_input, $options)->toString();
}
}
return array(
'path' => $url_object->getInternalPath(),
'urls' => $urls,
'options' => $url_object->getOptions(),
'access' => $this->access($url_object),
);
}
} }
...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator; ...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator; use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase; use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/** /**
* Menu class. * Menu class.
...@@ -24,7 +23,7 @@ class Menu extends LinkGeneratorBase { ...@@ -24,7 +23,7 @@ class Menu extends LinkGeneratorBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
function get_entity_bundle_paths($bundle) { function get_paths($bundle) {
$routes = db_query("SELECT mlid, route_name, route_parameters, options FROM {menu_tree} WHERE menu_name = :menu_name and enabled = 1", array(':menu_name' => $bundle)) $routes = db_query("SELECT mlid, route_name, route_parameters, options FROM {menu_tree} WHERE menu_name = :menu_name and enabled = 1", array(':menu_name' => $bundle))
->fetchAllAssoc('mlid'); ->fetchAllAssoc('mlid');
...@@ -33,14 +32,13 @@ class Menu extends LinkGeneratorBase { ...@@ -33,14 +32,13 @@ class Menu extends LinkGeneratorBase {
if (empty($entity->route_name)) if (empty($entity->route_name))
continue; continue;
$options = !empty($options = unserialize($entity->options)) ? $options : array(); //todo: Use Url::getOptions() //todo: There may be a better way to do this.
$route_parameters = !empty($route_parameters = unserialize($entity->route_parameters)) //todo: Use Url::getRouteParameters() $options = !empty($options = unserialize($entity->options)) ? $options : array();
$route_parameters = !empty($route_parameters = unserialize($entity->route_parameters))
? array(key($route_parameters) => $route_parameters[key($route_parameters)]) : array(); ? array(key($route_parameters) => $route_parameters[key($route_parameters)]) : array();
if (parent::access($url_object = Url::fromRoute($entity->route_name, $route_parameters, $options))) {
$paths[$id]['path'] = $url_object->getInternalPath(); $paths[$id]['path_data'] = $this->get_multilang_urls_from_route($entity->route_name, $route_parameters, $options);
$paths[$id]['options'] = $url_object->getOptions(); //todo: Implement lastmod for menu items.
//todo: Implement lastmod for menu items.
}
} }
return $paths; return $paths;
} }
......
...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator; ...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator; use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase; use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/** /**
* NodeType class. * NodeType class.
...@@ -24,16 +23,14 @@ class NodeType extends LinkGeneratorBase { ...@@ -24,16 +23,14 @@ class NodeType extends LinkGeneratorBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
function get_entity_bundle_paths($bundle) { function get_paths($bundle) {
$results = db_query("SELECT nid, changed FROM {node_field_data} WHERE status = 1 AND type = :type", array(':type' => $bundle)) $results = db_query("SELECT nid, changed FROM {node_field_data} WHERE status = 1 AND type = :type", array(':type' => $bundle))
->fetchAllAssoc('nid'); ->fetchAllAssoc('nid');
$paths = array(); $paths = array();
foreach ($results as $id => $data) { foreach ($results as $id => $data) {
if (parent::access($url_object = Url::fromRoute("entity.node.canonical", array('node' => $id), array()))) { $paths[$id]['path_data'] = $this->get_multilang_urls_from_route("entity.node.canonical", array('node' => $id));
$paths[$id]['path'] = $url_object->getInternalPath(); $paths[$id]['lastmod'] = $data->changed;
$paths[$id]['lastmod'] = $data->changed;
}
} }
return $paths; return $paths;
} }
......
...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator; ...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator; use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase; use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/** /**
* TaxonomyVocabulary class. * TaxonomyVocabulary class.
...@@ -24,16 +23,14 @@ class TaxonomyVocabulary extends LinkGeneratorBase { ...@@ -24,16 +23,14 @@ class TaxonomyVocabulary extends LinkGeneratorBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
function get_entity_bundle_paths($bundle) { function get_paths($bundle) {
$results = db_query("SELECT tid, changed FROM {taxonomy_term_field_data} WHERE vid = :vid", array(':vid' => $bundle)) $results = db_query("SELECT tid, changed FROM {taxonomy_term_field_data} WHERE vid = :vid", array(':vid' => $bundle))
->fetchAllAssoc('tid'); ->fetchAllAssoc('tid');
$paths = array(); $paths = array();
foreach ($results as $id => $data) { foreach ($results as $id => $data) {
if (parent::access($url_obj = Url::fromRoute("entity.taxonomy_term.canonical", array('taxonomy_term' => $id), array()))) { $paths[$id]['path_data'] = $this->get_multilang_urls_from_route("entity.taxonomy_term.canonical", array('taxonomy_term' => $id));
$paths[$id]['path'] = $url_obj->getInternalPath(); $paths[$id]['lastmod'] = $data->changed;
$paths[$id]['lastmod'] = $data->changed;
}
} }
return $paths; return $paths;
} }
......
...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator; ...@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator; use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase; use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/** /**
* User class. * User class.
...@@ -25,16 +24,14 @@ class User extends LinkGeneratorBase { ...@@ -25,16 +24,14 @@ class User extends LinkGeneratorBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
function get_entity_bundle_paths($bundle) { function get_paths($bundle) {
$results = db_query("SELECT uid, changed FROM {users_field_data} WHERE status = 1") $results = db_query("SELECT uid, changed FROM {users_field_data} WHERE status = 1")
->fetchAllAssoc('uid'); ->fetchAllAssoc('uid');
$paths = array(); $paths = array();
foreach ($results as $id => $data) { foreach ($results as $id => $data) {
if (parent::access($url_obj = Url::fromRoute("entity.user.canonical", array('user' => $id), array()))) { $paths[$id]['path_data'] = $this->get_multilang_urls_from_route("entity.user.canonical", array('user' => $id));
$paths[$id]['path'] = $url_obj->getInternalPath(); $paths[$id]['lastmod'] = $data->changed;
$paths[$id]['lastmod'] = $data->changed;
}
} }
return $paths; return $paths;
} }
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
namespace Drupal\simplesitemap; namespace Drupal\simplesitemap;
use \XMLWriter; use \XMLWriter;
use Drupal\Core\Url;
/** /**
* SitemapGenerator class. * SitemapGenerator class.
...@@ -27,11 +26,9 @@ class SitemapGenerator { ...@@ -27,11 +26,9 @@ class SitemapGenerator {
private $entity_types; private $entity_types;
private $custom; private $custom;
private $links; private $links;
private $languages;
private $default_language_id; private $default_language_id;
function __construct() { function __construct() {
$this->languages = \Drupal::languageManager()->getLanguages();
$this->default_language_id = \Drupal::languageManager()->getDefaultLanguage()->getId(); $this->default_language_id = \Drupal::languageManager()->getDefaultLanguage()->getId();
$this->links = array(); $this->links = array();
} }
...@@ -70,7 +67,6 @@ class SitemapGenerator { ...@@ -70,7 +67,6 @@ class SitemapGenerator {
$this->generate_custom_paths(); $this->generate_custom_paths();
$this->generate_entity_paths(); $this->generate_entity_paths();
$this->generate_urls_from_paths();
$sitemaps = array(); $sitemaps = array();
...@@ -93,23 +89,6 @@ class SitemapGenerator { ...@@ -93,23 +89,6 @@ class SitemapGenerator {
return $sitemaps; return $sitemaps;
} }
/**
* Generates multilingual urls for each path.
*/
private function generate_urls_from_paths() {
foreach($this->links as $i => $link) {
foreach($this->languages as $language) {
$this->links[$i]['url'][$language->getId()] = Url::fromUserInput('/' . $link['path'], array(
'language' => $language,
'absolute' => TRUE,
'query' => !empty($link['options']['query']) ? $link['options']['query'] : array(),
'fragment' => !empty($link['options']['fragment']) ? $link['options']['fragment'] : '',
))->toString();
}
}
}
/** /**
* Generates and returns the sitemap index. * Generates and returns the sitemap index.
* *
...@@ -160,11 +139,11 @@ class SitemapGenerator { ...@@ -160,11 +139,11 @@ class SitemapGenerator {
$writer->startElement('url'); $writer->startElement('url');
// Adding url to standard language. // Adding url to standard language.
$writer->writeElement('loc', $link['url'][$this->default_language_id]); $writer->writeElement('loc', $link['path_data']['urls'][$this->default_language_id]);
// Adding alternate urls (other languages) if any. // Adding alternate urls (other languages) if any.
if (count($link['url']) > 1) { if (count($link['path_data']['urls']) > 1) {
foreach($link['url'] as $language_id => $localised_url) { foreach($link['path_data']['urls'] as $language_id => $localised_url) {
$writer->startElement('xhtml:link'); $writer->startElement('xhtml:link');
$writer->writeAttribute('rel', 'alternate'); $writer->writeAttribute('rel', 'alternate');
$writer->writeAttribute('hreflang', $language_id); $writer->writeAttribute('hreflang', $language_id);
...@@ -193,7 +172,7 @@ class SitemapGenerator { ...@@ -193,7 +172,7 @@ class SitemapGenerator {
*/ */
private function generate_custom_paths() { private function generate_custom_paths() {
$link_generator = new CustomLinkGenerator(); $link_generator = new CustomLinkGenerator();
$links = $link_generator->get_custom_paths($this->custom); $links = $link_generator->get_paths($this->custom);
$this->add_created_paths($links); $this->add_created_paths($links);
} }
...@@ -223,10 +202,18 @@ class SitemapGenerator { ...@@ -223,10 +202,18 @@ class SitemapGenerator {
* Drupal internal paths generated by a plugin. * Drupal internal paths generated by a plugin.
*/ */
private function add_created_paths($paths) { private function add_created_paths($paths) {
// Do not include path if anonymous users have no access to it.
foreach($paths as $i => $path) { foreach($paths as $i => $path) {
if (!$path['path_data']['access']) {
unset($paths[$i]);
continue;
}
// Do not include path if it is a duplicate.
foreach($this->links as $existing_path) { foreach($this->links as $existing_path) {
if ($path['path'] == $existing_path['path']) { if ($path['path_data']['path'] === $existing_path['path_data']['path']) {
unset($paths[$i]); unset($paths[$i]);