You are here

xmlsitemap.module in XML sitemap 2.x

xmlsitemap XML sitemap

File

xmlsitemap.module
View source
<?php

/**
 * @file
 * @defgroup xmlsitemap XML sitemap
 */

/**
 * @file
 * Main file for the xmlsitemap module.
 */
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\xmlsitemap\Controller\XmlSitemapController;
use Drupal\xmlsitemap\Entity\XmlSitemap;
use Drupal\xmlsitemap\XmlSitemapInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * The maximum number of links in one sitemap chunk file.
 */
const XMLSITEMAP_MAX_SITEMAP_LINKS = 50000;

/**
 * The maximum filesize of a sitemap chunk file.
 */
const XMLSITEMAP_MAX_SITEMAP_FILESIZE = 52528800;

/**
 * Xmlsitemap Frequencies.
 */
const XMLSITEMAP_FREQUENCY_YEARLY = 31449600;

// 60 * 60 * 24 * 7 * 52.
const XMLSITEMAP_FREQUENCY_MONTHLY = 2419200;

// 60 * 60 * 24 * 7 * 4.
const XMLSITEMAP_FREQUENCY_WEEKLY = 604800;

// 60 * 60 * 24 * 7.
const XMLSITEMAP_FREQUENCY_DAILY = 86400;

// 60 * 60 * 24.
const XMLSITEMAP_FREQUENCY_HOURLY = 3600;

// 60 * 60.
const XMLSITEMAP_FREQUENCY_ALWAYS = 60;

/**
 * Short lastmod timestamp format.
 */
const XMLSITEMAP_LASTMOD_SHORT = 'Y-m-d';

/**
 * Medium lastmod timestamp format.
 */
const XMLSITEMAP_LASTMOD_MEDIUM = 'Y-m-d\\TH:i\\Z';

/**
 * Long lastmod timestamp format.
 */
const XMLSITEMAP_LASTMOD_LONG = 'c';

/**
 * The default inclusion status for link types in the sitemaps.
 */
const XMLSITEMAP_STATUS_DEFAULT = 0;

/**
 * The default priority for link types in the sitemaps.
 */
const XMLSITEMAP_PRIORITY_DEFAULT = 0.5;

/**
 * Implements hook_hook_info().
 */
function xmlsitemap_hook_info() {
  $hooks = [
    'xmlsitemap_link_info',
    'xmlsitemap_link_info_alter',
    'xmlsitemap_link_alter',
    'xmlsitemap_index_links',
    'xmlsitemap_context_info',
    'xmlsitemap_context_info_alter',
    'xmlsitemap_context_url_options',
    'xmlsitemap_context',
    'xmlsitemap_element_alter',
    'xmlsitemap_root_attributes_alter',
    'xmlsitemap_sitemap_insert',
    'xmlsitemap_sitemap_update',
    'xmlsitemap_sitemap_operations',
    'xmlsitemap_sitemap_delete',
    'xmlsitemap_sitemap_link_url_options_alter',
    'query_xmlsitemap_generate_alter',
    'query_xmlsitemap_index_links_alter',
    'form_xmlsitemap_sitemap_edit_form_alter',
    'xmlsitemap_rebuild_clear',
  ];
  $hooks = array_combine($hooks, $hooks);
  foreach ($hooks as $hook => $info) {
    $hooks[$hook] = [
      'group' => 'xmlsitemap',
    ];
  }
  return $hooks;
}

/**
 * Implements hook_help().
 */
function xmlsitemap_help($route_name, RouteMatchInterface $route_match) {
  $output = '';
  switch ($route_name) {
    case 'help.page.xmlsitemap':
    case 'xmlsitemap.admin_settings':
    case 'xmlsitemap.entities_settings':
    case 'entity.xmlsitemap.edit_form':
    case 'entity.xmlsitemap.delete_form':
      return;
    case 'xmlsitemap.admin_search':
      break;
    case 'xmlsitemap.admin_search_list':
      break;
    case 'xmlsitemap.admin_rebuild':
      $output .= '<p>' . t("This action rebuilds your site's XML sitemap and regenerates the cached files, and may be a lengthy process. If you just installed XML sitemap, this can be helpful to import all your site's content into the sitemap. Otherwise, this should only be used in emergencies.") . '</p>';
  }
  $currentUser = \Drupal::currentUser();
  if (strpos($route_name, 'xmlsitemap') !== FALSE && $currentUser
    ->hasPermission('administer xmlsitemap')) {

    // Alert the user to any potential problems detected by hook_requirements.
    $output .= _xmlsitemap_get_blurb();
  }
  return $output;
}

/**
 * Implements hook_theme().
 */
function xmlsitemap_theme() {
  return [
    'xmlsitemap_content_settings_table' => [
      'render element' => 'element',
      'file' => 'xmlsitemap.module',
    ],
  ];
}

/**
 * Menu access callback; determines if the user can use the rebuild links page.
 *
 * @return bool
 *   Returns TRUE if current user can access rebuild form. FALSE otherwise.
 */
function _xmlsitemap_rebuild_form_access() {
  $rebuild_types = xmlsitemap_get_rebuildable_link_types();
  return !empty($rebuild_types) && \Drupal::currentUser()
    ->hasPermission('administer xmlsitemap');
}

/**
 * Implements hook_cron().
 *
 * @todo Use new Queue system. Need to add {sitemap}.queued.
 * @todo Regenerate one at a time?
 */
function xmlsitemap_cron() {

  // If cron sitemap file regeneration is disabled, stop.
  if (\Drupal::config('xmlsitemap.settings')
    ->get('disable_cron_regeneration')) {
    return;
  }

  // If there were no new or changed links, skip.
  if (!\Drupal::state()
    ->get('xmlsitemap_regenerate_needed')) {
    return;
  }

  // If the minimum sitemap lifetime hasn't been passed, skip.
  $lifetime = \Drupal::time()
    ->getRequestTime() - \Drupal::state()
    ->get('xmlsitemap_generated_last');
  if ($lifetime < \Drupal::config('xmlsitemap.settings')
    ->get('minimum_lifetime')) {
    return;
  }
  xmlsitemap_xmlsitemap_index_links(\Drupal::config('xmlsitemap.settings')
    ->get('batch_limit'));

  // Regenerate the sitemap XML files.
  xmlsitemap_run_unprogressive_batch('xmlsitemap_regenerate_batch');
}

/**
 * Implements hook_modules_installed().
 */
function xmlsitemap_modules_installed(array $modules) {
  Cache::invalidateTags([
    'xmlsitemap',
  ]);
}

/**
 * Implements hook_modules_uninstalled().
 */
function xmlsitemap_modules_uninstalled(array $modules) {
  Cache::invalidateTags([
    'xmlsitemap',
  ]);
}

/**
 * Implements hook_robotstxt().
 */
function xmlsitemap_robotstxt() {
  if ($sitemap = XmlSitemap::loadByContext()) {
    $uri = xmlsitemap_sitemap_uri($sitemap);
    $path = UrlHelper::isExternal($uri['path']) ? $uri['path'] : 'base://' . $uri['path'];
    $robotstxt[] = 'Sitemap: ' . Url::fromUri($path, $uri['options'])
      ->toString();
    return $robotstxt;
  }
}

/**
 * Internal default variables config for xmlsitemap_var().
 *
 * @return array
 *   Array with config variables of xmlsitemap.settings config object.
 */
function xmlsitemap_config_variables() {
  return [
    'minimum_lifetime' => 0,
    'xsl' => 1,
    'prefetch_aliases' => 1,
    'chunk_size' => 'auto',
    'batch_limit' => 100,
    'path' => 'xmlsitemap',
    'frontpage_priority' => 1.0,
    'frontpage_changefreq' => XMLSITEMAP_FREQUENCY_DAILY,
    'lastmod_format' => XMLSITEMAP_LASTMOD_MEDIUM,
    'gz' => FALSE,
    'disable_cron_regeneration' => FALSE,
  ];
}

/**
 * Internal default variables state for xmlsitemap_var().
 *
 * @return array
 *   Array with state variables defined by xmlsitemap module.
 */
function xmlsitemap_state_variables() {
  return [
    'xmlsitemap_rebuild_needed' => FALSE,
    'xmlsitemap_regenerate_needed' => TRUE,
    'xmlsitemap_base_url' => '',
    'xmlsitemap_generated_last' => 0,
    'xmlsitemap_developer_mode' => 0,
    'max_chunks' => NULL,
    'max_filesize' => NULL,
  ];
}

/**
 * Internal implementation of variable_get().
 */
function xmlsitemap_var($name, $default = NULL) {
  $defaults =& drupal_static(__FUNCTION__);
  if (!isset($defaults)) {
    $defaults = xmlsitemap_config_variables();
    $defaults += xmlsitemap_state_variables();
  }

  // @todo Remove when stable.
  if (!isset($defaults[$name])) {
    trigger_error("Default variable for {$name} not found.");
  }
  if (\Drupal::state()
    ->get($name, NULL) === NULL) {
    return \Drupal::config('xmlsitemap.settings')
      ->get($name);
  }
  return \Drupal::state()
    ->get($name);
}

/**
 * @defgroup xmlsitemap_api XML sitemap API.
 * @{
 * This is the XML sitemap API to be used by modules wishing to work with
 * XML sitemap and/or link data.
 */

/**
 * Load an XML sitemap array from the database.
 *
 * @param mixed $smid
 *   An XML sitemap ID.
 *
 * @return \Drupal\xmlsitemap\XmlSitemapInterface
 *   The XML sitemap object.
 */
function xmlsitemap_sitemap_load($smid) {
  $sitemap = xmlsitemap_sitemap_load_multiple([
    $smid,
  ]);
  return $sitemap ? reset($sitemap) : FALSE;
}

/**
 * Load multiple XML sitemaps from the database.
 *
 * @param array|bool $smids
 *   An array of XML sitemap IDs, or FALSE to load all XML sitemaps.
 * @param array $conditions
 *   An array of conditions in the form 'field' => $value.
 *
 * @return \Drupal\xmlsitemap\XmlSitemapInterface[]
 *   An array of XML sitemap objects.
 */
function xmlsitemap_sitemap_load_multiple($smids = [], array $conditions = []) {
  if ($smids !== FALSE) {
    $conditions['smid'] = $smids;
  }
  else {
    $conditions['smid'] = NULL;
  }
  $storage = Drupal::entityTypeManager()
    ->getStorage('xmlsitemap');

  /** @var \Drupal\xmlsitemap\XmlSitemapInterface[] $sitemaps */
  $sitemaps = $storage
    ->loadMultiple($conditions['smid']);
  if (count($sitemaps) <= 0) {
    return [];
  }
  return $sitemaps;
}

/**
 * Save changes to an XML sitemap or add a new XML sitemap.
 *
 * @param Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   The XML sitemap array to be saved. If $sitemap->smid is omitted, a new
 *   XML sitemap will be added.
 *
 * @todo Save the sitemap's URL as a column?
 */
function xmlsitemap_sitemap_save(XmlSitemapInterface $sitemap) {
  $context = $sitemap->context;
  if (!isset($context) || !$context) {
    $sitemap->context = [];
  }

  // Make sure context is sorted before saving the hash.
  $sitemap
    ->setOriginalId($sitemap
    ->isNew() ? NULL : $sitemap
    ->getId());
  $sitemap
    ->setId(xmlsitemap_sitemap_get_context_hash($context));

  // If the context was changed, we need to perform additional actions.
  if (!$sitemap
    ->isNew() && $sitemap
    ->getId() != $sitemap
    ->getOriginalId()) {

    // Rename the files directory so the sitemap does not break.
    $old_sitemap = xmlsitemap_sitemap_load($sitemap
      ->getOriginalId());
    $old_dir = xmlsitemap_get_directory($old_sitemap);
    $new_dir = xmlsitemap_get_directory($sitemap);
    xmlsitemap_directory_move($old_dir, $new_dir);

    // Mark the sitemaps as needing regeneration.
    \Drupal::state()
      ->set('xmlsitemap_regenerate_needed', TRUE);
  }
  $sitemap
    ->save();
  return $sitemap;
}

/**
 * Delete an XML sitemap.
 *
 * @param string $smid
 *   An XML sitemap ID.
 */
function xmlsitemap_sitemap_delete($smid) {
  xmlsitemap_sitemap_delete_multiple([
    $smid,
  ]);
}

/**
 * Delete multiple XML sitemaps.
 *
 * @param array $smids
 *   An array of XML sitemap IDs.
 */
function xmlsitemap_sitemap_delete_multiple(array $smids) {
  if (!empty($smids)) {
    $sitemaps = xmlsitemap_sitemap_load_multiple($smids);
    foreach ($sitemaps as $sitemap) {
      $sitemap
        ->delete();
      \Drupal::moduleHandler()
        ->invokeAll('xmlsitemap_sitemap_delete', [
        $sitemap,
      ]);
    }
  }
}

/**
 * Return the expected file path for a specific sitemap chunk.
 *
 * @param Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   An XmlSitemapInterface sitemap object.
 * @param string $chunk
 *   An optional specific chunk in the sitemap. Defaults to the index page.
 *
 * @return string
 *   File path for a specific sitemap chunk.
 */
function xmlsitemap_sitemap_get_file(XmlSitemapInterface $sitemap, $chunk = 'index') {
  return xmlsitemap_get_directory($sitemap) . "/{$chunk}.xml";
}

/**
 * Find the maximum file size of all a sitemap's XML files.
 *
 * @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   The XML sitemap object.
 *
 * @return int
 *   Maximum file size in the directory.
 */
function xmlsitemap_sitemap_get_max_filesize(XmlSitemapInterface $sitemap) {
  $dir = xmlsitemap_get_directory($sitemap);
  $sitemap
    ->setMaxFileSize(0);

  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  $file_system = \Drupal::service('file_system');
  $files = $file_system
    ->scanDirectory($dir, '/\\.xml$/');
  foreach ($files as $file) {
    $sitemap
      ->setMaxFileSize(max($sitemap
      ->getMaxFileSize(), filesize($file->uri)));
  }
  return $sitemap
    ->getMaxFileSize();
}

/**
 * Returns the hash string for a context.
 *
 * @param array $context
 *   Context to be hashed.
 *
 * @return string
 *   Hash string for the context.
 */
function xmlsitemap_sitemap_get_context_hash(array &$context) {
  ksort($context);
  return Crypt::hashBase64(serialize($context));
}

/**
 * Returns the uri elements of an XML sitemap.
 *
 * @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   The sitemap represented by and XmlSitemapInterface object.
 *
 * @return array
 *   An array containing the 'path' and 'options' keys used to build the uri of
 *   the XML sitemap, and matching the signature of url().
 */
function xmlsitemap_sitemap_uri(XmlSitemapInterface $sitemap) {
  $uri['path'] = 'sitemap.xml';
  $uri['options'] = \Drupal::moduleHandler()
    ->invokeAll('xmlsitemap_context_url_options', [
    $sitemap->context,
  ]);
  $context = $sitemap->context;
  \Drupal::moduleHandler()
    ->alter('xmlsitemap_context_url_options', $uri['options'], $context);
  $uri['options'] += [
    'absolute' => TRUE,
    'base_url' => Settings::get('xmlsitemap_base_url', \Drupal::state()
      ->get('xmlsitemap_base_url')),
  ];
  return $uri;
}

/**
 * @} End of "defgroup xmlsitemap_api"
 */
function xmlsitemap_get_directory(XmlSitemapInterface $sitemap = NULL) {
  $directory =& drupal_static(__FUNCTION__);
  if (!isset($directory)) {
    $directory = \Drupal::config('xmlsitemap.settings')
      ->get('path') ?: 'xmlsitemap';
  }
  if ($sitemap != NULL && !empty($sitemap->id)) {
    return file_build_uri($directory . '/' . $sitemap->id);
  }
  else {
    return file_build_uri($directory);
  }
}

/**
 * Check that the sitemap files directory exists and is writable.
 */
function xmlsitemap_check_directory(XmlSitemapInterface $sitemap = NULL) {
  $directory = xmlsitemap_get_directory($sitemap);

  /** @var \Drupal\Core\File\FileSystemInterface $filesystem */
  $filesystem = \Drupal::service('file_system');
  $result = $filesystem
    ->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
  if (!$result) {
    \Drupal::logger('file system')
      ->error('The directory %directory does not exist or is not writable.', [
      '%directory' => $directory,
    ]);
  }
  return $result;
}

/**
 * Check all directories.
 */
function xmlsitemap_check_all_directories() {
  $directories = [];
  $sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
  foreach ($sitemaps as $sitemap) {
    $directory = xmlsitemap_get_directory($sitemap);
    $directories[$directory] = $directory;
  }

  /** @var \Drupal\Core\File\FileSystemInterface $filesystem */
  $filesystem = \Drupal::service('file_system');
  foreach ($directories as $directory) {
    $result = $filesystem
      ->prepareDirectory($directory, $filesystem::CREATE_DIRECTORY | $filesystem::MODIFY_PERMISSIONS);
    if ($result) {
      $directories[$directory] = TRUE;
    }
    else {
      $directories[$directory] = FALSE;
    }
  }
  return $directories;
}

/**
 * Clears sitemap directory.
 *
 * @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   Sitemap entity.
 * @param bool $delete
 *   If TRUE, delete the path directory afterwards.
 *
 * @return bool
 *   Returns TRUE is operation was successful, FALSE otherwise.
 */
function xmlsitemap_clear_directory(XmlSitemapInterface $sitemap = NULL, $delete = FALSE) {
  $directory = xmlsitemap_get_directory($sitemap);
  return _xmlsitemap_delete_recursive($directory, $delete);
}

/**
 * Move a directory to a new location.
 *
 * @param string $old_dir
 *   A string specifying the filepath or URI of the original directory.
 * @param string $new_dir
 *   A string specifying the filepath or URI of the new directory.
 * @param int $replace
 *   Behavior when the destination file already exists.
 *   Replace behavior when the destination file already exists.
 *
 * @return bool
 *   TRUE if the directory was moved successfully. FALSE otherwise.
 */
function xmlsitemap_directory_move($old_dir, $new_dir, $replace = FileSystemInterface::EXISTS_REPLACE) {

  /** @var \Drupal\Core\File\FileSystemInterface $filesystem */
  $filesystem = \Drupal::service('file_system');
  $success = $filesystem
    ->prepareDirectory($new_dir, $filesystem::CREATE_DIRECTORY | $filesystem::MODIFY_PERMISSIONS);
  $old_path = $filesystem
    ->realpath($old_dir);
  $new_path = $filesystem
    ->realpath($new_dir);
  if (!is_dir($old_path) || !is_dir($new_path) || !$success) {
    return FALSE;
  }
  $files = $filesystem
    ->scanDirectory($old_dir, '/.*/');
  foreach ($files as $file) {
    $file->uri_new = $new_dir . '/' . basename($file->filename);
    $success &= (bool) $filesystem
      ->move($file->uri, $file->uri_new, $replace);
  }

  // The remove the directory.
  $success &= $filesystem
    ->rmdir($old_dir);
  return $success;
}

/**
 * Recursively delete all files and folders in the specified filepath.
 *
 * This is a backport of Drupal 8's file_unmanaged_delete_recursive().
 *
 * Note that this only deletes visible files with write permission.
 *
 * @param string $path
 *   A filepath relative to the Drupal root directory.
 * @param bool $delete_root
 *   A boolean if TRUE will delete the $path directory afterwards.
 *
 * @return bool
 *   TRUE if operation was successful, FALSE otherwise.
 */
function _xmlsitemap_delete_recursive($path, $delete_root = FALSE) {

  /** @var \Drupal\Core\File\FileSystemInterface $filesystem */
  $filesystem = \Drupal::service('file_system');

  // Resolve streamwrapper URI to local path.
  $path = $filesystem
    ->realpath($path);
  if (is_dir($path)) {
    $dir = dir($path);
    while (($entry = $dir
      ->read()) !== FALSE) {
      if ($entry === '.' || $entry === '..') {
        continue;
      }
      $entry_path = $path . '/' . $entry;
      $filesystem
        ->deleteRecursive($entry_path);
    }
    $dir
      ->close();
    return $delete_root ? $filesystem
      ->rmdir($path) : TRUE;
  }
  return $filesystem
    ->delete($path);
}

/**
 * Implements hook_entity_type_build().
 */
function xmlsitemap_entity_type_build(array &$entity_types) {

  // Mark some specific core entity types as not supported by XML sitemap.
  // If a site wants to undo this, they may use hook_entity_type_alter().
  $unsupported_types = [
    // Custom blocks.
    'block_content',
    // Comments.
    'comment',
    // Shortcut items.
    'shortcut',
    // Redirects.
    'redirect',
    // Custom Token module.
    // @see https://www.drupal.org/project/token_custom/issues/3150038
    'token_custom',
  ];

  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  foreach ($unsupported_types as $entity_type_id) {
    if (isset($entity_types[$entity_type_id])) {
      $entity_types[$entity_type_id]
        ->set('xmlsitemap', FALSE);
    }
  }
}

/**
 * Determines if an entity type can be listed in the XML sitemap as links.
 *
 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
 *   The entity type.
 *
 * @return bool
 *   TRUE if the entity type can be used, or FALSE otherwise.
 */
function xmlsitemap_is_entity_type_supported(EntityTypeInterface $entity_type) {

  // If the XML sitemap status in the entity type annotation has been set then
  // return that first. This will allow modules to bypass the logic below if
  // needed.
  $status = $entity_type
    ->get('xmlsitemap');
  if ($status !== NULL) {
    return $status;
  }

  // Skip if the entity type is not a content entity type.
  if (!$entity_type instanceof ContentEntityTypeInterface) {
    return FALSE;
  }

  // Skip if the entity type is internal (and not considered public).
  if ($entity_type
    ->isInternal()) {
    return FALSE;
  }

  // Skip if the entity type does not have a canonical URL.
  if (!$entity_type
    ->hasLinkTemplate('canonical') && !$entity_type
    ->getUriCallback()) {
    return FALSE;
  }

  // Skip if the entity type as a bundle entity type but does not yet have
  // any bundles created.
  if ($entity_type
    ->getBundleEntityType() && !\Drupal::service('entity_type.bundle.info')
    ->getBundleInfo($entity_type
    ->id())) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Returns information about supported sitemap link types.
 *
 * @param mixed $type
 *   (optional) The link type to return information for. If omitted,
 *   information for all link types is returned.
 * @param mixed $reset
 *   (optional) Boolean whether to reset the static cache and do nothing. Only
 *   used for tests.
 *
 * @return array
 *   Info about sitemap link.
 *
 * @see hook_xmlsitemap_link_info()
 * @see hook_xmlsitemap_link_info_alter()
 */
function xmlsitemap_get_link_info($type = NULL, $reset = FALSE) {
  $language = \Drupal::languageManager()
    ->getCurrentLanguage();
  $link_info =& drupal_static(__FUNCTION__);
  if ($reset) {
    $link_info = NULL;
    \Drupal::service('cache_tags.invalidator')
      ->invalidateTags([
      'xmlsitemap',
    ]);
  }
  if (!isset($link_info)) {
    $cid = 'xmlsitemap:link_info:' . $language
      ->getId();
    if ($cache = \Drupal::cache()
      ->get($cid)) {
      $link_info = $cache->data;
    }
    else {
      $link_info = [];
      $entity_types = \Drupal::entityTypeManager()
        ->getDefinitions();
      foreach ($entity_types as $key => $entity_type) {
        if (!xmlsitemap_is_entity_type_supported($entity_type)) {
          continue;
        }
        $link_info[$key] = [
          'label' => $entity_type
            ->getLabel(),
          'type' => $entity_type
            ->id(),
          'base table' => $entity_type
            ->getBaseTable(),
          'bundles' => \Drupal::service('entity_type.bundle.info')
            ->getBundleInfo($entity_type
            ->id()),
          'bundle label' => $entity_type
            ->getBundleLabel(),
          'entity keys' => [
            'id' => $entity_type
              ->getKey('id'),
            'bundle' => $entity_type
              ->getKey('bundle'),
          ],
          'xmlsitemap' => [
            // Add in the default callbacks for entity types.
            'process callback' => $entity_type
              ->get('xmlsitemap')['process callback'] ?? 'xmlsitemap_xmlsitemap_process_entity_links',
            'rebuild callback' => $entity_type
              ->get('xmlsitemap')['process callback'] ?? 'xmlsitemap_rebuild_batch_fetch',
          ],
        ];
      }
      $link_info = array_merge($link_info, \Drupal::moduleHandler()
        ->invokeAll('xmlsitemap_link_info'));
      foreach ($link_info as $key => &$info) {
        $info += [
          'type' => $key,
          'base table' => FALSE,
          'bundles' => [],
        ];
        foreach ($info['bundles'] as $bundle => &$bundle_info) {
          $bundle_info += [
            'xmlsitemap' => [],
          ];
          $bundle_info['xmlsitemap'] += xmlsitemap_link_bundle_load($key, $bundle, FALSE);
        }
      }
      \Drupal::moduleHandler()
        ->alter('xmlsitemap_link_info', $link_info);

      // Sort the entity types by label.
      uasort($link_info, function ($a, $b) {

        // Put frontpage first.
        if ($a['type'] === 'frontpage') {
          return -1;
        }
        if ($b['type'] === 'frontpage') {
          return 1;
        }
        return strnatcmp($a['label'], $b['label']);
      });

      // Cache by language since this info contains translated strings.
      // Also include entity type tags since this is tied to entity and bundle
      // information.
      \Drupal::cache()
        ->set($cid, $link_info, Cache::PERMANENT, [
        'xmlsitemap',
        'entity_types',
        'entity_bundles',
      ]);
    }
  }
  if (isset($type)) {
    return isset($link_info[$type]) ? $link_info[$type] : NULL;
  }
  return $link_info;
}

/**
 * Returns enabled bundles of an entity type.
 *
 * @param string $entity_type
 *   Entity type id.
 *
 * @return array
 *   Array with entity bundles info.
 */
function xmlsitemap_get_link_type_enabled_bundles($entity_type) {
  $bundles = [];
  $info = xmlsitemap_get_link_info($entity_type);
  foreach ($info['bundles'] as $bundle => $bundle_info) {
    $settings = xmlsitemap_link_bundle_load($entity_type, $bundle);
    if (!empty($settings['status'])) {
      $bundles[] = $bundle;
    }
  }
  return $bundles;
}

/**
 * Returns statistics about specific entity links.
 *
 * @param string $entity_type_id
 *   Entity type id.
 * @param string $bundle
 *   Bundle id.
 *
 * @return array
 *   Array with statistics.
 */
function xmlsitemap_get_link_type_indexed_status($entity_type_id, $bundle = '') {
  $info = xmlsitemap_get_link_info($entity_type_id);
  $database = \Drupal::database();
  $entity_type = \Drupal::entityTypeManager()
    ->getDefinition($entity_type_id);
  $status['indexed'] = $database
    ->query("SELECT COUNT(id) FROM {xmlsitemap} WHERE type = :entity AND subtype = :bundle", [
    ':entity' => $entity_type_id,
    ':bundle' => $bundle,
  ])
    ->fetchField();
  $status['visible'] = $database
    ->query("SELECT COUNT(id) FROM {xmlsitemap} WHERE type = :entity AND subtype = :bundle AND status = 1 AND access = 1", [
    ':entity' => $entity_type_id,
    ':bundle' => $bundle,
  ])
    ->fetchField();
  try {
    $query = \Drupal::entityQuery($entity_type_id);
    if ($bundle && $entity_type
      ->hasKey('bundle')) {
      $query
        ->condition($entity_type
        ->getKey('bundle'), $bundle);
    }

    // We are only using this for totals, so we can skip the access check.
    $query
      ->accessCheck(FALSE);
    $query
      ->addTag('xmlsitemap_link_indexed_status');
    $status['total'] = $query
      ->count()
      ->execute();
    return $status;
  } catch (\Exception $e) {
    $status['total'] = 0;
  }
  return $status;
}

/**
 * Saves xmlsitemap settings for a specific bundle.
 *
 * @param string $entity
 *   Entity type id.
 * @param string $bundle
 *   Bundle id.
 * @param array $settings
 *   Settings to be saved.
 * @param bool $update_links
 *   Update bundle links after settings are saved.
 */
function xmlsitemap_link_bundle_settings_save($entity, $bundle, array $settings, $update_links = TRUE) {
  if ($update_links) {
    $old_settings = xmlsitemap_link_bundle_load($entity, $bundle);
    if ($settings['status'] != $old_settings['status']) {
      \Drupal::service('xmlsitemap.link_storage')
        ->updateMultiple([
        'status' => $settings['status'],
      ], [
        'type' => $entity,
        'subtype' => $bundle,
        'status_override' => 0,
      ]);
    }
    if ($settings['priority'] != $old_settings['priority']) {
      \Drupal::service('xmlsitemap.link_storage')
        ->updateMultiple([
        'priority' => $settings['priority'],
      ], [
        'type' => $entity,
        'subtype' => $bundle,
        'priority_override' => 0,
      ]);
    }
  }
  foreach ($settings as $key => $value) {
    \Drupal::configFactory()
      ->getEditable("xmlsitemap.settings.{$entity}.{$bundle}")
      ->set($key, $value)
      ->save();
  }
  foreach (\Drupal::languageManager()
    ->getLanguages() as $lang) {
    \Drupal::cache()
      ->delete('xmlsitemap:link_info:' . $lang
      ->getId());
  }
  xmlsitemap_get_link_info(NULL, TRUE);
}

/**
 * Renames a bundle.
 *
 * @param string $entity
 *   Entity type id.
 * @param string $bundle_old
 *   Old bundle name.
 * @param string $bundle_new
 *   New bundle name.
 */
function xmlsitemap_link_bundle_rename($entity, $bundle_old, $bundle_new) {
  if ($bundle_old != $bundle_new) {
    if (!\Drupal::config("xmlsitemap.settings.{$entity}.{$bundle_old}")
      ->isNew()) {
      $settings = xmlsitemap_link_bundle_load($entity, $bundle_old);
      \Drupal::configFactory()
        ->getEditable("xmlsitemap.settings.{$entity}.{$bundle_old}")
        ->delete();
      xmlsitemap_link_bundle_settings_save($entity, $bundle_new, $settings, FALSE);
      \Drupal::service('xmlsitemap.link_storage')
        ->updateMultiple([
        'subtype' => $bundle_new,
      ], [
        'type' => $entity,
        'subtype' => $bundle_old,
      ]);
    }
  }
}

/**
 * Loads link bundle info.
 *
 * @param string $entity
 *   Entity type id.
 * @param string $bundle
 *   Bundle info.
 * @param bool $load_bundle_info
 *   If TRUE, loads bundle info.
 *
 * @return array
 *   Info about a bundle.
 */
function xmlsitemap_link_bundle_load($entity, $bundle, $load_bundle_info = TRUE) {
  $info = [
    'entity' => $entity,
    'bundle' => $bundle,
  ];
  if ($load_bundle_info) {
    $entity_info = xmlsitemap_get_link_info($entity);
    if (isset($entity_info['bundles'][$bundle])) {
      $info['info'] = $entity_info['bundles'][$bundle];
    }
  }
  $bundle_settings = \Drupal::config("xmlsitemap.settings.{$entity}.{$bundle}")
    ->get();
  if ($bundle_settings) {
    $info += $bundle_settings;
  }
  $info += [
    'status' => XMLSITEMAP_STATUS_DEFAULT,
    'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
    'changefreq' => 0,
  ];
  return $info;
}

/**
 * Deletes all links of a specific bundle.
 *
 * @param string $entity
 *   Entity type id.
 * @param string $bundle
 *   Bundle id.
 * @param bool $delete_links
 *   If TRUE, deletes bundle links from {xmlsitemap} table.
 */
function xmlsitemap_link_bundle_delete($entity, $bundle, $delete_links = TRUE) {
  \Drupal::configFactory()
    ->getEditable("xmlsitemap.settings.{$entity}.{$bundle}")
    ->delete();
  if ($delete_links) {
    \Drupal::service('xmlsitemap.link_storage')
      ->deleteMultiple([
      'type' => $entity,
      'subtype' => $bundle,
    ]);
  }
  xmlsitemap_get_link_info(NULL, TRUE);
}

/**
 * Implements hook_entity_bundle_rename().
 */
function xmlsitemap_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
  xmlsitemap_link_bundle_rename($entity_type_id, $bundle_old, $bundle_new);
}

/**
 * Implements hook_entity_bundle_delete().
 */
function xmlsitemap_entity_bundle_delete($entity_type_id, $bundle) {
  xmlsitemap_link_bundle_delete($entity_type_id, $bundle, TRUE);
}

/**
 * Determine the frequency of updates to a link.
 *
 * @param int $interval
 *   An interval value in seconds.
 *
 * @return string
 *   A string representing the update frequency according to the sitemaps.org
 *   protocol.
 */
function xmlsitemap_get_changefreq($interval) {
  if ($interval <= 0 || !is_numeric($interval)) {
    return FALSE;
  }
  foreach (xmlsitemap_get_changefreq_options() as $value => $frequency) {
    if ($interval <= $value) {
      return $frequency;
    }
  }
  return 'never';
}

/**
 * Get the current number of sitemap chunks.
 *
 * @param int $reset
 *   If TRUE, reset number of chunks.
 *
 * @static int $chunks
 *   Number of chunks.
 *
 * @return int
 *   Number of chunks.
 */
function xmlsitemap_get_chunk_count($reset = FALSE) {
  static $chunks;
  if (!isset($chunks) || $reset) {
    $count = max(xmlsitemap_get_link_count($reset), 1);
    $chunks = ceil($count / xmlsitemap_get_chunk_size($reset));
  }
  return $chunks;
}

/**
 * Get the current number of sitemap links.
 *
 * @param bool $reset
 *   If TRUE, update current number of sitemap links.
 *
 * @static int $count
 *   Current number of sitemap links.
 *
 * @return int
 *   Returns current number of sitemap links.
 */
function xmlsitemap_get_link_count($reset = FALSE) {
  static $count;
  if (!isset($count) || $reset) {
    $count = \Drupal::database()
      ->query("SELECT COUNT(id) FROM {xmlsitemap} WHERE access = 1 AND status = 1")
      ->fetchField();
  }
  return $count;
}

/**
 * Get the sitemap chunk size.
 *
 * This function is useful with the chunk size is set to automatic as it will
 * calculate the appropriate value. Use this function instead of @code
 * xmlsitemap_var('chunk_size') @endcode when the actual value is needed.
 *
 * @param bool $reset
 *   A boolean to reset the saved, static result. Defaults to FALSE.
 *
 * @return int
 *   An integer with the number of links in each sitemap page.
 */
function xmlsitemap_get_chunk_size($reset = FALSE) {
  static $size;
  if (!isset($size) || $reset) {
    $size = xmlsitemap_var('chunk_size');
    if ($size === 'auto') {

      // Prevent divide by zero.
      $count = max(xmlsitemap_get_link_count($reset), 1);
      $size = min(ceil($count / 10000) * 5000, XMLSITEMAP_MAX_SITEMAP_LINKS);
    }
  }
  return $size;
}

/**
 * Recalculate the changefreq of a sitemap link.
 *
 * @param array $link
 *   A sitemap link array.
 */
function xmlsitemap_recalculate_changefreq(array &$link) {
  $time = \Drupal::time()
    ->getRequestTime();
  $link['changefreq'] = round(($link['changefreq'] * $link['changecount'] + ($time - $link['lastmod'])) / ($link['changecount'] + 1));
  $link['changecount']++;
  $link['lastmod'] = $time;
}

/**
 * Calculates the average interval between UNIX timestamps.
 *
 * @param array $timestamps
 *   An array of UNIX timestamp integers.
 *
 * @return int
 *   An integer of the average interval.
 */
function xmlsitemap_calculate_changefreq(array $timestamps) {
  sort($timestamps);
  $count = count($timestamps) - 1;
  $diff = 0;
  for ($i = 0; $i < $count; $i++) {
    $diff += $timestamps[$i + 1] - $timestamps[$i];
  }
  return $count > 0 ? round($diff / $count) : 0;
}

/**
 * Submit handler; Set the regenerate needed flag if variables have changed.
 *
 * This function needs to be called before system_settings_form_submit() or any
 * calls to variable_set().
 */
function xmlsitemap_form_submit_flag_regenerate(array $form, FormStateInterface $form_state) {
  $values = $form_state
    ->getValues();
  foreach ($values as $variable => $value) {
    if (\Drupal::config('xmlsitemap.settings')
      ->get($variable) == NULL) {
      $stored_value = 'not_a_variable';
    }
    else {
      $stored_value = \Drupal::config('xmlsitemap.settings')
        ->get($variable);
    }
    if (is_array($value) && !$form_state
      ->isValueEmpty('array_filter')) {
      $value = array_keys(array_filter($value));
    }
    if ($stored_value != 'not_a_variable' && $stored_value != $value) {
      \Drupal::state()
        ->set('xmlsitemap_regenerate_needed', TRUE);
      \Drupal::messenger()
        ->addWarning(t('XML sitemap settings have been modified and the files should be regenerated. You can <a href="@run-cron">run cron manually</a> to regenerate the cached files.', [
        '@run-cron' => Url::fromRoute('system.run_cron', [], [
          'query' => \Drupal::destination()
            ->getAsArray(),
        ])
          ->toString(),
      ]), FALSE);
      return;
    }
  }
}

/**
 * Add a link's XML sitemap options to the link's form.
 *
 * @param array $form
 *   Form array.
 * @param string $entity_type
 *   Entity type id.
 * @param string $bundle
 *   Bundle id.
 * @param int $id
 *   Entity id.
 *
 * @todo Add changefreq overridability.
 */
function xmlsitemap_add_form_link_options(array &$form, $entity_type, $bundle, $id) {
  if (!($link = \Drupal::service('xmlsitemap.link_storage')
    ->load($entity_type, $id))) {
    $link = [];
  }
  $bundle_info = xmlsitemap_link_bundle_load($entity_type, $bundle);
  $link += [
    'access' => 1,
    'status' => $bundle_info['status'],
    'status_default' => $bundle_info['status'],
    'status_override' => 0,
    'priority' => $bundle_info['priority'],
    'priority_default' => $bundle_info['priority'],
    'priority_override' => 0,
    'changefreq' => $bundle_info['changefreq'],
  ];
  $currentUser = \Drupal::currentUser();
  $admin_permission = \Drupal::entityTypeManager()
    ->getDefinition($entity_type)
    ->getAdminPermission();
  $form['xmlsitemap'] = [
    '#type' => 'details',
    '#tree' => TRUE,
    '#title' => t('XML sitemap'),
    '#collapsible' => TRUE,
    '#collapsed' => !$link['status_override'] && !$link['priority_override'],
    '#access' => $currentUser
      ->hasPermission('administer xmlsitemap') || $admin_permission && $currentUser
      ->hasPermission($admin_permission),
    '#group' => 'advanced',
  ];

  // Show a warning if the link is not accessible and will not be included in
  // the sitemap.
  if ($id && !$link['access']) {
    $form['xmlsitemap']['warning'] = [
      '#type' => 'markup',
      '#prefix' => '<p><strong>',
      '#suffix' => '</strong></p>',
      '#value' => 'This item is not currently visible to anonymous users, so it will not be included in the sitemap.',
    ];
  }

  // Status field (inclusion/exclusion)
  $form['xmlsitemap']['status'] = [
    '#type' => 'select',
    '#title' => t('Inclusion'),
    '#options' => xmlsitemap_get_status_options($link['status_default']),
    '#default_value' => $link['status_override'] ? $link['status'] : 'default',
  ];
  $form['xmlsitemap']['status_default'] = [
    '#type' => 'value',
    '#value' => $link['status_default'],
  ];
  $form['xmlsitemap']['status_override'] = [
    '#type' => 'value',
    '#value' => $link['status_override'],
  ];

  // Priority field.
  $form['xmlsitemap']['priority'] = [
    '#type' => 'select',
    '#title' => t('Priority'),
    '#options' => xmlsitemap_get_priority_options($link['priority_default']),
    '#default_value' => $link['priority_override'] ? number_format($link['priority'], 1) : 'default',
    '#description' => t('The priority of this URL relative to other URLs on your site.'),
    '#states' => [
      'invisible' => [
        'select[name="xmlsitemap[status]"]' => [
          'value' => '0',
        ],
      ],
    ],
  ];
  $form['xmlsitemap']['changefreq'] = [
    '#type' => 'select',
    '#title' => t('Change frequency'),
    '#options' => xmlsitemap_get_changefreq_options(),
    '#default_value' => $link['changefreq'],
    '#description' => t('Select the frequency of changes.'),
    '#states' => [
      'invisible' => [
        'select[name="xmlsitemap[status]"]' => [
          'value' => '0',
        ],
      ],
    ],
  ];
  if (!$link['status_default']) {

    // If the default status is excluded, add a visible state on the include
    // override option.
    $form['xmlsitemap']['priority']['#states']['visible'] = [
      'select[name="xmlsitemap[status]"]' => [
        'value' => '1',
      ],
    ];
  }
  $form['xmlsitemap']['priority_default'] = [
    '#type' => 'value',
    '#value' => $link['priority_default'],
  ];
  $form['xmlsitemap']['priority_override'] = [
    '#type' => 'value',
    '#value' => $link['priority_override'],
  ];
  array_unshift($form['actions']['submit']['#submit'], 'xmlsitemap_process_form_link_options');
  if (!empty($form['actions']['publish']['#submit'])) {
    array_unshift($form['actions']['publish']['#submit'], 'xmlsitemap_process_form_link_options');
  }
}

/**
 * Submit callback for the entity form to save.
 */
function xmlsitemap_process_form_link_options(array $form, FormStateInterface $form_state) {
  $link = $form_state
    ->getValue('xmlsitemap');
  $fields = [
    'status' => XMLSITEMAP_STATUS_DEFAULT,
    'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
  ];
  foreach ($fields as $field => $default) {
    if ($link[$field] === 'default') {
      $link[$field] = isset($link[$field . '_default']) ? $link[$field . '_default'] : $default;
      $link[$field . '_override'] = 0;
    }
    else {
      $link[$field . '_override'] = 1;
    }
  }
  $form_state
    ->setValue('xmlsitemap', $link);
  $entity = $form_state
    ->getFormObject()
    ->getEntity();
  $entity->xmlsitemap = $form_state
    ->getValue('xmlsitemap');
}

/**
 * Submit callback for link bundle settings.
 */
function xmlsitemap_link_bundle_settings_form_submit($form, &$form_state) {
  $entity = $form['xmlsitemap']['#entity'];
  $bundle = $form['xmlsitemap']['#bundle'];

  // Handle new bundles by fetching the proper bundle key value from the form
  // state values.
  if (empty($bundle)) {
    $entity_info = $form['xmlsitemap']['#entity_info'];
    if (isset($entity_info['bundle keys']['bundle'])) {
      $bundle_key = $entity_info['bundle keys']['bundle'];
      if ($form_state
        ->hasValue($bundle_key)) {
        $bundle = $form_state
          ->getValue($bundle_key);
        $form['xmlsitemap']['#bundle'] = $bundle;
      }
    }
  }
  xmlsitemap_link_bundle_settings_save($entity, $bundle, $form_state
    ->getValue('xmlsitemap'));
  $entity_info = $form['xmlsitemap']['#entity_info'];
  if (!empty($form['xmlsitemap']['#show_message'])) {
    \Drupal::messenger()
      ->addStatus(t('XML sitemap settings for the @bundle-label %bundle have been saved.', [
      '@bundle-label' => mb_strtolower($entity_info['bundle label']),
      '%bundle' => $entity_info['bundles'][$bundle]['label'],
    ]));
  }

  // Unset the form values since we have already saved the bundle settings and
  // we don't want these values to get saved as variables in-case this form
  // also uses system_settings_form().
  $form_state
    ->unsetValue('xmlsitemap');
}

/**
 * Gets xmlsitemap frequency options.
 *
 * @return array
 *   Frequency options.
 *
 * @todo Document this function.
 * @todo Make these translatable
 */
function xmlsitemap_get_changefreq_options() {
  return [
    XMLSITEMAP_FREQUENCY_ALWAYS => 'always',
    XMLSITEMAP_FREQUENCY_HOURLY => 'hourly',
    XMLSITEMAP_FREQUENCY_DAILY => 'daily',
    XMLSITEMAP_FREQUENCY_WEEKLY => 'weekly',
    XMLSITEMAP_FREQUENCY_MONTHLY => 'monthly',
    XMLSITEMAP_FREQUENCY_YEARLY => 'yearly',
  ];
}

/**
 * Load a language object by its language code.
 *
 * @param string $language
 *   A language code. If not provided the default language will be returned.
 *
 * @return \Drupal\core\Language\LanguageInterface
 *   A language object.
 *
 * @todo Remove when https://www.drupal.org/node/660736 is fixed in Drupal core.
 */
function xmlsitemap_language_load($language = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
  $languages =& drupal_static(__FUNCTION__);
  if (!isset($languages)) {
    $languages = \Drupal::languageManager()
      ->getLanguages();
    $languages[LanguageInterface::LANGCODE_NOT_SPECIFIED] = NULL;
  }
  return isset($languages[$language]) ? $languages[$language] : NULL;
}

/**
 * @defgroup xmlsitemap_context_api XML sitemap API for sitemap contexts.
 * @{
 */

/**
 * Gets info about a context.
 *
 * @param string $context
 *   The context.
 * @param bool $reset
 *   If TRUE, resets context info.
 *
 * @return array
 *   Array with info.
 */
function xmlsitemap_get_context_info($context = NULL, $reset = FALSE) {
  $language = \Drupal::languageManager()
    ->getCurrentLanguage();
  $info =& drupal_static(__FUNCTION__);
  if ($reset) {
    $info = NULL;
  }
  elseif ($cached = \Drupal::cache()
    ->get('xmlsitemap:context_info:' . $language
    ->getId())) {
    $info = $cached->data;
  }
  if (!isset($info)) {
    $info = \Drupal::moduleHandler()
      ->invokeAll('xmlsitemap_context_info');
    \Drupal::moduleHandler()
      ->alter('xmlsitemap_context_info', $info);
    ksort($info);

    // Cache by language since this info contains translated strings.
    \Drupal::cache()
      ->set('xmlsitemap:context_info:' . $language
      ->getId(), $info, Cache::PERMANENT, [
      'xmlsitemap',
    ]);
  }
  if (isset($context)) {
    return isset($info[$context]) ? $info[$context] : NULL;
  }
  return $info;
}

/**
 * Get the sitemap context of the current request.
 *
 * @return array
 *   Current context.
 */
function xmlsitemap_get_current_context() {
  $context =& drupal_static(__FUNCTION__);
  if (!isset($context)) {
    $context = \Drupal::moduleHandler()
      ->invokeAll('xmlsitemap_context');
    \Drupal::moduleHandler()
      ->alter('xmlsitemap_context', $context);
    ksort($context);
  }
  return $context;
}

/**
 * Gets summary about a context.
 *
 * @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   Sitemap entity.
 * @param string $context_key
 *   Key for the context.
 * @param array $context_info
 *   Info about the context.
 *
 * @return string
 *   Context summary.
 */
function _xmlsitemap_sitemap_context_summary(XmlSitemapInterface $sitemap, $context_key, array $context_info) {
  $context_value = isset($sitemap->context[$context_key]) ? $sitemap->context[$context_key] : NULL;
  if (!isset($context_value)) {
    return t('Default');
  }
  elseif (!empty($context_info['summary callback'])) {
    return $context_info['summary callback']($context_value);
  }
  else {
    return $context_value;
  }
}

/**
 * @} End of "defgroup xmlsitemap_context_api"
 */

/**
 * Run a not-progressive batch operation.
 */
function xmlsitemap_run_unprogressive_batch() {
  $batch = batch_get();
  $lock = \Drupal::lock();
  if (!empty($batch)) {

    // If there is already something in the batch, don't run.
    return FALSE;
  }
  $args = func_get_args();
  $batch_callback = array_shift($args);
  if (!$lock
    ->acquire($batch_callback)) {
    return FALSE;
  }

  // Attempt to increase the execution time.
  Environment::setTimeLimit(240);

  // Build the batch array.
  $batch = call_user_func_array($batch_callback, $args);
  batch_set($batch);

  // We need to manually set the progressive variable again.
  // @todo Remove when https://www.drupal.org/node/638712 is fixed.
  $batch =& batch_get();
  $batch['progressive'] = FALSE;

  // Run the batch process.
  if (PHP_SAPI === 'cli' && function_exists('drush_backend_batch_process')) {
    drush_backend_batch_process();
  }
  else {
    batch_process();
  }
  $lock
    ->release($batch_callback);
  return TRUE;
}

/**
 * Gets a link from url.
 *
 * @param string $url
 *   Url of the link.
 * @param array $options
 *   Extra options of the url such as 'query'.
 *
 * @static string $destination
 *   Destination option.
 *
 * @return array
 *   An array representing a link.
 */
function xmlsitemap_get_operation_link($url, array $options = []) {
  static $destination;
  if (!isset($destination)) {
    $destination = \Drupal::destination()
      ->getAsArray();
  }
  $link = [
    'href' => $url,
  ] + $options;
  $link += [
    'query' => $destination,
  ];
  return $link;
}

/**
 * Returns HTML for an administration settings table.
 *
 * @param array $variables
 *   An associative array containing:
 *   - build: A render element representing a table of bundle content language
 *     settings for a particular entity type.
 *
 * @return string
 *   HTML content.
 *
 * @ingroup themable
 */
function theme_xmlsitemap_content_settings_table(array $variables) {
  return '<h4>' . $variables['build']['#title'] . '</h4>' . \Drupal::service('renderer')
    ->render($variables['build']);
}

/**
 * Implements hook_form_alter().
 */
function xmlsitemap_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  $form_object = $form_state
    ->getFormObject();
  if ($form_object instanceof ContentEntityFormInterface) {
    $entity = $form_object
      ->getEntity();

    // Some entity types use 'default' for add/edit forms.
    $operations = [
      'default',
      'edit',
    ];
    if ($entity
      ->getEntityTypeId() === 'user') {
      $operations[] = 'register';
    }
    if (in_array($form_object
      ->getOperation(), $operations, TRUE) && xmlsitemap_link_bundle_check_enabled($entity
      ->getEntityTypeId(), $entity
      ->bundle())) {
      xmlsitemap_add_form_link_options($form, $entity
        ->getEntityTypeId(), $entity
        ->bundle(), $entity
        ->id());
      $form['xmlsitemap']['#weight'] = 10;
    }
  }
}

/**
 * Implements hook_xmlsitemap_index_links().
 */
function xmlsitemap_xmlsitemap_index_links($limit) {
  $entity_type_manager = \Drupal::entityTypeManager();
  $entity_types = $entity_type_manager
    ->getDefinitions();
  foreach ($entity_types as $entity_type_id => $entity_type) {

    // If an entity type is not supported it will not have link info.
    $info = xmlsitemap_get_link_info($entity_type_id);
    if (empty($info)) {
      continue;
    }
    $bundles = xmlsitemap_get_link_type_enabled_bundles($entity_type_id);
    if (empty($bundles)) {
      continue;
    }
    try {
      $query = $entity_type_manager
        ->getStorage($entity_type_id)
        ->getQuery();
      $query
        ->range(0, $limit);
      if (!empty($info['entity keys']['bundle'])) {
        $query
          ->condition($info['entity keys']['bundle'], $bundles, 'IN');
      }

      // Perform a subquery against the xmlsitemap table to ensure that we are
      // only looking for items that we have not already indexed.
      $subquery = \Drupal::database()
        ->select('xmlsitemap', 'x');
      $subquery
        ->addField('x', 'id');
      $subquery
        ->condition('type', $entity_type_id);

      // If the storage for this entity type is against a SQL backend, perform
      // a direct subquery condition to avoid needing to load all the IDs.
      if ($query instanceof \Drupal\Core\Entity\Query\Sql\Query) {
        $query
          ->condition($info['entity keys']['id'], $subquery, 'NOT IN');
      }
      else {
        $query
          ->condition($info['entity keys']['id'], $subquery
          ->execute()
          ->fetchCol(), 'NOT IN');
      }

      // Access for entities is checked individually for the anonymous user
      // when each item is processed. We can skip the access check for the
      // query.
      $query
        ->accessCheck(FALSE);
      $query
        ->addTag('xmlsitemap_index_links');
      if ($ids = $query
        ->execute()) {

        // Chunk the array into batch sizes.
        $chunks = array_chunk($ids, \Drupal::config('xmlsitemap.settings')
          ->get('batch_limit'));
        foreach ($chunks as $chunk) {
          $info['xmlsitemap']['process callback']($entity_type_id, $chunk);
        }
        \Drupal::logger('xmlsitemap')
          ->info('Indexed @count new @type items.', [
          '@count' => count($ids),
          '@type' => $entity_type_id,
        ]);
      }
    } catch (\Exception $e) {
      watchdog_exception('xmlsitemap', $e);
    }
  }
}

/**
 * Process sitemap links.
 *
 * @param string $entity_type_id
 *   The entity type to process.
 * @param array $entity_ids
 *   Entity IDs to be processed.
 */
function xmlsitemap_xmlsitemap_process_entity_links($entity_type_id, array $entity_ids) {

  /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
  $entities = \Drupal::entityTypeManager()
    ->getStorage($entity_type_id)
    ->loadMultiple($entity_ids);
  foreach ($entities as $entity) {
    xmlsitemap_xmlsitemap_process_entity_link($entity);
  }

  // Reset the entity cache afterwards to clear out some memory.
  \Drupal::entityTypeManager()
    ->getStorage($entity_type_id)
    ->resetCache();
}

/**
 * Process sitemap link for an entity.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity object.
 */
function xmlsitemap_xmlsitemap_process_entity_link(EntityInterface $entity) {

  /** @var \Drupal\xmlsitemap\XmlSitemapLinkStorageInterface $link_storage */
  $link_storage = \Drupal::service('xmlsitemap.link_storage');
  if ($entity instanceof ContentEntityInterface) {

    // Generate an entry for each translation.
    $translation_languages = $entity
      ->getTranslationLanguages();
    foreach ($translation_languages as $langcode => $language) {
      $translation = $entity
        ->getTranslation($langcode);
      $link = $link_storage
        ->create($translation);
      $context = [
        $link['type'] => $entity,
        'entity' => $entity,
      ];
      $link_storage
        ->save($link, $context);
    }

    // Check if there are any removed language translations.
    if (isset($entity->original)) {
      $original_translation_languages = $entity->original
        ->getTranslationLanguages();
      $removed_translation_languages = array_diff(array_keys($original_translation_languages), array_keys($translation_languages));
      if (!empty($removed_translation_languages)) {
        $link_storage
          ->deleteMultiple([
          'type' => $entity
            ->getEntityTypeId(),
          'id' => $entity
            ->id(),
          'language' => $removed_translation_languages,
        ]);
      }
    }
  }
  else {
    $link = $link_storage
      ->create($entity);
    $context = [
      $link['type'] => $entity,
      'entity' => $entity,
    ];
    $link_storage
      ->save($link, $context);
  }
}

/**
 * Implements hook_entity_insert().
 */
function xmlsitemap_entity_insert(EntityInterface $entity) {
  if (xmlsitemap_link_bundle_check_enabled($entity
    ->getEntityTypeId(), $entity
    ->bundle())) {
    xmlsitemap_xmlsitemap_process_entity_link($entity);
  }
}

/**
 * Implements hook_entity_update().
 */
function xmlsitemap_entity_update(EntityInterface $entity) {
  if (xmlsitemap_link_bundle_check_enabled($entity
    ->getEntityTypeId(), $entity
    ->bundle())) {
    xmlsitemap_xmlsitemap_process_entity_link($entity);
  }
}

/**
 * Implements hook_entity_delete().
 */
function xmlsitemap_entity_delete(EntityInterface $entity) {
  $langcode = $entity
    ->language() ? $entity
    ->language()
    ->getId() : NULL;
  \Drupal::service('xmlsitemap.link_storage')
    ->delete($entity
    ->getEntityTypeId(), $entity
    ->id(), $langcode);
}

/**
 * Implements hook_entity_translation_delete().
 */
function xmlsitemap_entity_translation_delete(EntityInterface $translation) {
  \Drupal::service('xmlsitemap.link_storage')
    ->delete($translation
    ->getEntityTypeId(), $translation
    ->id(), $translation
    ->language()
    ->getId());
}

/**
 * Implements hook_xmlsitemap_context_info() for language module.
 */
function language_xmlsitemap_context_info() {
  $context['language'] = [
    'label' => t('Language'),
    'summary callback' => 'language_name',
    'default' => \Drupal::languageManager()
      ->getDefaultLanguage()
      ->getId(),
  ];
  return $context;
}

/**
 * Implements hook_xmlsitemap_context() for language module.
 */
function language_xmlsitemap_context() {
  $language = \Drupal::languageManager()
    ->getCurrentLanguage();
  $context['language'] = $language
    ->getId();
  return $context;
}

/**
 * Implements hook_xmlsitemap_context_url_options() for language module.
 */
function language_xmlsitemap_context_url_options(array $context) {
  $options = [];
  if (isset($context['language'])) {
    $options['language'] = xmlsitemap_language_load($context['language']);
  }
  return $options;
}

/**
 * Implements hook_form_FORM_ID_alter() for language module.
 */
function language_form_xmlsitemap_sitemap_edit_form_alter(&$form, FormStateInterface $form_state) {
  $options = [];
  $languages = \Drupal::languageManager()
    ->getLanguages();
  foreach ($languages as $language_key => $language) {
    $options[$language_key] = $language
      ->getName();
  }
  $form['context']['language'] = [
    '#type' => 'select',
    '#title' => t('Language'),
    '#options' => $options,
    '#default_value' => isset($form['#entity']->context['language']) ? $form['#entity']->context['language'] : \Drupal::languageManager()
      ->getDefaultLanguage()
      ->getId(),
    '#description' => t('Most sites should only need a sitemap for their default language since translated content is now added to the sitemap using alternate links. If you truly need a sitemap for multiple languages, it is still possible to do so.'),
  ];
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Set the regeneration needed flag if settings are changed.
 */
function xmlsitemap_form_language_admin_overview_form_alter(&$form, FormStateInterface $form_state) {
  array_unshift($form['#submit'], 'xmlsitemap_form_submit_flag_regenerate');
}

/**
 * Implements hook_query_TAG_alter() for language module.
 */
function language_query_xmlsitemap_generate_alter(AlterableInterface $query) {
  $mode = \Drupal::config('xmlsitemap.settings')
    ->get('i18n_selection_mode') ?: 'simple';
  if ($mode === 'off') {
    return;
  }

  /** @var \Drupal\xmlsitemap\XmlSitemapInterface $sitemap */
  $sitemap = $query
    ->getMetaData('sitemap');

  // Get languages to simplify query building.
  $default = \Drupal::languageManager()
    ->getDefaultLanguage()
    ->getId();
  $current = isset($sitemap->context['language']) ? $sitemap->context['language'] : $default;
  if ($mode == 'mixed' && $current == $default) {

    // If mode is mixed but current = default, is the same as 'simple'.
    $mode = 'simple';
  }
  switch ($mode) {
    case 'simple':

      // Current language and language neutral.
      $query
        ->condition('language', [
        $current,
        LanguageInterface::LANGCODE_NOT_SPECIFIED,
      ], 'IN');
      break;
    case 'mixed':

      // Mixed current language (if available) or default language (if not) and
      // language neutral.
      $query
        ->condition('language', [
        $current,
        $default,
        LanguageInterface::LANGCODE_NOT_SPECIFIED,
      ], 'IN');
      break;
    case 'default':

      // Only default language and language neutral.
      $query
        ->condition('language', [
        $default,
        LanguageInterface::LANGCODE_NOT_SPECIFIED,
      ], 'IN');
      break;
    case 'strict':

      // Only current language (for nodes), simple for all other types.
      $node_condition = new Condition('AND');
      $node_condition
        ->condition('type', 'node', '=');
      $node_condition
        ->condition('language', $current, '=');
      $normal_condition = new Condition('AND');
      $normal_condition
        ->condition('type', 'node', '<>');
      $normal_condition
        ->condition('language', [
        $current,
        LanguageInterface::LANGCODE_NOT_SPECIFIED,
      ], 'IN');
      $condition = new Condition('OR');
      $condition
        ->condition($node_condition);
      $condition
        ->condition($normal_condition);
      $query
        ->condition($condition);
      break;
    case 'off':

      // All content. No language conditions apply.
      break;
  }
}

/**
 * Implements hook_xmlsitemap_element_alter() for language module.
 */
function language_xmlsitemap_element_alter(array &$element, array $link, XmlSitemapInterface $sitemap) {

  // Add alternate links for each language for generic links.
  if ($link['langcode'] === LanguageInterface::LANGCODE_NOT_SPECIFIED) {
    $languages = \Drupal::languageManager()
      ->getLanguages();
    unset($languages[$sitemap->context['language']]);
    foreach ($languages as $language) {
      _xmlsitemap_element_add_alternate_lang($element, $link['loc'], $language, $sitemap);
    }
  }
}

/**
 * Implements hook_xmlsitemap_element_alter() for content_translation module.
 */
function content_translation_xmlsitemap_element_alter(array &$element, array $link, XmlSitemapInterface $sitemap) {
  if ($link['langcode'] === LanguageInterface::LANGCODE_NOT_SPECIFIED) {
    return;
  }

  // @todo Cache this information longer than a static method.
  $loc_whitelist =& drupal_static(__FUNCTION__);
  if (!isset($loc_whitelist)) {
    $loc_whitelist = FALSE;
    if ($types = array_keys(\Drupal::service('content_translation.manager')
      ->getSupportedEntityTypes())) {
      $query = \Drupal::database()
        ->select('xmlsitemap', 'x');
      $query
        ->distinct(TRUE);
      $query
        ->addExpression('SUBSTRING_INDEX(x.loc, :slash, 2)', 'path', [
        ':slash' => '/',
      ]);
      $query
        ->condition('x.type', $types, 'IN');
      $whitelist_paths = $query
        ->execute()
        ->fetchCol();
      $loc_whitelist = '/^(' . implode('|', array_map(function ($path) {
        return preg_quote($path . '/', '/') . '.*';
      }, $whitelist_paths)) . ')/';
    }
  }
  if ($loc_whitelist && preg_match($loc_whitelist, $link['loc'])) {
    $query = \Drupal::database()
      ->select('xmlsitemap', 'x');
    $query
      ->fields('x', [
      'loc',
      'language',
    ]);
    $query
      ->condition('x.loc', $link['loc']);
    $query
      ->condition('x.language', [
      $link['langcode'],
      LanguageInterface::LANGCODE_NOT_SPECIFIED,
    ], 'NOT IN');
    $query
      ->condition('x.access', 1);
    $query
      ->condition('x.status', 1);
    $language_links = $query
      ->execute();
    while ($language_link = $language_links
      ->fetchAssoc()) {
      _xmlsitemap_element_add_alternate_lang($element, $language_link['loc'], xmlsitemap_language_load($language_link['language']), $sitemap);
    }
  }
}

/**
 * Adds alternate language links to a sitemap element.
 *
 * @param array $element
 *   The sitemap element from hook_xmlsitemap_element_alter().
 * @param string $loc
 *   The location for the URL.
 * @param \Drupal\Core\Language\LanguageInterface $language
 *   The alternate language.
 * @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   The current sitemap being generated for the element.
 *
 * @internal
 */
function _xmlsitemap_element_add_alternate_lang(array &$element, $loc, LanguageInterface $language, XmlSitemapInterface $sitemap) {
  $url_options = $sitemap->uri['options'];
  $url_options += [
    'absolute' => TRUE,
    'base_url' => rtrim(Settings::get('xmlsitemap_base_url', \Drupal::state()
      ->get('xmlsitemap_base_url')), '/'),
    'alias' => FALSE,
  ];
  $alternate_href = Url::fromUri('internal:' . $loc, [
    'language' => $language,
  ] + $url_options)
    ->toString();
  if ($alternate_href !== $element['loc']) {
    $element[] = [
      'key' => 'xhtml:link',
      'attributes' => [
        'rel' => 'alternate',
        'hreflang' => $language
          ->getId(),
        'href' => $alternate_href,
      ],
    ];
  }
}

/**
 * Implements hook_xmlsitemap_link_info().
 */
function xmlsitemap_xmlsitemap_link_info() {
  return [
    'frontpage' => [
      'label' => t('Frontpage'),
      'xmlsitemap' => [
        'settings callback' => 'xmlsitemap_link_frontpage_settings',
      ],
    ],
  ];
}

/**
 * Implements hook_xmlsitemap_link_alter().
 */
function xmlsitemap_xmlsitemap_link_alter(&$link) {

  // Alter the frontpage priority.
  if ($link['type'] == 'frontpage' || $link['loc'] == '/' || $link['loc'] == Drupal::config('system.site')
    ->get('page.front')) {
    $link['priority'] = \Drupal::config('xmlsitemap.settings')
      ->get('frontpage_priority');
    $link['changefreq'] = \Drupal::config('xmlsitemap.settings')
      ->get('frontpage_changefreq');
  }
}

/**
 * Implements hook_xmlsitemap_links().
 */
function xmlsitemap_xmlsitemap_links() {

  // Frontpage link.
  $links[] = [
    'type' => 'frontpage',
    'id' => 0,
    'loc' => '/',
  ];
  return $links;
}

/**
 * Implements hook_xmlsitemap_sitemap_operations().
 */
function xmlsitemap_xmlsitemap_sitemap_operations() {
  $operations['update'] = [
    'label' => t('Update cached files'),
    'action past' => t('Updated'),
    'callback' => 'xmlsitemap_sitemap_multiple_update',
  ];
  return $operations;
}

/**
 * XML sitemap link type settings callback for frontpage link entity.
 *
 * @param array $form
 *   Form array.
 *
 * @return array
 *   Updated form.
 */
function xmlsitemap_link_frontpage_settings(array &$form) {
  if (\Drupal::currentUser()
    ->hasPermission('administer site configuration')) {
    $form['#description'] = t('The front page path can be changed in the <a href="@url-frontpage">site information configuration</a>.', [
      '@url-frontpage' => Url::fromRoute('system.site_information_settings')
        ->toString(),
    ]);
  }
  $form['frontpage_priority'] = [
    '#type' => 'select',
    '#title' => t('Priority'),
    '#options' => xmlsitemap_get_priority_options(),
    '#default_value' => \Drupal::config('xmlsitemap.settings')
      ->get('frontpage_priority'),
  ];
  $form['frontpage_changefreq'] = [
    '#type' => 'select',
    '#title' => t('Change frequency'),
    '#options' => xmlsitemap_get_changefreq_options(),
    '#default_value' => \Drupal::config('xmlsitemap.settings')
      ->get('frontpage_changefreq'),
  ];
  return $form;
}

/**
 * XML sitemap operation callback; regenerate sitemap files using the batch API.
 *
 * @param array $smids
 *   An array of XML sitemap IDs.
 *
 * @see xmlsitemap_regenerate_batch()
 */
function xmlsitemap_sitemap_multiple_update(array $smids) {
  $batch = xmlsitemap_regenerate_batch($smids);
  batch_set($batch);
}

/**
 * Add a table summary for an entity and its bundles.
 *
 * @param array $form
 *   Form array.
 * @param string $entity
 *   Entity type id.
 * @param array $entity_info
 *   Info about the entity type.
 */
function xmlsitemap_add_form_entity_summary(array &$form, $entity, array $entity_info) {
  $priorities = xmlsitemap_get_priority_options(NULL, FALSE);
  $statuses = xmlsitemap_get_status_options(NULL);
  $rows = [];
  $totals = [
    'total' => 0,
    'indexed' => 0,
    'visible' => 0,
  ];
  foreach ($entity_info['bundles'] as $bundle => $bundle_info) {

    // Fetch current per-bundle link total and indexed counts.
    if (!xmlsitemap_link_bundle_check_enabled($entity, $bundle)) {
      continue;
    }
    $status = xmlsitemap_get_link_type_indexed_status($entity, $bundle);
    $totals['total'] += $status['total'];
    $totals['indexed'] += $status['indexed'];
    $totals['visible'] += $status['visible'];
    $rows[] = [
      Link::createFromRoute($bundle_info['label'], 'xmlsitemap.admin_settings_bundle', [
        'entity' => $entity,
        'bundle' => $bundle,
      ]),
      $statuses[$bundle_info['xmlsitemap']['status'] ? 1 : 0],
      $priorities[number_format($bundle_info['xmlsitemap']['priority'], 1)],
      $status['total'],
      $status['indexed'],
      $status['visible'],
    ];
  }
  if ($rows) {
    $header = [
      isset($entity_info['bundle label']) ? $entity_info['bundle label'] : '',
      t('Inclusion'),
      t('Priority'),
      t('Available'),
      t('Indexed'),
      t('Visible'),
    ];
    $rows[] = [
      [
        'data' => t('Totals'),
        'colspan' => 3,
        'header' => TRUE,
      ],
      [
        'data' => $totals['total'],
        'header' => TRUE,
      ],
      [
        'data' => $totals['indexed'],
        'header' => TRUE,
      ],
      [
        'data' => $totals['visible'],
        'header' => TRUE,
      ],
    ];
    $form['summary'] = [
      '#theme' => 'table',
      '#header' => $header,
      '#rows' => $rows,
    ];
  }
}

/**
 * Add the link type XML sitemap options to the link type's form.
 *
 * Caller is responsible for ensuring xmlsitemap_link_bundle_settings_save()
 * is called during submission.
 *
 * @param array $form
 *   Form array.
 * @param Drupal\Core\Form\FormStateInterface $form_state
 *   Form state array.
 * @param string $entity
 *   Entity type id.
 * @param string $bundle
 *   Bundle id.
 */
function xmlsitemap_add_link_bundle_settings(array &$form, FormStateInterface $form_state, $entity, $bundle) {
  $entity_info = xmlsitemap_get_link_info($entity);
  $bundle_info = xmlsitemap_link_bundle_load($entity, $bundle);
  $form['xmlsitemap'] = [
    '#type' => 'details',
    '#title' => t('XML sitemap'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#access' => \Drupal::currentUser()
      ->hasPermission('administer xmlsitemap'),
    '#group' => 'advanced',
    '#tree' => TRUE,
    '#entity' => $entity,
    '#bundle' => $bundle,
    '#entity_info' => $entity_info,
    '#bundle_info' => $bundle_info,
  ];
  $form['xmlsitemap']['description'] = [
    '#prefix' => '<div class="description">',
    '#suffix' => '</div>',
    '#markup' => t('Changing these type settings will affect any items of this type that have either inclusion or priority set to default.'),
  ];
  $form['xmlsitemap']['status'] = [
    '#type' => 'select',
    '#title' => t('Inclusion'),
    '#options' => xmlsitemap_get_status_options(),
    '#default_value' => (int) $bundle_info['status'],
  ];
  $form['xmlsitemap']['priority'] = [
    '#type' => 'select',
    '#title' => t('Default priority'),
    '#options' => xmlsitemap_get_priority_options(),
    '#default_value' => $bundle_info['priority'],
    '#states' => [
      'invisible' => [
        'select[name="xmlsitemap[status]"]' => [
          'value' => '0',
        ],
      ],
    ],
  ];
  $form['xmlsitemap']['changefreq'] = [
    '#type' => 'select',
    '#title' => t('Default change frequency'),
    '#options' => xmlsitemap_get_changefreq_options(),
    '#default_value' => $bundle_info['changefreq'],
    '#states' => [
      'invisible' => [
        'select[name="xmlsitemap[status]"]' => [
          'value' => '0',
        ],
      ],
    ],
  ];
}

/**
 * Get a list of priority options.
 *
 * @param string $default
 *   Include a 'default' option.
 * @param bool $guides
 *   Add helpful indicators for the highest, middle and lowest values.
 *
 * @return array
 *   An array of options.
 */
function xmlsitemap_get_priority_options($default = NULL, $guides = TRUE) {
  $options = [];
  $priorities = [
    '1.0' => t('1.0'),
    '0.9' => t('0.9'),
    '0.8' => t('0.8'),
    '0.7' => t('0.7'),
    '0.6' => t('0.6'),
    '0.5' => t('0.5'),
    '0.4' => t('0.4'),
    '0.3' => t('0.3'),
    '0.2' => t('0.2'),
    '0.1' => t('0.1'),
    '0.0' => t('0.0'),
  ];
  if (isset($default)) {
    $default = number_format($default, 1);
    $options['default'] = t('Default (@value)', [
      '@value' => $priorities[$default],
    ]);
  }

  // Add the rest of the options.
  $options += $priorities;
  if ($guides) {
    $options['1.0'] .= ' ' . t('(highest)');
    $options['0.5'] .= ' ' . t('(normal)');
    $options['0.0'] .= ' ' . t('(lowest)');
  }
  return $options;
}

/**
 * Get a list of priority options.
 *
 * @param string $default
 *   Include a 'default' option.
 *
 * @return array
 *   An array of options.
 *
 * @see _xmlsitemap_translation_strings()
 */
function xmlsitemap_get_status_options($default = NULL) {
  $options = [];
  $statuses = [
    1 => t('Included'),
    0 => t('Excluded'),
  ];
  if (isset($default)) {
    $default = $default ? 1 : 0;
    $options['default'] = t('Default (@value)', [
      '@value' => mb_strtolower($statuses[$default]),
    ]);
  }
  $options += $statuses;
  return $options;
}

/**
 * Get the sitemap chunk/page of the current request.
 *
 * @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
 *   Sitemap entity.
 * @param \Symfony\Component\HttpFoundation\Request $request
 *   The request to use if provided, otherwise \Drupal::request() will be used.
 *
 * @return int|string
 *   Returns current chunk of the sitemap.
 */
function xmlsitemap_get_current_chunk(XmlSitemapInterface $sitemap, Request $request = NULL) {
  if (!isset($request)) {
    $request = \Drupal::request();
  }

  // Check if we should display the index.
  $query = $request->query;
  $query_page = $query
    ->get('page');
  if (!isset($query_page) || !is_numeric($query_page)) {
    if ($sitemap
      ->getChunks() > 1) {
      return 'index';
    }
    else {
      return 1;
    }
  }
  else {
    return (int) $query_page;
  }
}

/**
 * Get Blurb.
 *
 * Fetch a short blurb string about module maintainership and sponsors.
 * This message will be FALSE in 'official' releases.
 *
 * @param mixed $check_version
 *   Check version.
 *
 * @static string $blurb
 *   Blurb message.
 *
 * @return string
 *   String with $blurb.
 */
function _xmlsitemap_get_blurb($check_version = TRUE) {
  static $blurb;
  if (!isset($blurb)) {
    $blurb = FALSE;
    if (!$check_version || ($version = _xmlsitemap_get_version()) && preg_match('/dev|unstable|alpha|beta|HEAD/i', $version)) {
      $sponsors = [
        \Drupal::linkGenerator()
          ->generate('Symantec', Url::fromUri('http://www.symantec.com/', [
          'attributes' => [
            'target' => 'blank',
          ],
        ])),
        \Drupal::linkGenerator()
          ->generate('WebWise Solutions', Url::fromUri('http://www.webwiseone.com/', [
          'attributes' => [
            'target' => 'blank',
          ],
        ])),
        \Drupal::linkGenerator()
          ->generate('Volacci', Url::fromUri('http://www.volacci.com/', [
          'attributes' => [
            'target' => 'blank',
          ],
        ])),
        \Drupal::linkGenerator()
          ->generate('lanetro', Url::fromUri('http://www.lanetro.com/', [
          'attributes' => [
            'target' => 'blank',
          ],
        ])),
        \Drupal::linkGenerator()
          ->generate('Coupons Dealuxe', Url::fromUri('http://couponsdealuxe.com/', [
          'attributes' => [
            'target' => 'blank',
          ],
        ])),
      ];

      // Don't extract the following string for translation.
      $blurb = '<div class="description"><p>Thank you for helping test the XML sitemap module rewrite. Please consider helping offset developer free time by <a href="http://davereid.chipin.com/" target="blank">donating</a> or if your company is interested in sponsoring the rewrite or a specific feature, please <a href="http://davereid.net/contact" target="blank">contact the developer</a>. Thank you to the following current sponsors: ' . implode(', ', $sponsors) . ', and all the individuals that have donated. This message will not be seen in the stable versions.</p></div>';

      // http://drupalmodules.com/module/xml-sitemap
    }
  }
  return $blurb;
}

/**
 * Returns xmlsitemap module version.
 *
 * @static string $version
 *   Current version.
 *
 * @return string
 *   Xmlsitemap module version.
 */
function _xmlsitemap_get_version() {
  static $version;
  if (!isset($version)) {
    $modules = \Drupal::service('extension.list.module')
      ->getList();
    $version = $modules['xmlsitemap']->info['version'];
  }
  return $version;
}

/**
 * Check the status of all hook_requirements() from any xmlsitemap modules.
 *
 * @return bool
 *   TRUE if all requirements are met, FALSE otherwise.
 */
function xmlsitemap_check_status() {
  $messages =& drupal_static(__FUNCTION__);
  if (!isset($messages)) {

    // Cache the list of modules that are checked.
    if ($cache = \Drupal::cache()
      ->get('xmlsitemap:registry:requirements')) {
      $modules = $cache->data;
    }
    else {
      $modules = [];
      \Drupal::moduleHandler()
        ->loadAllIncludes('install');
      foreach (\Drupal::moduleHandler()
        ->getImplementations('requirements') as $module) {
        if (strpos($module, 'xmlsitemap') !== FALSE) {
          $modules[] = $module;
        }
      }
      \Drupal::cache()
        ->set('xmlsitemap:registry:requirements', $modules, Cache::PERMANENT, [
        'xmlsitemap',
      ]);
    }
    $messages = [];
    foreach ($modules as $module) {
      module_load_install($module);
      $requirements = \Drupal::moduleHandler()
        ->invoke($module, 'requirements', [
        'runtime',
      ]);
      foreach ($requirements as $requirement) {
        if (isset($requirement['severity']) && max(REQUIREMENT_OK, $requirement['severity'])) {
          $messages[] = $requirement['description'];
        }
      }
    }
    if ($messages) {
      $messages = [
        '#type' => 'item_list',
        '#items' => [
          $messages,
        ],
      ];
      $message = t('One or more problems were detected with your XML sitemap configuration: @messages', [
        '@messages' => \Drupal::service('renderer')
          ->render($messages),
      ]);
      \Drupal::messenger()
        ->addWarning($message, FALSE);
      if (\Drupal::currentUser()
        ->hasPermission('access site reports')) {
        \Drupal::messenger()
          ->addWarning(t('Check the <a href="@status-report">status report</a> for more information.', [
          '@status-report' => Url::fromRoute('system.status')
            ->toString(),
        ]), FALSE);
      }
    }
  }
  return !empty($messages);
}

/**
 * Perform operations before rebuilding the sitemap.
 */
function _xmlsitemap_regenerate_before() {
  \Drupal::service('xmlsitemap_generator')
    ->regenerateBefore();
}

/**
 * Batch information callback for regenerating the sitemap files.
 *
 * @param array $smids
 *   An optional array of XML sitemap IDs. If not provided, it will load all
 *   existing XML sitemaps.
 */
function xmlsitemap_regenerate_batch(array $smids = []) {
  if (empty($smids)) {
    $sitemaps = \Drupal::entityTypeManager()
      ->getStorage('xmlsitemap')
      ->loadMultiple();
    foreach ($sitemaps as $sitemap) {
      $smids[] = $sitemap
        ->id();
    }
  }
  $t = 't';
  $batch = [
    'operations' => [],
    'error_message' => $t('An error has occurred.'),
    'finished' => 'xmlsitemap_regenerate_batch_finished',
    'title' => t('Regenerating Sitemap'),
  ];

  // Set the regenerate flag in case something fails during file generation.
  $batch['operations'][] = [
    'xmlsitemap_batch_variable_set',
    [
      [
        'xmlsitemap_regenerate_needed' => TRUE,
      ],
    ],
  ];

  // @todo Get rid of this batch operation.
  $batch['operations'][] = [
    '_xmlsitemap_regenerate_before',
    [],
  ];

  // Generate all the sitemap pages for each context.
  foreach ($smids as $smid) {
    $batch['operations'][] = [
      'xmlsitemap_regenerate_batch_generate',
      [
        $smid,
      ],
    ];
    $batch['operations'][] = [
      'xmlsitemap_regenerate_batch_generate_index',
      [
        $smid,
      ],
    ];
  }

  // Clear the regeneration flag.
  $batch['operations'][] = [
    'xmlsitemap_batch_variable_set',
    [
      [
        'xmlsitemap_regenerate_needed' => FALSE,
      ],
    ],
  ];
  return $batch;
}

/**
 * Batch callback; generate all pages of a sitemap.
 *
 * @param string $smid
 *   Sitemap entity id.
 * @param array|\ArrayAccess $context
 *   Sitemap context.
 */
function xmlsitemap_regenerate_batch_generate($smid, &$context = []) {
  \Drupal::service('xmlsitemap_generator')
    ->regenerateBatchGenerate($smid, $context);
}

/**
 * Batch callback; generate the index page of a sitemap.
 *
 * @param string $smid
 *   Sitemap entity id.
 * @param array|\ArrayAccess $context
 *   Sitemap context.
 */
function xmlsitemap_regenerate_batch_generate_index($smid, &$context = []) {
  \Drupal::service('xmlsitemap_generator')
    ->regenerateBatchGenerateIndex($smid, $context);
}

/**
 * Batch callback; sitemap regeneration finished.
 *
 * @param bool $success
 *   Checks if regeneration batch process was successful.
 * @param array $results
 *   Results for the regeneration process.
 * @param array $operations
 *   Operations performed.
 * @param int $elapsed
 *   Time elapsed.
 */
function xmlsitemap_regenerate_batch_finished($success, array $results, array $operations, $elapsed) {
  \Drupal::service('xmlsitemap_generator')
    ->regenerateBatchFinished($success, $results, $operations, $elapsed);
}

/**
 * Batch information callback for rebuilding the sitemap data.
 *
 * @param array $entity_type_ids
 *   Entity types to rebuild.
 * @param bool $save_custom
 *   Save custom data.
 *
 * @return array
 *   Batch array.
 */
function xmlsitemap_rebuild_batch(array $entity_type_ids, $save_custom = FALSE) {
  $batch = [
    'operations' => [],
    'finished' => 'xmlsitemap_rebuild_batch_finished',
    'title' => t('Rebuilding Sitemap'),
    'file' => drupal_get_path('module', 'xmlsitemap') . '/xmlsitemap.generate.inc',
  ];

  // Set the rebuild flag in case something fails during the rebuild.
  $batch['operations'][] = [
    'xmlsitemap_batch_variable_set',
    [
      [
        'xmlsitemap_rebuild_needed' => TRUE,
      ],
    ],
  ];

  // Purge any links first.
  $batch['operations'][] = [
    'xmlsitemap_rebuild_batch_clear',
    [
      $entity_type_ids,
      (bool) $save_custom,
    ],
  ];

  // Fetch all the sitemap links and save them to the {xmlsitemap} table.
  foreach ($entity_type_ids as $entity_type_id) {
    $info = xmlsitemap_get_link_info($entity_type_id);
    $batch['operations'][] = [
      $info['xmlsitemap']['rebuild callback'],
      [
        $entity_type_id,
      ],
    ];
  }

  // Clear the rebuild flag.
  $batch['operations'][] = [
    'xmlsitemap_batch_variable_set',
    [
      [
        'xmlsitemap_rebuild_needed' => FALSE,
      ],
    ],
  ];

  // Add the regeneration batch.
  $regenerate_batch = xmlsitemap_regenerate_batch();
  $batch['operations'] = array_merge($batch['operations'], $regenerate_batch['operations']);
  return $batch;
}

/**
 * Batch callback; set an array of variables and their values.
 *
 * @param array $variables
 *   Variables to be set during the batch process.
 */
function xmlsitemap_batch_variable_set(array $variables) {
  \Drupal::service('xmlsitemap_generator')
    ->batchVariableSet($variables);
}

/**
 * Batch callback; clear sitemap links for entities.
 *
 * @param array $entity_type_ids
 *   Entity types to rebuild.
 * @param bool $save_custom
 *   Save custom data.
 * @param array|\ArrayAccess $context
 *   Context to be rebuilt.
 */
function xmlsitemap_rebuild_batch_clear(array $entity_type_ids, $save_custom, &$context = []) {
  \Drupal::service('xmlsitemap_generator')
    ->rebuildBatchClear($entity_type_ids, $save_custom, $context);
}

/**
 * Batch callback; fetch and add the sitemap links for a specific entity type.
 *
 * @param string $entity_type_id
 *   Entity type ID.
 * @param array|\ArrayAccess $context
 *   Sitemap context.
 */
function xmlsitemap_rebuild_batch_fetch($entity_type_id, &$context) {
  \Drupal::service('xmlsitemap_generator')
    ->rebuildBatchFetch($entity_type_id, $context);
}

/**
 * Batch callback; sitemap rebuild finished.
 *
 * @param bool $success
 *   Checks if regeneration batch process was successful.
 * @param array $results
 *   Results for the regeneration process.
 * @param array $operations
 *   Operations performed.
 * @param int $elapsed
 *   Time elapsed.
 */
function xmlsitemap_rebuild_batch_finished($success, array $results, array $operations, $elapsed) {
  \Drupal::service('xmlsitemap_generator')
    ->rebuildBatchFinished($success, $results, $operations, $elapsed);
}

/**
 * Get all rebuildable entity types.
 *
 * @return array
 *   Array with all rebuildable entity types.
 */
function xmlsitemap_get_rebuildable_link_types() {
  $rebuild_types = [];
  $entities = xmlsitemap_get_link_info();
  foreach ($entities as $entity => $info) {
    if (empty($info['xmlsitemap']['rebuild callback'])) {

      // If the entity is missing a rebuild callback, skip.
      continue;
    }
    if (!empty($info['bundles']) && !xmlsitemap_get_link_type_enabled_bundles($entity)) {

      // If the entity has bundles, but no enabled bundles, skip since
      // rebuilding wouldn't get any links.
      continue;
    }
    $rebuild_types[] = $entity;
  }
  return $rebuild_types;
}

/**
 * Link bundle enable.
 *
 * Enable an entity bundle and create specific xmlsitemap settings config
 * object.
 *
 * @param string $entity_type_id
 *   Entity type id.
 * @param string $bundle_id
 *   Bundle id.
 *
 * @return bool
 *   Returns TRUE if operation was successful.
 */
function xmlsitemap_link_bundle_enable($entity_type_id, $bundle_id) {
  if (\Drupal::config("xmlsitemap.settings.{$entity_type_id}.{$bundle_id}")
    ->isNew()) {
    $settings = [
      'status' => XMLSITEMAP_STATUS_DEFAULT,
      'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
      'changefreq' => 0,
    ];
    xmlsitemap_link_bundle_settings_save($entity_type_id, $bundle_id, $settings);
  }
  return TRUE;
}

/**
 * Link bundle check enabled.
 *
 * Check if a bundle is enabled and config object xmlsitemap.settings object
 * exists.
 *
 * @param string $entity_type_id
 *   Entity type id.
 * @param string $bundle_id
 *   Bundle id.
 *
 * @return bool
 *   Returns TRUE if bundle is enabled, FALSE otherwise.
 */
function xmlsitemap_link_bundle_check_enabled($entity_type_id, $bundle_id) {
  return !\Drupal::config("xmlsitemap.settings.{$entity_type_id}.{$bundle_id}")
    ->isNew();
}

/**
 * Check if an entity is enabled and configuration exists for the entity type.
 *
 * @param string $entity_type_id
 *   Entity type id.
 *
 * @return bool
 *   Returns TRUE if bundle is enabled, FALSE otherwise.
 */
function xmlsitemap_link_entity_check_enabled($entity_type_id) {
  $configuration = \Drupal::configFactory()
    ->listAll("xmlsitemap.settings.{$entity_type_id}.");
  return !empty($configuration);
}

/**
 * Implements hook_xmlsitemap_link_alter() on behalf of metatag.module.
 */
function metatag_xmlsitemap_link_alter(array &$link, array $context) {
  $enabled =& drupal_static(__FUNCTION__);
  if (!isset($enabled)) {
    $enabled = \Drupal::config('xmlsitemap.settings')
      ->get('metatag_exclude_noindex');
  }
  if ($enabled && !empty($context['entity']) && $context['entity'] instanceof ContentEntityInterface && $link['access']) {

    /** @var \Drupal\metatag\MetatagManagerInterface $metatagManager */
    $metatagManager = \Drupal::service('metatag.manager');
    $metatags = $metatagManager
      ->tagsFromEntity($context['entity']);
    if (!empty($metatags['robots']) && strpos($metatags['robots'], 'noindex') !== FALSE) {
      $link['access'] = FALSE;
    }
  }
}

Functions

Namesort descending Description
content_translation_xmlsitemap_element_alter Implements hook_xmlsitemap_element_alter() for content_translation module.
language_form_xmlsitemap_sitemap_edit_form_alter Implements hook_form_FORM_ID_alter() for language module.
language_query_xmlsitemap_generate_alter Implements hook_query_TAG_alter() for language module.
language_xmlsitemap_context Implements hook_xmlsitemap_context() for language module.
language_xmlsitemap_context_info Implements hook_xmlsitemap_context_info() for language module.
language_xmlsitemap_context_url_options Implements hook_xmlsitemap_context_url_options() for language module.
language_xmlsitemap_element_alter Implements hook_xmlsitemap_element_alter() for language module.
metatag_xmlsitemap_link_alter Implements hook_xmlsitemap_link_alter() on behalf of metatag.module.
theme_xmlsitemap_content_settings_table Returns HTML for an administration settings table.
xmlsitemap_add_form_entity_summary Add a table summary for an entity and its bundles.
xmlsitemap_add_form_link_options Add a link's XML sitemap options to the link's form.
xmlsitemap_add_link_bundle_settings Add the link type XML sitemap options to the link type's form.
xmlsitemap_batch_variable_set Batch callback; set an array of variables and their values.
xmlsitemap_calculate_changefreq Calculates the average interval between UNIX timestamps.
xmlsitemap_check_all_directories Check all directories.
xmlsitemap_check_directory Check that the sitemap files directory exists and is writable.
xmlsitemap_check_status Check the status of all hook_requirements() from any xmlsitemap modules.
xmlsitemap_clear_directory Clears sitemap directory.
xmlsitemap_config_variables Internal default variables config for xmlsitemap_var().
xmlsitemap_cron Implements hook_cron().
xmlsitemap_directory_move Move a directory to a new location.
xmlsitemap_entity_bundle_delete Implements hook_entity_bundle_delete().
xmlsitemap_entity_bundle_rename Implements hook_entity_bundle_rename().
xmlsitemap_entity_delete Implements hook_entity_delete().
xmlsitemap_entity_insert Implements hook_entity_insert().
xmlsitemap_entity_translation_delete Implements hook_entity_translation_delete().
xmlsitemap_entity_type_build Implements hook_entity_type_build().
xmlsitemap_entity_update Implements hook_entity_update().
xmlsitemap_form_alter Implements hook_form_alter().
xmlsitemap_form_language_admin_overview_form_alter Implements hook_form_FORM_ID_alter().
xmlsitemap_form_submit_flag_regenerate Submit handler; Set the regenerate needed flag if variables have changed.
xmlsitemap_get_changefreq Determine the frequency of updates to a link.
xmlsitemap_get_changefreq_options Gets xmlsitemap frequency options.
xmlsitemap_get_chunk_count Get the current number of sitemap chunks.
xmlsitemap_get_chunk_size Get the sitemap chunk size.
xmlsitemap_get_context_info Gets info about a context.
xmlsitemap_get_current_chunk Get the sitemap chunk/page of the current request.
xmlsitemap_get_current_context Get the sitemap context of the current request.
xmlsitemap_get_directory End of "defgroup xmlsitemap_api"
xmlsitemap_get_link_count Get the current number of sitemap links.
xmlsitemap_get_link_info Returns information about supported sitemap link types.
xmlsitemap_get_link_type_enabled_bundles Returns enabled bundles of an entity type.
xmlsitemap_get_link_type_indexed_status Returns statistics about specific entity links.
xmlsitemap_get_operation_link Gets a link from url.
xmlsitemap_get_priority_options Get a list of priority options.
xmlsitemap_get_rebuildable_link_types Get all rebuildable entity types.
xmlsitemap_get_status_options Get a list of priority options.
xmlsitemap_help Implements hook_help().
xmlsitemap_hook_info Implements hook_hook_info().
xmlsitemap_is_entity_type_supported Determines if an entity type can be listed in the XML sitemap as links.
xmlsitemap_language_load Load a language object by its language code.
xmlsitemap_link_bundle_check_enabled Link bundle check enabled.
xmlsitemap_link_bundle_delete Deletes all links of a specific bundle.
xmlsitemap_link_bundle_enable Link bundle enable.
xmlsitemap_link_bundle_load Loads link bundle info.
xmlsitemap_link_bundle_rename Renames a bundle.
xmlsitemap_link_bundle_settings_form_submit Submit callback for link bundle settings.
xmlsitemap_link_bundle_settings_save Saves xmlsitemap settings for a specific bundle.
xmlsitemap_link_entity_check_enabled Check if an entity is enabled and configuration exists for the entity type.
xmlsitemap_link_frontpage_settings XML sitemap link type settings callback for frontpage link entity.
xmlsitemap_modules_installed Implements hook_modules_installed().
xmlsitemap_modules_uninstalled Implements hook_modules_uninstalled().
xmlsitemap_process_form_link_options Submit callback for the entity form to save.
xmlsitemap_rebuild_batch Batch information callback for rebuilding the sitemap data.
xmlsitemap_rebuild_batch_clear Batch callback; clear sitemap links for entities.
xmlsitemap_rebuild_batch_fetch Batch callback; fetch and add the sitemap links for a specific entity type.
xmlsitemap_rebuild_batch_finished Batch callback; sitemap rebuild finished.
xmlsitemap_recalculate_changefreq Recalculate the changefreq of a sitemap link.
xmlsitemap_regenerate_batch Batch information callback for regenerating the sitemap files.
xmlsitemap_regenerate_batch_finished Batch callback; sitemap regeneration finished.
xmlsitemap_regenerate_batch_generate Batch callback; generate all pages of a sitemap.
xmlsitemap_regenerate_batch_generate_index Batch callback; generate the index page of a sitemap.
xmlsitemap_robotstxt Implements hook_robotstxt().
xmlsitemap_run_unprogressive_batch Run a not-progressive batch operation.
xmlsitemap_sitemap_delete Delete an XML sitemap.
xmlsitemap_sitemap_delete_multiple Delete multiple XML sitemaps.
xmlsitemap_sitemap_get_context_hash Returns the hash string for a context.
xmlsitemap_sitemap_get_file Return the expected file path for a specific sitemap chunk.
xmlsitemap_sitemap_get_max_filesize Find the maximum file size of all a sitemap's XML files.
xmlsitemap_sitemap_load Load an XML sitemap array from the database.
xmlsitemap_sitemap_load_multiple Load multiple XML sitemaps from the database.
xmlsitemap_sitemap_multiple_update XML sitemap operation callback; regenerate sitemap files using the batch API.
xmlsitemap_sitemap_save Save changes to an XML sitemap or add a new XML sitemap.
xmlsitemap_sitemap_uri Returns the uri elements of an XML sitemap.
xmlsitemap_state_variables Internal default variables state for xmlsitemap_var().
xmlsitemap_theme Implements hook_theme().
xmlsitemap_var Internal implementation of variable_get().
xmlsitemap_xmlsitemap_index_links Implements hook_xmlsitemap_index_links().
xmlsitemap_xmlsitemap_links Implements hook_xmlsitemap_links().
xmlsitemap_xmlsitemap_link_alter Implements hook_xmlsitemap_link_alter().
xmlsitemap_xmlsitemap_link_info Implements hook_xmlsitemap_link_info().
xmlsitemap_xmlsitemap_process_entity_link Process sitemap link for an entity.
xmlsitemap_xmlsitemap_process_entity_links Process sitemap links.
xmlsitemap_xmlsitemap_sitemap_operations Implements hook_xmlsitemap_sitemap_operations().
_xmlsitemap_delete_recursive Recursively delete all files and folders in the specified filepath.
_xmlsitemap_element_add_alternate_lang Adds alternate language links to a sitemap element.
_xmlsitemap_get_blurb Get Blurb.
_xmlsitemap_get_version Returns xmlsitemap module version.
_xmlsitemap_rebuild_form_access Menu access callback; determines if the user can use the rebuild links page.
_xmlsitemap_regenerate_before Perform operations before rebuilding the sitemap.
_xmlsitemap_sitemap_context_summary Gets summary about a context.

Constants

Namesort descending Description
XMLSITEMAP_FREQUENCY_ALWAYS
XMLSITEMAP_FREQUENCY_DAILY
XMLSITEMAP_FREQUENCY_HOURLY
XMLSITEMAP_FREQUENCY_MONTHLY
XMLSITEMAP_FREQUENCY_WEEKLY
XMLSITEMAP_FREQUENCY_YEARLY Xmlsitemap Frequencies.
XMLSITEMAP_LASTMOD_LONG Long lastmod timestamp format.
XMLSITEMAP_LASTMOD_MEDIUM Medium lastmod timestamp format.
XMLSITEMAP_LASTMOD_SHORT Short lastmod timestamp format.
XMLSITEMAP_MAX_SITEMAP_FILESIZE The maximum filesize of a sitemap chunk file.
XMLSITEMAP_MAX_SITEMAP_LINKS The maximum number of links in one sitemap chunk file.
XMLSITEMAP_PRIORITY_DEFAULT The default priority for link types in the sitemaps.
XMLSITEMAP_STATUS_DEFAULT The default inclusion status for link types in the sitemaps.