Commit d1f54da1 authored by gbyte.co's avatar gbyte.co

Added entity support and created a framework so custom entity types can be added easily.

parent 1d53cb54
name: Simple XML Sitemap name: Simple XML Sitemap
type: module type: module
description: 'This simple module creates a standard conform xml sitemap of your content.' description: 'This module creates a standard conform xml sitemap of your content.'
package: SEO package: SEO
core: 8.x core: 8.x
...@@ -5,17 +5,24 @@ ...@@ -5,17 +5,24 @@
*/ */
use Drupal\simplesitemap\Simplesitemap; use Drupal\simplesitemap\Simplesitemap;
use Drupal\simplesitemap\SitemapGenerator;
/** /**
* Implements hook_form_alter. * Implements hook_form_alter.
*/ */
function simplesitemap_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { function simplesitemap_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
if ($form_id == 'node_type_edit_form') {
$form_entity = Simplesitemap::get_form_entity($form_state);
$entity_type_name = Simplesitemap::get_entity_type_name($form_entity);
if ($entity_type_name != FALSE && Simplesitemap::get_plugin_path($entity_type_name)) {
$bundle_name = $form_entity->Id();
$sitemap = new Simplesitemap; $sitemap = new Simplesitemap;
// Get current content type sitemap settings. // Get current content type sitemap settings.
$content_types = $sitemap->get_content_types(); $entity_types = $sitemap->get_entity_types();
$form['simplesitemap'] = array( $form['simplesitemap'] = array(
'#group' => 'additional_settings', '#group' => 'additional_settings',
...@@ -23,49 +30,55 @@ function simplesitemap_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $ ...@@ -23,49 +30,55 @@ function simplesitemap_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $
'#type' => 'details' '#type' => 'details'
); );
$index_content_checked = isset($content_types[$form['type']['#default_value']]['index']) ? $content_types[$form['type']['#default_value']]['index'] : FALSE; $index_content_checked = isset($entity_types[$entity_type_name][$bundle_name]['index']) ? $entity_types[$entity_type_name][$bundle_name]['index'] : FALSE;
$form['simplesitemap']['simplesitemap_index_content'] = array( $form['simplesitemap']['simplesitemap_index_content'] = array(
'#type' => 'checkbox', '#type' => 'checkbox',
'#title' => 'Index content of this type', '#title' => 'Index content of this type',
'#default_value' => $index_content_checked, '#default_value' => $index_content_checked,
); );
$priority = isset($content_types[$form['type']['#default_value']]['priority']) ? $content_types[$form['type']['#default_value']]['priority'] : $sitemap->get_priority_default(); $priority = isset($entity_types[$entity_type_name][$bundle_name]['priority']) ? $entity_types[$entity_type_name][$bundle_name]['priority'] : SitemapGenerator::PRIORITY_DEFAULT;
$form['simplesitemap']['simplesitemap_priority'] = array( $form['simplesitemap']['simplesitemap_priority'] = array(
'#type' => 'select', '#type' => 'select',
'#title' => 'Priority', '#title' => 'Priority',
'#options' => $sitemap->get_priority_select_values(), '#options' => SitemapGenerator::get_priority_select_values(),
'#default_value' => $priority, '#default_value' => $priority,
'#description' => 'The priority nodes of this content type will have in the eyes of search engine bots.', '#description' => 'The priority entities of this bundle will have in the eyes of search engine bots.',
); );
// Add submission handler. // Add submission handler.
$form['actions']['submit']['#submit'][] = 'simplesitemap_bundle_form_submit'; $form['actions']['submit']['#submit'][] = 'simplesitemap_entity_form_submit';
} }
} }
/** /**
* Form submission handler callback. * Form submission handler called in hook_form_alter.
*/ */
function simplesitemap_bundle_form_submit($form, &$form_state) { function simplesitemap_entity_form_submit($form, &$form_state) {
$values = $form_state->getValues();
// Only make changes in DB if sitemap settings actually changed. // Only make changes in DB if sitemap settings actually changed.
if ($form['simplesitemap']['simplesitemap_index_content']['#default_value'] != $form['simplesitemap']['simplesitemap_index_content']['#value'] || $form['simplesitemap']['simplesitemap_priority']['#default_value'] != $form['simplesitemap']['simplesitemap_priority']['#value']) { if ($form['simplesitemap']['simplesitemap_index_content']['#default_value'] != $form['simplesitemap']['simplesitemap_index_content']['#value'] || $form['simplesitemap']['simplesitemap_priority']['#default_value'] != $form['simplesitemap']['simplesitemap_priority']['#value']) {
$entity = Simplesitemap::get_form_entity($form_state);
$entity_type_name = Simplesitemap::get_entity_type_name($entity);
$bundle_name = $entity->Id();
$sitemap = new Simplesitemap; $sitemap = new Simplesitemap;
// Get current content type sitemap settings. // Get current entity type sitemap settings.
$content_types = $sitemap->get_content_types(); $entity_types = $sitemap->get_entity_types();
$content_types[$form['type']['#default_value']]['index'] = $values['simplesitemap_index_content']; $values = $form_state->getValues();
$content_types[$form['type']['#default_value']]['priority'] = $values['simplesitemap_priority']; $entity_types[$entity_type_name][$bundle_name]['index'] = $values['simplesitemap_index_content'];
$entity_types[$entity_type_name][$bundle_name]['priority'] = $values['simplesitemap_priority'];
// Save new content type settings. // Save new entity type settings.
$sitemap->save_content_types($content_types); $sitemap->save_entity_types($entity_types);
// Regenerate sitemaps for all languages and save them into strings for performance reason (pseudo caching). // Regenerate sitemaps for all languages and save them into strings for performance reason (pseudo caching).
$sitemap->generate_all_sitemaps(); $sitemap->generate_all_sitemaps();
drupal_set_message(t('XML sitemaps have been regenerated for all languages.'));
} }
} }
......
<?php
/**
* @file
* Contains \Drupal\simplesitemap\LinkGenerators\CustomLinkGenerator.
*
* Generates custom sitemap links provided by the user.
*/
namespace Drupal\simplesitemap\LinkGenerators;
use Drupal\Core\Url;
use Drupal\simplesitemap\SitemapGenerator;
/**
* CustomLinkGenerator class.
*/
class CustomLinkGenerator {
public function get_custom_links($custom_paths, $language) {
$links = array();
foreach($custom_paths as $custom_path) {
if ($custom_path['index']) {
$links[] = SitemapGenerator::add_xml_link_markup(Url::fromUserInput($custom_path['path'], array(
'language' => $language,
'absolute' => TRUE
))->toString(), $custom_path['priority']);
}
}
return $links;
}
}
<?php
/**
* @file
* Contains \Drupal\simplesitemap\LinkGenerators\EntityLinkGenerator.
*
* Abstract class to be extended for plugin creation.
* See \Drupal\simplesitemap\LinkGenerators\CustomLinkGenerator\node for more
* documentation.
*/
namespace Drupal\simplesitemap\LinkGenerators;
use Drupal\simplesitemap\SitemapGenerator;
/**
* EntityLinkGenerator abstract class.
*/
abstract class EntityLinkGenerator {
private $entity_links = array();
public function get_entity_links($entity_type, $bundles, $language) {
foreach($bundles as $bundle => $bundle_settings) {
if (!$bundle_settings['index']) {
continue;
}
$links = $this->get_entity_bundle_links($entity_type, $bundle, $language);
foreach ($links as &$link) {
$link = SitemapGenerator::add_xml_link_markup($link, $bundle_settings['priority']);
}
$this->entity_links += $links;
}
return $this->entity_links;
}
abstract function get_entity_bundle_links($entity_type, $bundle, $language);
}
<?php
/**
* @file
* Contains \Drupal\simplesitemap\LinkGenerators\EntityTypeLinkGenerators\node.
*
* Plugin for node entity link generation.
*
* This can be used as a template to create new plugins for other entity types.
* To create a plugin simply create a new class file in the
* EntityTypeLinkGenerators folder. Name this file after the entity type (eg.
* 'node' or 'taxonomy_term'.
* This class needs to extend the EntityLinkGenerator class and include
* the get_entity_bundle_links() method. - as shown here. This method has to
* return an array of pure urls to the entities of the entity type in question.
*/
namespace Drupal\simplesitemap\LinkGenerators\EntityTypeLinkGenerators;
use Drupal\simplesitemap\LinkGenerators\EntityLinkGenerator;
use Drupal\Core\Url;
/**
* Node class.
*/
class node extends EntityLinkGenerator {
function get_entity_bundle_links($entity_type, $bundle, $language) {
$ids = array();
$query = \Drupal::entityQuery($entity_type)
->condition('status', 1)
->condition('type', $bundle);
$ids += $query->execute();
$urls = array();
foreach ($ids as $id => $entity) {
$urls[] = Url::fromRoute("entity.$entity_type.canonical", array('node' => $id), array(
'language' => $language,
'absolute' => TRUE
))->toString();
}
return $urls;
}
}
<?php
/**
* @file
* Contains \Drupal\simplesitemap\LinkGenerators\EntityTypeLinkGenerators\taxonomy_term.
*
* Plugin for taxonomy term entity link generation.
* See \Drupal\simplesitemap\LinkGenerators\CustomLinkGenerator\node for more
* documentation.
*/
namespace Drupal\simplesitemap\LinkGenerators\EntityTypeLinkGenerators;
use Drupal\simplesitemap\LinkGenerators\EntityLinkGenerator;
use Drupal\Core\Url;
/**
* EntityLinkGenerator class.
*/
class taxonomy_term extends EntityLinkGenerator {
function get_entity_bundle_links($entity_type, $bundle, $language) {
$ids = array();
$query = \Drupal::entityQuery($entity_type)
->condition('vid', $bundle);
$ids += $query->execute();
$urls = array();
foreach ($ids as $id => $entity) {
$urls[] = Url::fromRoute("entity.$entity_type.canonical", array('taxonomy_term' => $id), array(
'language' => $language,
'absolute' => TRUE
))->toString();
}
return $urls;
}
}
...@@ -6,25 +6,47 @@ ...@@ -6,25 +6,47 @@
namespace Drupal\simplesitemap; namespace Drupal\simplesitemap;
use Drupal\Core\Url;
/** /**
* Simplesitemap class. * Simplesitemap class.
*/ */
class Simplesitemap { class Simplesitemap {
const SITEMAP_PLUGIN_PATH = 'src/LinkGenerators/EntityTypeLinkGenerators';
private $config;
private $sitemap; private $sitemap;
private $content_types; private $language;
private $custom;
private $lang;
function __construct() { function __construct() {
$this->set_current_lang(); $this->set_current_lang();
$this->set_config(); $this->set_config();
} }
public static function get_form_entity($form_state) {
if (!is_null($form_state->getFormObject()) && method_exists($form_state->getFormObject(), 'getEntity')) {
$entity = $form_state->getFormObject()->getEntity();
return $entity;
}
return FALSE;
}
public static function get_entity_type_name($entity) {
if (method_exists($entity, 'getEntityType')) {
return $entity->getEntityType()->getBundleOf();
}
return FALSE;
}
public static function get_plugin_path($entity_type_name) {
$class_path = drupal_get_path('module', 'simplesitemap') . '/' . self::SITEMAP_PLUGIN_PATH . '/' . $entity_type_name . '.php';
if (file_exists($class_path)) {
return $class_path;
}
return FALSE;
}
private function set_current_lang($language = NULL) { private function set_current_lang($language = NULL) {
$this->lang = is_null($language) ? \Drupal::languageManager()->getCurrentLanguage() : $language; $this->language = is_null($language) ? \Drupal::languageManager()->getCurrentLanguage() : $language;
} }
private function set_config() { private function set_config() {
...@@ -36,30 +58,65 @@ class Simplesitemap { ...@@ -36,30 +58,65 @@ class Simplesitemap {
private function get_sitemap_from_db() { private function get_sitemap_from_db() {
$result = db_select('simplesitemap', 's') $result = db_select('simplesitemap', 's')
->fields('s', array('sitemap_string')) ->fields('s', array('sitemap_string'))
->condition('language_code', $this->lang->getId()) ->condition('language_code', $this->language->getId())
->execute()->fetchAll(); ->execute()->fetchAll();
$this->sitemap = !empty($result[0]->sitemap_string) ? $result[0]->sitemap_string : NULL; $this->sitemap = !empty($result[0]->sitemap_string) ? $result[0]->sitemap_string : NULL;
} }
// Get sitemap settings from configuration storage. // Get sitemap settings from configuration storage.
private function get_config_from_db() { private function get_config_from_db() {
$config = \Drupal::config('simplesitemap.settings'); $this->config = \Drupal::config('simplesitemap.settings');
$this->content_types = $config->get('content_types'); }
$this->custom = $config->get('custom');
public function save_entity_types($entity_types) {
$this->save_config('entity_types', $entity_types);
}
private function save_config($key, $value) {
\Drupal::service('config.factory')->getEditable('simplesitemap.settings')->set($key, $value)->save();
$this->set_config();
}
public function get_sitemap() {
if (empty($this->sitemap)) {
$this->generate_sitemap();
}
return $this->sitemap;
}
private function generate_sitemap() {
$generator = new SitemapGenerator();
$generator->set_sitemap_lang($this->language);
$generator->set_custom_links($this->config->get('custom'));
$generator->set_entity_types($this->config->get('entity_types'));
$this->sitemap = $generator->generate_sitemap();
$this->save_sitemap();
}
public function generate_all_sitemaps() {
$generator = new SitemapGenerator();
$generator->set_custom_links($this->config->get('custom'));
$generator->set_entity_types($this->config->get('entity_types'));
foreach(\Drupal::languageManager()->getLanguages() as $language) {
$generator->set_sitemap_lang($language);
$this->language = $language;
$this->sitemap = $generator->generate_sitemap();
$this->save_sitemap();
}
} }
private function save_sitemap() { private function save_sitemap() {
//todo: db_merge not working in D8(?), this is why the following queries are needed: //todo: db_merge not working in D8(?), this is why the following queries are needed:
// db_merge('simplesitemap') // db_merge('simplesitemap')
// ->key(array('language_code', $this->lang->getId())) // ->key(array('language_code', $this->lang))
// ->fields(array( // ->fields(array(
// 'language_code' => $this->lang->getId(), // 'language_code' => $this->lang,
// 'sitemap_string' => $this->sitemap, // 'sitemap_string' => $this->sitemap,
// )) // ))
// ->execute(); // ->execute();
$exists_query = db_select('simplesitemap') $exists_query = db_select('simplesitemap')
->condition('language_code', $this->lang->getId()) ->condition('language_code', $this->language->getId())
->countQuery()->execute()->fetchField(); ->countQuery()->execute()->fetchField();
if ($exists_query > 0) { if ($exists_query > 0) {
...@@ -67,111 +124,20 @@ class Simplesitemap { ...@@ -67,111 +124,20 @@ class Simplesitemap {
->fields(array( ->fields(array(
'sitemap_string' => $this->sitemap, 'sitemap_string' => $this->sitemap,
)) ))
->condition('language_code', $this->lang->getId()) ->condition('language_code', $this->language->getId())
->execute(); ->execute();
} }
else { else {
db_insert('simplesitemap') db_insert('simplesitemap')
->fields(array( ->fields(array(
'language_code' => $this->lang->getId(), 'language_code' => $this->language->getId(),
'sitemap_string' => $this->sitemap, 'sitemap_string' => $this->sitemap,
)) ))
->execute(); ->execute();
} }
} }
public function save_content_types($content_types) { public function get_entity_types() {
$this->save_config('content_types', $content_types); return $this->config->get('entity_types');
}
private function save_config($key, $value) {
\Drupal::service('config.factory')->getEditable('simplesitemap.settings')->set($key, $value)->save();
$this->set_config();
}
private function add_xml_link_markup($url, $priority) {
return "<url><loc>" . $url . "</loc><priority>" . $priority . "</priority></url>";
}
private function generate_sitemap() {
$output = '';
// Add custom links according to config file.
$custom = $this->custom;
foreach ($custom as $page) {
if ($page['index']) {
$output .= $this->add_xml_link_markup(Url::fromUserInput($page['path'], array(
'language' => $this->lang,
'absolute' => TRUE
))->toString(), $page['priority']);
}
}
// Add node links according to content type settings.
$content_types = $this->content_types;
if (count($content_types) > 0) {
//todo: D8 entityQuery doesn't seem to take multiple OR conditions, that's why that ugly db_select.
// $query = \Drupal::entityQuery('node')
// ->condition('status', 1)
// ->condition('type', array_keys($content_types));
// $nids = $query->execute();
$query = db_select('node_field_data', 'n')
->fields('n', array('nid', 'type'))
->condition('status', 1);
$db_or = db_or();
foreach ($content_types as $machine_name => $options) {
if ($options['index']) {
$db_or->condition('type', $machine_name);
}
}
$query->condition($db_or);
$nids = $query->execute()->fetchAllAssoc('nid');
foreach ($nids as $nid => $node) {
$output .= $this->add_xml_link_markup(Url::fromRoute('entity.node.canonical', array('node' => $nid), array(
'language' => $this->lang,
'absolute' => TRUE
))->toString(), $content_types[$node->type]['priority']);
}
}
// Add sitemap markup.
$output = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" . $output . "</urlset>";
$this->sitemap = $output;
$this->save_sitemap();
}
public function get_content_types() {
return $this->content_types;
}
public function get_sitemap() {
if (empty($this->sitemap)) {
$this->generate_sitemap();
}
return $this->sitemap;
}
public function generate_all_sitemaps() {
foreach(\Drupal::languageManager()->getLanguages() as $language) {
$this->set_current_lang($language);
$this->generate_sitemap();
}
//todo: Delete sitemaps the languages of which have been disabled/removed.
}
public static function get_priority_select_values() {
foreach(range(0, 10) as $value) {
$value = $value / 10;
$options[(string)$value] = (string)$value;
}
return $options;
}
public static function get_priority_default() {
return $priority_default = 0.5;
} }
} }
<?php
/**
* @file
* Contains \Drupal\simplesitemap\SitemapGenerator.
*
* Generates a sitemap for entities and custom links.
*/
namespace Drupal\simplesitemap;
use Drupal\simplesitemap\LinkGenerators\CustomLinkGenerator;
/**
* SitemapGenerator class.
*/
class SitemapGenerator {
const PRIORITY_DEFAULT = 0.5;
const PRIORITY_HIGHEST = 10;
const PRIORITY_DIVIDER = 10;
const XML_VERSION = '1.0';
const ENCODING = 'UTF-8';
const XMLNS_URL = 'http://www.sitemaps.org/schemas/sitemap/0.9';
private $entity_types;
private $custom;
private $language;
private $links;
public static function get_priority_select_values() {
$options = array();
foreach(range(0, self::PRIORITY_HIGHEST) as $value) {
$value = $value / self::PRIORITY_DIVIDER;
$options[(string)$value] = (string)$value;
}
return $options;
}
public static function add_xml_link_markup($url, $priority) {
return "<url><loc>" . $url . "</loc><priority>" . $priority . "</priority></url>";
}
public function set_entity_types($entity_types) {
$this->entity_types = $entity_types;
}
public function set_custom_links($custom) {
$this->custom = $custom;
}
public function set_sitemap_lang($language) {
// Reset links array to make space for a sitemap with a different language.
$this->links = array();
$this->language = $language;
}
public function generate_sitemap() {
$this->generate_custom_links();
$this->generate_entity_links();
$sitemap = implode($this->links);
return $this->add_xml_sitemap_markup($sitemap);
}
// Add custom links.
private function generate_custom_links() {
$link_generator = new CustomLinkGenerator();
$links = $link_generator->get_custom_links($this->custom , $this->language);
$this->links += $links;
}
// Add entity type links.
private function generate_entity_links() {
foreach($this->entity_types as $entity_type => $bundles) {
$class_path = Simplesitemap::get_plugin_path($entity_type);
if ($class_path !== FALSE) {
require_once $class_path;
$class_name = "Drupal\\simplesitemap\\LinkGenerators\\EntityTypeLinkGenerators\\$entity_type";
$link_generator = new $class_name();
$links = $link_generator->get_entity_links($entity_type, $bundles, $this->language);
$this->links += $links;
}
}
}
// Add sitemap markup.
private function add_xml_sitemap_markup($sitemap) {
return "<?xml version=\"" . self::XML_VERSION . "\" encoding=\"" . self::ENCODING . "\"?><urlset xmlns=\"" . self::XMLNS_URL . "\">" . $sitemap . "</urlset>";
}
}
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