$info) { $hooks[$hook] = ['group' => 'xmlsitemap']; } return $hooks; } /** * Implements hook_help(). */ function xmlsitemap_help($route_name, RouteMatchInterface $route_match) { $output = ''; switch ($route_name) { 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 'help.page.xmlsitemap': $output = '

' . t('About') . '

'; $output .= '

' . t('XML Sitemap automatically creates a sitemap that conforms to the sitemaps.org specification. This helps search engines keep their search results up to date.') . '

'; $output .= '

' . t('Usage') . '

'; $output .= '

' . t("You can adjust the settings for your site's sitemap at admin/config/search/xmlsitemap. Your can view your site's sitemap at http://yoursite.com/sitemap.xml.") . '

'; $output .= '

' . t("When is necessary you can rebuild your sitemap at admin/config/search/xmlsitemap/rebuild.") . '

'; $output .= '

' . t("You can configure all Custom Entities Settings at admin/config/search/xmlsitemap/entities/settings") . '

'; $output .= '

' . t('It is highly recommended that you have clean URLs enabled for this project.') . '

'; return $output; case 'xmlsitemap.admin_rebuild': $output .= '

' . 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.") . '

'; } $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); } /** * Checks access for a bundle. * * @param string $entity * Entity type id. * @param string $bundle * Bundle id. * * @return bool * If TRUE, access is allowed, FALSE otherwise. * * @deprecated in xmlsitemap:8.x-1.1 and is removed from xmlsitemap:2.0.0. * * @see https://www.drupal.org/project/xmlsitemap/issues/3156088 */ function xmlsitemap_link_bundle_access($entity, $bundle = NULL) { @trigger_error(__FUNCTION__ . ' is deprecated in xmlsitemap:8.x-1.1 and will be removed in xmlsitemap:2.0.0. See https://www.drupal.org/project/xmlsitemap/issues/3156088', E_USER_DEPRECATED); return FALSE; } /** * Get path of a bundle. * * @param string $entity * Entity type id. * @param string $bundle * Bundle id. * * @return mixed * Path of bundle, or FALSE if it does not exist. * * @deprecated in xmlsitemap:8.x-1.1 and is removed from xmlsitemap:2.0.0. * * @see https://www.drupal.org/project/xmlsitemap/issues/3156088 */ function xmlsitemap_get_bundle_path($entity, $bundle) { @trigger_error(__FUNCTION__ . ' is deprecated in xmlsitemap:8.x-1.1 and will be removed in xmlsitemap:2.0.0. See https://www.drupal.org/project/xmlsitemap/issues/3156088', E_USER_DEPRECATED); return FALSE; } /** * 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, bool $translated = TRUE) { if ($interval <= 0 || !is_numeric($interval)) { return FALSE; } foreach (xmlsitemap_get_changefreq_options() as $value => $frequency) { if ($interval <= $value) { return $translated ? $frequency : $frequency->getUntranslatedString(); } } return $translated ? t('never', [], ['context' => 'At no time']) : '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 run cron manually 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' => '

', '#suffix' => '

', '#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 \Drupal\Core\StringTranslation\TranslatableMarkup[] * Frequency options. */ function xmlsitemap_get_changefreq_options() { return [ XMLSITEMAP_FREQUENCY_ALWAYS => t('always', [], ['context' => 'At all times']), XMLSITEMAP_FREQUENCY_HOURLY => t('hourly'), XMLSITEMAP_FREQUENCY_DAILY => t('daily'), XMLSITEMAP_FREQUENCY_WEEKLY => t('weekly'), XMLSITEMAP_FREQUENCY_MONTHLY => t('monthly'), XMLSITEMAP_FREQUENCY_YEARLY => t('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 '

' . $variables['build']['#title'] . '

' . \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::service('entity.memory_cache')->deleteAll(); } /** * Process sitemap link for an entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. * @param bool $process_translations * If TRUE will also process all the translations on the entity. FALSE will * skip the translations and just process the entity itself. */ function xmlsitemap_xmlsitemap_process_entity_link(EntityInterface $entity, bool $process_translations = TRUE) { // Ignore any non-default revision entity data (for example, unpublished // future revisions of existing published content). if ($entity instanceof RevisionableInterface && !$entity->isDefaultRevision()) { return; } /** @var \Drupal\xmlsitemap\XmlSitemapLinkStorageInterface $link_storage */ $link_storage = \Drupal::service('xmlsitemap.link_storage'); if ($process_translations && $entity instanceof TranslatableInterface) { // Generate an entry for each translation. $translation_languages = $entity->getTranslationLanguages(); foreach ($translation_languages as $langcode => $language) { $translation = $entity->getTranslation($langcode); xmlsitemap_xmlsitemap_process_entity_link($translation, FALSE); } // 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 site information configuration.', ['@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' => '
', '#suffix' => '
', '#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; } } /** * Creates a response reading the sitemap file and adding content to response. * * @param \Symfony\Component\HttpFoundation\Response $response * Response object. * @param string $file * File uri. * @param array $headers * Headers of the response. * * @return \Symfony\Component\HttpFoundation\BinaryFileResponse * The sitemap response object. * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * If the sitemap is not found or the sitemap file is not readable. * * @deprecated in xmlsitemap:8.x-1.0 and is removed from xmlsitemap:2.0.0. Use * \Drupal\xmlsitemap\Controller\XmlSitemapController::getSitemapResponse() * instead. * * @see https://www.drupal.org/project/xmlsitemap/issues/2869214 */ function xmlsitemap_output_file(Response $response, $file, array $headers = []) { @trigger_error(__FUNCTION__ . ' is deprecated in xmlsitemap:8.x-1.0 and will be removed in xmlsitemap:2.0.0. Use \Drupal\xmlsitemap\Controller\XmlSitemapController::getSitemapResponse. See https://www.drupal.org/project/xmlsitemap/issues/2869214', E_USER_DEPRECATED); /** @var \Drupal\xmlsitemap\Controller\XmlSitemapController $controller */ $controller = \Drupal::classResolver(XmlSitemapController::class); return $controller->getSitemapResponse($file, \Drupal::request(), $headers + $response->headers->all()); } /** * 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 = '

Thank you for helping test the XML Sitemap module rewrite. Please consider helping offset developer free time by donating or if your company is interested in sponsoring the rewrite or a specific feature, please contact the developer. 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.

'; // 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 status report 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; } } }