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

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:
title: 'Administer sitemap settings'
description: 'Administer the sitemap settings and generate sitemap on demand.'
restrict access: false
add custom links:
title: 'Add custom links'
description: 'Add any Drupal internal paths to the xml sitemap.'
description: 'Administer the sitemap settings, alter inclusion and priority of content and generate the sitemap on demand.'
restrict access: false
......@@ -26,4 +26,4 @@ simplesitemap.settings_custom:
_form: '\Drupal\simplesitemap\Form\SimplesitemapCustomLinksForm'
_title: 'Simple XML Sitemap Settings'
requirements:
_permission: 'add custom links'
_permission: 'administer sitemap settings'
......@@ -8,14 +8,10 @@
namespace Drupal\simplesitemap;
use Drupal\Core\Url;
use \Drupal\user\Entity\User;
/**
* CustomLinkGenerator class.
*/
class CustomLinkGenerator {
const ANONYMOUS_USER_ID = 0;
class CustomLinkGenerator extends LinkGeneratorBase {
/**
* Returns an array of all urls of the custom paths.
......@@ -25,18 +21,13 @@ class CustomLinkGenerator {
* @return array $urls
*
*/
public function get_custom_paths($custom_paths) {
$anonymous_account = User::load(self::ANONYMOUS_USER_ID);
public function get_paths($custom_paths) {
$paths = array();
foreach($custom_paths as $i => $custom_path) {
if (!isset($custom_path['index']) || $custom_path['index']) {
$url_object = Url::fromUserInput($custom_path['path'], array());
if ($url_object->access($anonymous_account)) {
$paths[$i]['path'] = $url_object->getInternalPath();
$paths[$i]['options'] = $url_object->getOptions();
$paths[$i]['priority'] = isset($custom_path['priority']) ? $custom_path['priority'] : NULL;
$paths[$i]['lastmod'] = NULL; //todo: implement lastmod
}
if (FALSE !== $path_data = $this->get_multilang_urls_from_user_input($custom_path['path'])) {
$paths[$i]['path_data'] = $path_data;
$paths[$i]['priority'] = isset($custom_path['priority']) ? $custom_path['priority'] : NULL;
$paths[$i]['lastmod'] = NULL; //todo: implement lastmod
}
}
return $paths;
......
......@@ -8,52 +8,87 @@ namespace Drupal\simplesitemap;
use Drupal\Component\Plugin\PluginBase;
use \Drupal\user\Entity\User;
use Drupal\Core\Url;
abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInterface {
private $entity_paths = array();
private $current_entity_type;
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;
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}
*/
public function get_entity_paths($entity_type, $bundles) {
$this->current_entity_type = $entity_type;
$this->anonymous_account = User::load(self::ANONYMOUS_USER_ID);
$i = 0;
foreach($bundles as $bundle => $bundle_settings) {
if (!$bundle_settings['index'])
continue;
$paths = $this->get_entity_bundle_paths($bundle);
$paths = $this->get_paths($bundle);
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;
}
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;
}
// 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'];
$this->entity_paths[$i]['options'] = !empty($path['options']) ? $path['options'] : array();
$this->entity_paths[$i]['priority'] = !empty($path['priority']) ? $path['priority'] : $bundle_settings['priority'];
$this->entity_paths[$i]['lastmod'] = isset($path['lastmod']) && is_numeric($path['lastmod']) ? date_iso8601($path['lastmod']) : NULL;
// Adding lastmod if returned by the plugin.
$this->entity_paths[$i]['lastmod'] = isset($path['lastmod']) && is_numeric($path['lastmod'])
? date_iso8601($path['lastmod']) : NULL;
$i++;
}
}
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_set_message($message, 'error');
}
......@@ -65,22 +100,112 @@ abstract class LinkGeneratorBase extends PluginBase implements LinkGeneratorInte
* Machine name of the bundle, eg. 'page'.
*
* @return array $paths
* A numeric array of Drupal internal path data sets containing the path and
* optionally lastmod and link options:
* array(
* 'path' => 'drupal/internal/path/to/content', // required
* 'options' => array( // optional
* 'query' => array('page' => '1'),
* 'fragment' => 'my-anchor-id',
* A numeric array of Drupal internal path data sets containing the internal
* path, url objects for every language (optional), lastmod (optional) and
* priority (optional):
*
* array(
* 0 => array(
* '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
* )
* ),
* )
*
* @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) {
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;
use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/**
* Menu class.
......@@ -24,7 +23,7 @@ class Menu extends LinkGeneratorBase {
/**
* {@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))
->fetchAllAssoc('mlid');
......@@ -33,14 +32,13 @@ class Menu extends LinkGeneratorBase {
if (empty($entity->route_name))
continue;
$options = !empty($options = unserialize($entity->options)) ? $options : array(); //todo: Use Url::getOptions()
$route_parameters = !empty($route_parameters = unserialize($entity->route_parameters)) //todo: Use Url::getRouteParameters()
//todo: There may be a better way to do this.
$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();
if (parent::access($url_object = Url::fromRoute($entity->route_name, $route_parameters, $options))) {
$paths[$id]['path'] = $url_object->getInternalPath();
$paths[$id]['options'] = $url_object->getOptions();
//todo: Implement lastmod for menu items.
}
$paths[$id]['path_data'] = $this->get_multilang_urls_from_route($entity->route_name, $route_parameters, $options);
//todo: Implement lastmod for menu items.
}
return $paths;
}
......
......@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/**
* NodeType class.
......@@ -24,16 +23,14 @@ class NodeType extends LinkGeneratorBase {
/**
* {@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))
->fetchAllAssoc('nid');
$paths = array();
foreach ($results as $id => $data) {
if (parent::access($url_object = Url::fromRoute("entity.node.canonical", array('node' => $id), array()))) {
$paths[$id]['path'] = $url_object->getInternalPath();
$paths[$id]['lastmod'] = $data->changed;
}
$paths[$id]['path_data'] = $this->get_multilang_urls_from_route("entity.node.canonical", array('node' => $id));
$paths[$id]['lastmod'] = $data->changed;
}
return $paths;
}
......
......@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/**
* TaxonomyVocabulary class.
......@@ -24,16 +23,14 @@ class TaxonomyVocabulary extends LinkGeneratorBase {
/**
* {@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))
->fetchAllAssoc('tid');
$paths = array();
foreach ($results as $id => $data) {
if (parent::access($url_obj = Url::fromRoute("entity.taxonomy_term.canonical", array('taxonomy_term' => $id), array()))) {
$paths[$id]['path'] = $url_obj->getInternalPath();
$paths[$id]['lastmod'] = $data->changed;
}
$paths[$id]['path_data'] = $this->get_multilang_urls_from_route("entity.taxonomy_term.canonical", array('taxonomy_term' => $id));
$paths[$id]['lastmod'] = $data->changed;
}
return $paths;
}
......
......@@ -10,7 +10,6 @@ namespace Drupal\simplesitemap\Plugin\LinkGenerator;
use Drupal\simplesitemap\Annotation\LinkGenerator;
use Drupal\simplesitemap\LinkGeneratorBase;
use Drupal\Core\Url;
/**
* User class.
......@@ -25,16 +24,14 @@ class User extends LinkGeneratorBase {
/**
* {@inheritdoc}
*/
function get_entity_bundle_paths($bundle) {
function get_paths($bundle) {
$results = db_query("SELECT uid, changed FROM {users_field_data} WHERE status = 1")
->fetchAllAssoc('uid');
$paths = array();
foreach ($results as $id => $data) {
if (parent::access($url_obj = Url::fromRoute("entity.user.canonical", array('user' => $id), array()))) {
$paths[$id]['path'] = $url_obj->getInternalPath();
$paths[$id]['lastmod'] = $data->changed;
}
$paths[$id]['path_data'] = $this->get_multilang_urls_from_route("entity.user.canonical", array('user' => $id));
$paths[$id]['lastmod'] = $data->changed;
}
return $paths;
}
......
......@@ -9,7 +9,6 @@
namespace Drupal\simplesitemap;
use \XMLWriter;
use Drupal\Core\Url;
/**
* SitemapGenerator class.
......@@ -27,11 +26,9 @@ class SitemapGenerator {
private $entity_types;
private $custom;
private $links;
private $languages;
private $default_language_id;
function __construct() {
$this->languages = \Drupal::languageManager()->getLanguages();
$this->default_language_id = \Drupal::languageManager()->getDefaultLanguage()->getId();
$this->links = array();
}
......@@ -70,7 +67,6 @@ class SitemapGenerator {
$this->generate_custom_paths();
$this->generate_entity_paths();
$this->generate_urls_from_paths();
$sitemaps = array();
......@@ -93,23 +89,6 @@ class SitemapGenerator {
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.
*
......@@ -160,11 +139,11 @@ class SitemapGenerator {
$writer->startElement('url');
// 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.
if (count($link['url']) > 1) {
foreach($link['url'] as $language_id => $localised_url) {
if (count($link['path_data']['urls']) > 1) {
foreach($link['path_data']['urls'] as $language_id => $localised_url) {
$writer->startElement('xhtml:link');
$writer->writeAttribute('rel', 'alternate');
$writer->writeAttribute('hreflang', $language_id);
......@@ -193,7 +172,7 @@ class SitemapGenerator {
*/
private function generate_custom_paths() {
$link_generator = new CustomLinkGenerator();
$links = $link_generator->get_custom_paths($this->custom);
$links = $link_generator->get_paths($this->custom);
$this->add_created_paths($links);
}
......@@ -223,10 +202,18 @@ class SitemapGenerator {
* Drupal internal paths generated by a plugin.
*/
private function add_created_paths($paths) {
// Do not include path if anonymous users have no access to it.
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) {
if ($path['path'] == $existing_path['path']) {
if ($path['path_data']['path'] === $existing_path['path_data']['path']) {
unset($paths[$i]);
break;
}
}
}
......
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