$info) {
$hooks[$hook] = array('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 'xmlsitemap.admin_edit':
case 'xmlsitemap.admin_delete':
return;
case 'xmlsitemap.admin_search':
break;
case 'xmlsitemap.admin_search_list':
break;
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_permission().
*/
function xmlsitemap_permission() {
$permissions['administer xmlsitemap'] = array(
'title' => t('Administer XML sitemap settings.'),
);
return $permissions;
}
/**
* Implements hook_theme().
*/
function xmlsitemap_theme() {
return array(
'xmlsitemap_content_settings_table' => array(
'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 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 = REQUEST_TIME - \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_enabled().
*/
function xmlsitemap_modules_enabled(array $modules) {
cache_clear_all('xmlsitemap:', 'cache', TRUE);
}
/**
* Implements hook_modules_disabled().
*/
function xmlsitemap_modules_disabled(array $modules) {
cache_clear_all('xmlsitemap:', 'cache', TRUE);
}
/**
* Implements hook_robotstxt().
*/
function xmlsitemap_robotstxt() {
$sitemap_storage = \Drupal::entityManager()->getStorage('xmlsitemap');
if ($sitemap = $sitemap_storage->loadByContext()) {
$uri = xmlsitemap_sitemap_uri($sitemap);
$robotstxt[] = 'Sitemap: ' . url($uri['path'], $uri['options']);
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 array(
'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,
// Removed variables are set to NULL so they can still be deleted.
'regenerate_last' => NULL,
'custom_links' => NULL,
'priority_default' => NULL,
'languages' => NULL,
'max_chunks' => NULL,
'max_filesize' => NULL,
);
}
/**
* Internal default variables state for xmlsitemap_var().
*
* @return array
* Array with state variables defined by xmlsitemap module.
*/
function xmlsitemap_state_variables() {
return array(
'xmlsitemap_rebuild_needed' => FALSE,
'xmlsitemap_regenerate_needed' => TRUE,
'xmlsitemap_base_url' => '',
'xmlsitemap_generated_last' => 0,
'xmlsitemap_developer_mode' => 0
);
}
/**
* 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(strtr('Default variable for %variable not found.', array('%variable' => drupal_placeholder($name))));
}
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 $smid
* An XML sitemap ID.
*
* @return
* The XML sitemap object.
*/
function xmlsitemap_sitemap_load($smid) {
$sitemap = xmlsitemap_sitemap_load_multiple(array($smid));
return $sitemap ? reset($sitemap) : FALSE;
}
/**
* Load multiple XML sitemaps from the database.
*
* @param $smids
* An array of XML sitemap IDs, or FALSE to load all XML sitemaps.
* @param $conditions
* An array of conditions in the form 'field' => $value.
*
* @return
* An array of XML sitemap objects.
*/
function xmlsitemap_sitemap_load_multiple($smids = array(), array $conditions = array()) {
if ($smids !== FALSE) {
$conditions['smid'] = $smids;
}
else {
$conditions['smid'] = NULL;
}
$storage = Drupal::entityManager()->getStorage('xmlsitemap');
$sitemaps = $storage->loadMultiple($conditions['smid']);
if (count($sitemaps) <= 0) {
return array();
}
foreach ($sitemaps as &$sitemap) {
$uri = xmlsitemap_sitemap_uri($sitemap);
$sitemap->uri = $uri;
}
return $sitemaps;
}
/**
* Save changes to an XML sitemap or add a new XML sitemap.
*
* @param $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 = array();
}
// 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 = (object) array('smid' => $sitemap->old_smid);
$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(array($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) {
xmlsitemap_clear_directory($sitemap, TRUE);
$sitemap->delete();
\Drupal::moduleHandler()->invokeAll('xmlsitemap_sitemap_delete', array($sitemap));
}
}
}
/**
* Return the expected file path for a specific sitemap chunk.
*
* @param $sitemap
* An XmlSitemapInterface sitemap object.
* @param $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.
*/
function xmlsitemap_sitemap_get_max_filesize(XmlSitemapInterface $sitemap) {
$dir = xmlsitemap_get_directory($sitemap);
$sitemap->setMaxFileSize(0);
foreach (file_scan_directory($dir, '/\.xml$/') 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) {
asort($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
* 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', array($sitemap->context));
$context = $sitemap->context;
\Drupal::moduleHandler()->alter('xmlsitemap_context_url_options', $uri['options'], $context);
$uri['options'] += array(
'absolute' => TRUE,
'base_url' => \Drupal::state()->get('xmlsitemap_base_url')
);
return $uri;
}
/**
* Load a specific sitemap link from the database.
*
* @param string $entity_type
* A string with the entity type.
* @param $entity_id
* ID for the entity to be loaded.
* @return
* A sitemap link (array) or FALSE if the conditions were not found.
*/
function xmlsitemap_link_load($entity_type, $entity_id) {
$link = xmlsitemap_link_load_multiple(array('type' => $entity_type, 'id' => $entity_id));
return $link ? reset($link) : FALSE;
}
/**
* Load sitemap links from the database.
*
* @param $conditions
* An array of conditions on the {xmlsitemap} table in the form
* 'field' => $value.
* @return
* An array of sitemap link arrays.
*/
function xmlsitemap_link_load_multiple(array $conditions = array()) {
$query = db_select('xmlsitemap');
$query->fields('xmlsitemap');
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
$links = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
return $links;
}
/**
* Saves or updates a sitemap link.
*
* @param array $link
* An array with a sitemap link.
*
* @return array
* Sitemap link saved.
*/
function xmlsitemap_link_save(array $link) {
$link += array(
'access' => 1,
'status' => 1,
'status_override' => 0,
'lastmod' => 0,
'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
'priority_override' => 0,
'changefreq' => 0,
'changecount' => 0,
'language' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
);
// Allow other modules to alter the link before saving.
\Drupal::moduleHandler()->alter('xmlsitemap_link', $link);
// Temporary validation checks.
// @todo Remove in final?
if ($link['priority'] < 0 || $link['priority'] > 1) {
trigger_error(t('Invalid sitemap link priority %priority. @link', array('%priority' => $link['priority'], '@link' => var_export($link, TRUE))), E_USER_ERROR);
}
if ($link['changecount'] < 0) {
trigger_error(t('Negative changecount value. Please report this to @516928. @link', array('@516928' => 'http://drupal.org/node/516928', '@link' => var_export($link, TRUE))), E_USER_ERROR);
$link['changecount'] = 0;
}
$existing = db_query_range("SELECT loc, access, status, lastmod, priority, changefreq, changecount, language FROM {xmlsitemap} WHERE type = :type AND id = :id", 0, 1, array(':type' => $link['type'], ':id' => $link['id']))->fetchAssoc();
// Check if this is a changed link and set the regenerate flag if necessary.
if (!\Drupal::state()->get('xmlsitemap_regenerate_needed')) {
_xmlsitemap_check_changed_link($link, $existing, TRUE);
}
// Save the link and allow other modules to respond to the link being saved.
if ($existing) {
drupal_write_record('xmlsitemap', $link, array('type', 'id'));
\Drupal::moduleHandler()->invokeAll('xmlsitemap_link_update', array($link));
}
else {
$result = drupal_write_record('xmlsitemap', $link);
\Drupal::moduleHandler()->invokeAll('xmlsitemap_link_insert', array($link));
}
return $link;
}
/**
* Perform a mass update of sitemap data.
*
* If visible links are updated, this will automatically set the regenerate
* needed flag to TRUE.
*
* @param $updates
* An array of values to update fields to, keyed by field name.
* @param $conditions
* An array of values to match keyed by field.
* @return
* The number of links that were updated.
*/
function xmlsitemap_link_update_multiple($updates = array(), $conditions = array(), $check_flag = TRUE) {
// If we are going to modify a visible sitemap link, we will need to set
// the regenerate needed flag.
if ($check_flag && !\Drupal::state()->get('xmlsitemap_regenerate_needed')) {
_xmlsitemap_check_changed_links($conditions, $updates, TRUE);
}
// Process updates.
$query = db_update('xmlsitemap');
$query->fields($updates);
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
return $query->execute();
}
/**
* Delete a specific sitemap link from the database.
*
* If a visible sitemap link was deleted, this will automatically set the
* regenerate needed flag.
*
* @param $entity_type
* Entity type id.
* @param $entity_id
* Entity ID.
* @return
* The number of links that were deleted.
*/
function xmlsitemap_link_delete($entity_type, $entity_id) {
$conditions = array('type' => $entity_type, 'id' => $entity_id);
return xmlsitemap_link_delete_multiple($conditions);
}
/**
* Delete multiple sitemap links from the database.
*
* If visible sitemap links were deleted, this will automatically set the
* regenerate needed flag.
*
* @param $conditions
* An array of conditions on the {xmlsitemap} table in the form
* 'field' => $value.
* @return
* The number of links that were deleted.
*/
function xmlsitemap_link_delete_multiple(array $conditions) {
// Because this function is called from sub-module uninstall hooks, we have
// to manually check if the table exists since it could have been removed
// in xmlsitemap_uninstall().
// @todo Remove this check when http://drupal.org/node/151452 is fixed.
if (!db_table_exists('xmlsitemap')) {
return FALSE;
}
if (!\Drupal::state()->get('xmlsitemap_regenerate_needed')) {
_xmlsitemap_check_changed_links($conditions, array(), TRUE);
}
// @todo Add a hook_xmlsitemap_link_delete() hook invoked here.
$query = db_delete('xmlsitemap');
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
return $query->execute();
}
/**
* Check if there is a visible sitemap link given a certain set of conditions.
*
* @param $conditions
* An array of values to match keyed by field.
* @param $flag
* An optional boolean that if TRUE, will set the regenerate needed flag if
* there is a match. Defaults to FALSE.
* @return
* TRUE if there is a visible link, or FALSE otherwise.
*/
function _xmlsitemap_check_changed_links(array $conditions = array(), array $updates = array(), $flag = FALSE) {
// If we are changing status or access, check for negative current values.
$conditions['status'] = (!empty($updates['status']) && empty($conditions['status'])) ? 0 : 1;
$conditions['access'] = (!empty($updates['access']) && empty($conditions['access'])) ? 0 : 1;
$query = db_select('xmlsitemap');
$query->addExpression('1');
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
$query->range(0, 1);
$changed = $query->execute()->fetchField();
if ($changed && $flag) {
\Drupal::state()->set('xmlsitemap_regenerate_needed', TRUE);
}
return $changed;
}
/**
* Check if there is sitemap link is changed from the existing data.
*
* @param array $link
* An array of the sitemap link.
* @param array $original_link
* An optional array of the existing data. This should only contain the
* fields necessary for comparison. If not provided the existing data will be
* loaded from the database.
* @param bool $flag
* An optional boolean that if TRUE, will set the regenerate needed flag if
* there is a match. Defaults to FALSE.
* @return bool
* TRUE if the link is changed, or FALSE otherwise.
*/
function _xmlsitemap_check_changed_link(array $link, $original_link = NULL, $flag = FALSE) {
$changed = FALSE;
if ($original_link === NULL) {
// Load only the fields necessary for data to be changed in the sitemap.
$original_link = db_query_range("SELECT loc, access, status, lastmod, priority, changefreq, changecount, language FROM {xmlsitemap} WHERE type = :type AND id = :id", 0, 1, array(':type' => $link['type'], ':id' => $link['id']))->fetchAssoc();
}
if (!$original_link) {
if ($link['access'] && $link['status']) {
// Adding a new visible link.
$changed = TRUE;
}
}
else {
if (!($original_link['access'] && $original_link['status']) && $link['access'] && $link['status']) {
// Changing a non-visible link to a visible link.
$changed = TRUE;
}
elseif ($original_link['access'] && $original_link['status'] && array_diff_assoc($original_link, $link)) {
// Changing a visible link
$changed = TRUE;
}
}
if ($changed && $flag) {
\Drupal::state()->set('xmlsitemap_regenerate_needed', TRUE);
}
return $changed;
}
/**
* @} 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');
}
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);
$result = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
if (!$result) {
watchdog('file system', 'The directory %directory does not exist or is not writable.', array('%directory' => $directory), WATCHDOG_ERROR);
}
return $result;
}
function xmlsitemap_check_all_directories() {
$directories = array();
$sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
foreach ($sitemaps as $smid => $sitemap) {
$directory = xmlsitemap_get_directory($sitemap);
$directories[$directory] = $directory;
}
foreach ($directories as $directory) {
$result = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_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 integer $replace
* 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 = FILE_EXISTS_REPLACE) {
$success = file_prepare_directory($new_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$old_path = drupal_realpath($old_dir);
$new_path = drupal_realpath($new_dir);
if (!is_dir($old_path) || !is_dir($new_path) || !$success) {
return FALSE;
}
$files = file_scan_directory($old_dir, '/.*/');
foreach ($files as $file) {
$file->uri_new = $new_dir . '/' . basename($file->filename);
$success &= (bool) file_unmanaged_move($file->uri, $file->uri_new, $replace);
}
// The remove the directory.
$success &= drupal_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) {
// Resolve streamwrapper URI to local path.
$path = drupal_realpath($path);
if (is_dir($path)) {
$dir = dir($path);
while (($entry = $dir->read()) !== FALSE) {
if ($entry == '.' || $entry == '..') {
continue;
}
$entry_path = $path . '/' . $entry;
file_unmanaged_delete_recursive($entry_path, NULL);
}
$dir->close();
return $delete_root ? drupal_rmdir($path) : TRUE;
}
return file_unmanaged_delete($path);
}
/**
* Returns information about supported sitemap link types.
*
* @param $type
* (optional) The link type to return information for. If omitted,
* information for all link types is returned.
* @param $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;
foreach (\Drupal::languageManager()->getLanguages() as $lang) {
\Drupal::cache()->delete('xmlsitemap:link_info:' . $lang->getId());
}
}
if (!isset($link_info)) {
$cid = 'xmlsitemap:link_info:' . $language->getId();
if ($cache = \Drupal::cache()->get($cid)) {
$link_info = $cache->data;
}
else {
\Drupal::entityManager()->clearCachedDefinitions();
$link_info = array();
$entity_types = \Drupal::entityManager()->getDefinitions();
foreach ($entity_types as $key => $entity_type) {
$link_info[$key] = array(
'label' => $entity_type->getLabel(),
'type' => $entity_type->id(),
'base table' => $entity_type->getBaseTable(),
'bundles' => \Drupal::entityManager()->getBundleInfo($entity_type->id())
);
$uri_callback = $entity_type->getUriCallback();
if (empty($uri_callback) || !isset($entity_type->xmlsitemap)) {
// Remove any non URL-able or XML sitemap un-supported entites.
}
foreach (\Drupal::entityManager()->getBundleInfo($entity_type->id()) as $bundle_key => $bundle) {
if (!isset($bundle['xmlsitemap'])) {
// Remove any un-supported entity bundles.
}
}
}
$link_info = array_merge($link_info, \Drupal::moduleHandler()->invokeAll('xmlsitemap_link_info'));
foreach ($link_info as $key => &$info) {
$info += array(
'type' => $key,
'base table' => FALSE,
'bundles' => array(),
'xmlsitemap' => array(),
);
if (!isset($info['xmlsitemap']['rebuild callback']) && !empty($info['base table']) && !empty($info['entity keys']['id']) && !empty($info['xmlsitemap']['process callback'])) {
$info['xmlsitemap']['rebuild callback'] = 'xmlsitemap_rebuild_batch_fetch';
}
foreach ($info['bundles'] as $bundle => &$bundle_info) {
$bundle_info += array(
'xmlsitemap' => array(),
);
$bundle_info['xmlsitemap'] += xmlsitemap_link_bundle_load($key, $bundle, FALSE);
}
}
\Drupal::moduleHandler()->alter('xmlsitemap_link_info', $link_info);
ksort($link_info);
// Cache by language since this info contains translated strings.
\Drupal::cache()->set($cid, $link_info);
}
}
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 = array();
$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
* Entity type id.
* @param string $bundle
* Bundle id;
*
* @return array
* Array with statistics.
*/
function xmlsitemap_get_link_type_indexed_status($entity_type, $bundle = '') {
$info = xmlsitemap_get_link_info($entity_type);
$status['indexed'] = db_query("SELECT COUNT(id) FROM {xmlsitemap} WHERE type = :entity AND subtype = :bundle", array(':entity' => $entity_type, ':bundle' => $bundle))->fetchField();
$status['visible'] = db_query("SELECT COUNT(id) FROM {xmlsitemap} WHERE type = :entity AND subtype = :bundle AND status = 1 AND access = 1", array(':entity' => $entity_type, ':bundle' => $bundle))->fetchField();
try {
$entity_query_object = \Drupal::entityQuery($entity_type);
$entity_query_object->addTag('xmlsitemap_link_bundle_access');
$entity_query_object->addTag('xmlsitemap_link_indexed_status');
$entity_query_object->addMetaData('entity', $entity_type);
$entity_query_object->addMetaData('type', $bundle);
$entity_query_object->addMetaData('entity_info', $info);
$entity_query_object->count();
$status['total'] = $entity_query_object->execute();
return $status;
}
catch (Exception $e) {
$status['total'] = 0;
}
return $status;
}
/**
* Implements hook_entity_query_alter().
*
* @todo Remove when http://drupal.org/node/1054168 is fixed.
*/
function xmlsitemap_entity_query_alter($query) {
$conditions = &$query->entityConditions;
// Alter user entity queries only.
if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'user' && isset($conditions['bundle'])) {
unset($conditions['bundle']);
}
}
/**
* 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.
*
* @return array
* Info about sitemap link.
*/
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']) {
xmlsitemap_link_update_multiple(array('status' => $settings['status']), array('type' => $entity, 'subtype' => $bundle, 'status_override' => 0));
}
if ($settings['priority'] != $old_settings['priority']) {
xmlsitemap_link_update_multiple(array('priority' => $settings['priority']), array('type' => $entity, 'subtype' => $bundle, 'priority_override' => 0));
}
}
\Drupal::config('xmlsitemap.settings')->set("xmlsitemap_settings_{$entity}_{$bundle}", $settings)->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) {
$settings = xmlsitemap_link_bundle_load($entity, $bundle_old);
\Drupal::config('xmlsitemap.settings')->clear("xmlsitemap_settings_{$entity}_{$bundle_old}");
$old_bundle_value = \Drupal::config('xmlsitemap.settings')->get("xmlsitemap_entity_{$entity}_bundle_{$bundle_old}");
\Drupal::config('xmlsitemap.settings')->clear("xmlsitemap_entity_{$entity}_bundle_{$bundle_old}");
\Drupal::config('xmlsitemap.settings')->set("xmlsitemap_entity_{$entity}_bundle_{$bundle_new}", $old_bundle_value)->save();
xmlsitemap_link_bundle_settings_save($entity, $bundle_new, $settings, FALSE);
xmlsitemap_link_update_multiple(array('subtype' => $bundle_new), array('type' => $entity, 'subtype' => $bundle_old));
}
}
/**
* Renames a link type.
*
* @param string $entity_old
* Old entity type id.
* @param type $entity_new
* New entity type id.
* @param array $bundles
* Bundles to be updated.
*/
function xmlsitemap_link_type_rename($entity_old, $entity_new, $bundles = NULL) {
$variables = \Drupal::config('xmlsitemap.settings')->get();
foreach ($variables as $key => $value) {
if (!strpos($key, "xmlsitemap_settings_{$entity_old}") && !strpos($key, "xmlsitemap_entity_{$entity_old}")) {
continue;
}
\Drupal::config('xmlsitemap.settings')->clear($key);
if (isset($value)) {
$variable_new = str_replace('xmlsitemap_settings_' . $entity_old, 'xmlsitemap_settings_' . $entity_new, $key);
\Drupal::config('xmlsitemap.settings')->set($variable_new, $value)->save();
}
}
xmlsitemap_link_update_multiple(array('type' => $entity_new), array('type' => $entity_old), FALSE);
xmlsitemap_get_link_info(NULL, TRUE);
}
/**
* 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 = array(
'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')->get("xmlsitemap_settings_{$entity}_{$bundle}");
if ($bundle_settings) {
$info += $bundle_settings;
}
$info += array(
'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::config('xmlsitemap.settings')->clear("xmlsitemap_settings_{$entity}_{$bundle}");
\Drupal::config('xmlsitemap.settings')->clear("xmlsitemap_entity_{$entity}_bundle_{$bundle}");
if ($delete_links) {
xmlsitemap_link_delete_multiple(array('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.
*/
function xmlsitemap_link_bundle_access($entity, $bundle = NULL) {
if (is_array($entity) && !isset($bundle)) {
$bundle = $entity;
}
else {
$bundle = xmlsitemap_link_bundle_load($entity, $bundle);
}
if (isset($bundle['info']['admin'])) {
$admin = $bundle['info']['admin'];
$admin += array('access arguments' => array());
if (!isset($admin['access callback']) && count($admin['access arguments']) == 1) {
$admin['access callback'] = 'user_access';
}
if (!empty($admin['access callback'])) {
return call_user_func_array($admin['access callback'], $admin['access arguments']);
}
}
return FALSE;
}
/**
* Get path of a bundle.
*
* @param string $entity
* Entity type id.
* @param string $bundle
* Bundle id.
*
* @return
* Path of bundle, or FALSE if it does not exist.
*/
function xmlsitemap_get_bundle_path($entity, $bundle) {
$info = xmlsitemap_get_link_info($entity);
if (!empty($info['bundles'][$bundle]['admin']['real path'])) {
return $info['bundles'][$bundle]['admin']['real path'];
}
elseif (!empty($info['bundles'][$bundle]['admin']['path'])) {
return $info['bundles'][$bundle]['admin']['path'];
}
else {
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, $bundle, $instances) {
xmlsitemap_link_bundle_delete($entity_type, $bundle, TRUE);
}
/**
* Determine the frequency of updates to a link.
*
* @param $interval
* An interval value in seconds.
* @return
* 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.
*
* @static int $chunks
* Number of chunks.
* @param int $reset
* If TRUE, reset number of chunks.
*
* @return integer
* 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.
*
* @static int $count
* Current number of sitemap links.
* @param bool $reset
* If TRUE, update current number of sitemap links.
*
* @return integer
* Returns current number of sitemap links.
*/
function xmlsitemap_get_link_count($reset = FALSE) {
static $count;
if (!isset($count) || $reset) {
$count = db_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 $reset
* A boolean to reset the saved, static result. Defaults to FALSE.
* @return
* 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') {
$count = max(xmlsitemap_get_link_count($reset), 1); // Prevent divide by zero.
$size = min(ceil($count / 10000) * 5000, XMLSITEMAP_MAX_SITEMAP_LINKS);
}
}
return $size;
}
/**
* Recalculate the changefreq of a sitemap link.
*
* @param $link
* A sitemap link array.
*/
function xmlsitemap_recalculate_changefreq(&$link) {
$link['changefreq'] = round((($link['changefreq'] * $link['changecount']) + (REQUEST_TIME - $link['lastmod'])) / ($link['changecount'] + 1));
$link['changecount'] ++;
$link['lastmod'] = REQUEST_TIME;
}
/**
* Calculates the average interval between UNIX timestamps.
*
* @param $timestamps
* An array of UNIX timestamp integers.
* @return
* An integer of the average interval.
*/
function xmlsitemap_calculate_changefreq($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($form, $form_state) {
foreach ($form_state['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) && !empty($form_state['values']['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_set_message(t('XML sitemap settings have been modified and the files should be regenerated. You can run cron manually to regenerate the cached files.', array('@run-cron' => url('admin/reports/status/run-cron', array('query' => drupal_get_destination())))), 'warning', FALSE);
return;
}
}
}
/**
* Add a link's XML sitemap options to the link's form.
*
* @param array $form
* Form array.
* @param string $entity
* Entity type id.
* @param string $bundle
* Bundle id.
* @param $id
* Entity id.
* @todo Add changefreq overridability.
*/
function xmlsitemap_add_form_link_options(array &$form, $entity, $bundle, $id) {
$info = xmlsitemap_get_link_info($entity, TRUE);
if (!$link = xmlsitemap_link_load($entity, $id)) {
$link = array();
}
$bundle_info = xmlsitemap_link_bundle_load($entity, $bundle);
$link += array(
'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();
$form['xmlsitemap'] = array(
'#type' => 'details',
'#tree' => TRUE,
'#title' => t('XML sitemap'),
'#collapsible' => TRUE,
'#collapsed' => !$link['status_override'] && !$link['priority_override'],
'#access' => $currentUser->hasPermission('administer xmlsitemap') || xmlsitemap_link_bundle_access($bundle_info),
'#group' => 'advanced',
'#attached' => array(
'js' => array(
'vertical-tabs' => drupal_get_path('module', 'xmlsitemap') . '/js/xmlsitemap.js',
),
),
);
// Hack to remove fieldset summary if Vertical tabs is not enabled.
if (!isset($form['advanced'])) {
unset($form['xmlsitemap']['#attached']['js']['vertical-tabs']);
}
if (xmlsitemap_link_bundle_access($bundle_info) && $path = xmlsitemap_get_bundle_path($entity, $bundle)) {
$form['xmlsitemap']['description'] = array(
'#prefix' => '
',
'#suffix' => '
',
'#markup' => t('The default XML sitemap settings for this @bundle can be changed here.', array('@bundle' => drupal_strtolower($info['bundle label']), '@link-type' => url($path, array('query' => drupal_get_destination())))),
);
}
// Show a warning if the link is not accessible and will not be included in
// the sitemap.
if ($id && !$link['access']) {
$form['xmlsitemap']['warning'] = array(
'#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'] = array(
'#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'] = array(
'#type' => 'value',
'#value' => $link['status_default'],
);
$form['xmlsitemap']['status_override'] = array(
'#type' => 'value',
'#value' => $link['status_override'],
);
// Priority field
$form['xmlsitemap']['priority'] = array(
'#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' => array(
'invisible' => array(
'select[name="xmlsitemap[status]"]' => array('value' => '0'),
),
),
);
$form['xmlsitemap']['changefreq'] = array(
'#type' => 'select',
'#title' => t('Change frequency'),
'#options' => xmlsitemap_get_changefreq_options(),
'#default_value' => $link['changefreq'],
'#description' => t('Select the frequency of changes.'),
'#states' => array(
'invisible' => array(
'select[name="xmlsitemap[status]"]' => array('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'] = array(
'select[name="xmlsitemap[status]"]' => array('value' => '1'),
);
}
$form['xmlsitemap']['priority_default'] = array(
'#type' => 'value',
'#value' => $link['priority_default'],
);
$form['xmlsitemap']['priority_override'] = array(
'#type' => 'value',
'#value' => $link['priority_override'],
);
array_unshift($form['actions']['submit']['#submit'], 'xmlsitemap_process_form_link_options');
if (isset($form['actions']['publish'])) {
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($form, &$form_state) {
$link = &$form_state['values']['xmlsitemap'];
$fields = array('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;
}
}
$entity = $form_state['controller']->getEntity();
$entity->xmlsitemap = $form_state['values']['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 (isset($form_state['values'][$bundle_key])) {
$bundle = $form_state['values'][$bundle_key];
$form['xmlsitemap']['#bundle'] = $bundle;
}
}
}
xmlsitemap_link_bundle_settings_save($entity, $bundle, $form_state['values']['xmlsitemap']);
$entity_info = $form['xmlsitemap']['#entity_info'];
if (!empty($form['xmlsitemap']['#show_message'])) {
drupal_set_message(t('XML sitemap settings for the @bundle-label %bundle have been saved.', array('@bundle-label' => drupal_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().
unset($form_state['values']['xmlsitemap']);
}
/**
* Gets xmlsitemap frequency options.
*
* @return array
* Frequency options.
*
* @todo Document this function.
* @todo Make these translatable
*/
function xmlsitemap_get_changefreq_options() {
return array(
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 http://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 array $context
* Context to be updated.
* @param bool $reset
* If TRUE, resets context info.
*
* @return array
*/
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);
}
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);
asort($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.
drupal_set_time_limit(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 http://drupal.org/node/638712 is fixed.
$batch = & batch_get();
$batch['progressive'] = FALSE;
// Run the batch process.
batch_process();
$lock->release($batch_callback);
return TRUE;
}
/**
* Gets a link from url.
*
* @static string $destination
* Destination option.
* @param string $url
* Url of the link.
* @param array $options
* Extra options of the url such as 'query'.
*
* @return array
* An array representing a link.
*/
function xmlsitemap_get_operation_link($url, $options = array()) {
static $destination;
if (!isset($destination)) {
$destination = drupal_get_destination();
}
$link = array('href' => $url) + $options;
$link += array('query' => $destination);
return $link;
}
/**
* Implements hook_entity_info_alter().
*/
function xmlsitemap_entity_info_alter(&$entity_info) {
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_user')) {
return $entity_info;
}
$entity_info['user']['bundle label'] = t('User');
$entity_info['user']['xmlsitemap'] = array(
'process callback' => 'xmlsitemap_user_xmlsitemap_process_user_links',
);
}
/**
* Implements hook_preprocess_HOOK() for theme_language_content_settings_table().
*/
function template_preprocess_xmlsitemap_content_settings_table(&$variables) {
// Add a render element representing the bundle language settings table.
$element = $variables['element'];
$header = array(
array(
'data' => $element['#bundle_label'],
'class' => array('bundle'),
),
array(
'data' => t('Configuration'),
'class' => array('operations'),
),
);
$rows = array();
foreach (Element::children($element) as $bundle) {
$rows[$bundle] = array(
'data' => array(
array(
'data' => array(
'#prefix' => '',
'#markup' => String::checkPlain($element[$bundle]['settings']['#label']),
),
'class' => array('bundle'),
),
array(
'data' => $element[$bundle]['settings'],
'class' => array('operations'),
),
),
'class' => array('bundle-settings'),
);
}
$variables['build'] = array(
'#title' => $element['#title'],
'#header' => $header,
'#rows' => $rows,
'#type' => 'table',
);
}
/**
* 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($variables) {
return '
' . $variables['build']['#title'] . '
' . drupal_render($variables['build']);
}
/**
* Returns the entity form for the given form.
*
* @param array $form_state
* The form state array holding the entity form.
*
* @return \Drupal\Core\Entity\EntityFormInterface;
* An instance of the entity form interface or FALSE if not an
* entity form.
*/
function xmlsitemap_form_controller(array $form_state) {
return isset($form_state['controller']) && $form_state['controller'] instanceof EntityForm ? $form_state['controller'] : FALSE;
}
/**
* Implements hook_form_alter().
*/
function xmlsitemap_form_alter(array &$form, array &$form_state, $form_id) {
$form_controller = xmlsitemap_form_controller($form_state);
$entity = $form_controller ? $form_controller->getEntity() : NULL;
$entity_type = $entity ? $entity->getEntityTypeId() : NULL;
$bundle = $entity ? $entity->bundle() : NULL;
$anonymous_user = new AnonymousUserSession();
if (!$form_controller) {
return;
}
if (!$entity || !$entity->access('view', $anonymous_user)) {
return;
}
// If this entity/bundle can be included in sitemap alter the form
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity_type . '_bundle_' . $bundle)) {
return;
}
if ($form_controller->getOperation() == 'delete') {
return;
}
xmlsitemap_add_form_link_options($form, $entity_type, $bundle, $entity->id());
$form['xmlsitemap']['#weight'] = 100;
}
/**
* Implements hook_xmlsitemap_index_links().
*/
function xmlsitemap_xmlsitemap_index_links($limit) {
$entity_types = \Drupal::entityManager()->getDefinitions();
$bundles = \Drupal::entityManager()->getAllBundleInfo();
foreach ($entity_types as $entity_type_id => $entity_type) {
$entity_bundles = array();
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity_type_id)) {
continue;
}
foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) {
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity_type_id . '_bundle_' . $bundle)) {
continue;
}
$entity_bundles[] = $bundle;
}
$query = \Drupal::entityManager()->getStorage($entity_type_id)->getQuery();
$query->range(0, $limit);
if ($entity_type->get('type')) {
$query->condition('type', $entity_bundles, 'IN');
}
$ids = $query->execute();
xmlsitemap_xmlsitemap_process_entity_links($entity_type_id, $ids);
}
}
/**
* Process sitemap links.
*
* @param array $entities
* An array of \Drupal\Core\Entity\EntityInterface objects.
* @param array
* Entity ids to be processed.
*/
function xmlsitemap_xmlsitemap_process_entity_links($entity_type, array $ids) {
$entities = entity_load_multiple($entity_type, $ids);
$anonymous_user = new AnonymousUserSession();
foreach ($entities as $entity) {
if (!$entity->access('view', $anonymous_user)) {
continue;
}
$link = xmlsitemap_entity_create_link($entity);
xmlsitemap_link_save($link);
}
}
/**
* Implements hook_entity_presave().
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that will be presaved.
*/
function xmlsitemap_entity_presave(EntityInterface $entity) {
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
$anonymous_user = new AnonymousUserSession();
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity_type . '_bundle_' . $bundle)) {
return;
}
if (!$entity->access('view', $anonymous_user)) {
return;
}
$id = $entity->id();
if (!empty($id)) {
$link = xmlsitemap_entity_create_link($entity);
xmlsitemap_link_save($link);
}
}
/**
* Implements hook_entity_insert().
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that will be inserted.
*/
function xmlsitemap_entity_insert(EntityInterface $entity) {
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
$anonymous_user = new AnonymousUserSession();
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity_type . '_bundle_' . $bundle)) {
return;
}
if (!$entity->access('view', $anonymous_user)) {
return;
}
$link = xmlsitemap_entity_create_link($entity);
xmlsitemap_link_save($link);
}
/**
* Implements hook_entity_update().
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that will be updated.
*/
function xmlsitemap_entity_update(EntityInterface $entity) {
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
$anonymous_user = new AnonymousUserSession();
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity_type . '_bundle_' . $bundle)) {
return;
}
if (!$entity->access('view', $anonymous_user)) {
return;
}
$link = xmlsitemap_entity_create_link($entity);
xmlsitemap_link_save($link);
}
/**
* Implements hook_entity_delete().
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that will be deleted.
*/
function xmlsitemap_entity_delete(EntityInterface $entity) {
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
xmlsitemap_link_delete($entity->getEntityTypeId(), $entity->id());
}
/**
* Create a sitemap link from an entity.
*
* The link will be saved as $entity->xmlsitemap.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose sitemap link will be created.
*/
function xmlsitemap_entity_create_link(EntityInterface $entity) {
if (!isset($entity->xmlsitemap)) {
$entity->xmlsitemap = array();
if ($entity->id() && $link = xmlsitemap_link_load($entity->getEntityTypeId(), $entity->id())) {
$entity->xmlsitemap = $link;
}
}
$settings = xmlsitemap_link_bundle_load($entity->getEntityTypeId(), $entity->bundle());
$uri = $entity->url();
$entity->xmlsitemap += array(
'type' => $entity->getEntityTypeId(),
'id' => (string) $entity->id(),
'subtype' => $entity->bundle(),
'status' => $settings['status'],
'status_default' => $settings['status'],
'status_override' => 0,
'priority' => $settings['priority'],
'priority_default' => $settings['priority'],
'priority_override' => 0,
'changefreq' => isset($settings['changefreq']) ? $settings['changefreq'] : 0
);
$url = $entity->url();
// The following values must always be checked because they are volatile.
$entity->xmlsitemap['loc'] = $uri;
$entity->xmlsitemap['access'] = isset($url) ? 1 : 0;
$language = $entity->language();
$entity->xmlsitemap['language'] = !empty($language) ? $language->getId() : LanguageInterface::LANGCODE_NOT_SPECIFIED;
return $entity->xmlsitemap;
}
/**
* Implements hook_xmlsitemap_context_info().
*/
function xmlsitemap_xmlsitemap_context_info() {
if (!\Drupal::moduleHandler()->moduleExists('language')) {
return array();
}
$context['language'] = array(
'label' => t('Language'),
'summary callback' => 'language_name',
'default' => \Drupal::languageManager()->getDefaultLanguage(),
);
return $context;
}
/**
* Implements hook_xmlsitemap_context().
*/
function xmlsitemap_xmlsitemap_context() {
if (!\Drupal::moduleHandler()->moduleExists('language')) {
return array();
}
$language = \Drupal::languageManager()->getCurrentLanguage();
$context['language'] = $language->getId();
return $context;
}
/**
* Implements hook_xmlsitemap_context_url_options().
*/
function xmlsitemap_xmlsitemap_context_url_options(array $context) {
$options = array();
if (!\Drupal::moduleHandler()->moduleExists('language')) {
return $options;
}
if (isset($context['language'])) {
$options['language'] = xmlsitemap_language_load($context['language']);
}
return $options;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function xmlsitemap_form_xmlsitemap_sitemap_edit_form_alter(&$form, $form_state) {
if (!\Drupal::moduleHandler()->moduleExists('language')) {
return;
}
$options = array();
$languages = \Drupal::languageManager()->getLanguages();
foreach ($languages as $language_key => $language) {
$options[$language_key] = $language->getName();
}
$options[LanguageInterface::LANGCODE_NOT_SPECIFIED] = t('Undefined language');
$form['context']['language'] = array(
'#type' => 'select',
'#title' => t('Language'),
'#options' => $options,
'#default_value' => isset($form['#entity']->context['language']) ? $form['#entity']->context['language'] : LanguageInterface::LANGCODE_NOT_SPECIFIED,
);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Set the regeneration needed flag if settings are changed.
*/
function xmlsitemap_form_language_admin_overview_form_alter(&$form, $form_state) {
if (!\Drupal::moduleHandler()->moduleExists('language')) {
return;
}
array_unshift($form['#submit'], 'xmlsitemap_form_submit_flag_regenerate');
}
/**
* Implements hook_query_TAG_alter().
*
* @see i18n_db_rewrite_where()
*/
function xmlsitemap_query_xmlsitemap_generate_alter(AlterableInterface $query) {
if (!\Drupal::moduleHandler()->moduleExists('language')) {
return;
}
$mode = \Drupal::config('xmlsitemap.settings')->get('i18n_selection_mode');
if (!$mode) {
$mode = 'simple';
}
$sitemap = $query->getMetaData('sitemap');
if (!isset($sitemap->context['language']) || $mode == 'off') {
return;
}
// Get languages to simplify query building.
$current = $sitemap->context['language'];
$default = \Drupal::languageManager()->getDefaultLanguage()->getId();
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', array($current, LanguageInterface::LANGCODE_NOT_SPECIFIED));
break;
case 'mixed':
// Mixed current language (if available) or default language (if not) and language neutral.
$query->condition('language', array($current, $default, LanguageInterface::LANGCODE_NOT_SPECIFIED));
break;
case 'default':
// Only default language and language neutral.
$query->condition('language', array($default, LanguageInterface::LANGCODE_NOT_SPECIFIED));
break;
case 'strict':
// Only current language (for nodes), simple for all other types.
$node_condition = db_and();
$node_condition->condition('type', 'node');
$node_condition->condition('language', $current);
$normal_condition = db_and();
$normal_condition->condition('type', 'node', '<>');
$normal_condition->condition('language', array($current, LanguageInterface::LANGCODE_NOT_SPECIFIED));
$condition = db_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_link_info().
*/
function xmlsitemap_xmlsitemap_link_info() {
return array(
'frontpage' => array(
'label' => t('Frontpage'),
'xmlsitemap' => array(
'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[] = array(
'type' => 'frontpage',
'id' => 0,
'loc' => '',
);
return $links;
}
/**
* Implements hook_xmlsitemap_sitemap_operations().
*/
function xmlsitemap_xmlsitemap_sitemap_operations() {
$operations['update'] = array(
'label' => t('Update cached files'),
'action past' => t('Updated'),
'callback' => 'xmlsitemap_sitemap_multiple_update',
);
return $operations;
}
/**
* Implements hook_query_TAG_alter().
*/
function xmlsitemap_query_xmlsitemap_link_bundle_access_alter(AlterableInterface $query) {
if ($query instanceof QueryInterface && $entity = $query->getMetaData('entity')) {
$info = $query->getMetaData('entity_info');
$bundle = $query->getMetaData('bundle');
if (empty($bundle)) {
$bundle = xmlsitemap_get_link_type_enabled_bundles($entity);
}
$query->entityCondition('bundle', $bundle, is_array($bundle) ? 'IN' : '=');
}
}
/**
* XML sitemap link type settings callback for frontpage link entity.
*
* @param array $form
* Form array.
*
* @return array
* Updated form.
*/
function xmlsitemap_link_frontpage_settings(&$form) {
if (\Drupal::currentUser()->hasPermission('administer site configuration')) {
$form['#description'] = t('The front page path can be changed in the site information configuration.', array('@url-frontpage' => url('admin/config/system/site-information')));
}
$form['xmlsitemap_frontpage_priority'] = array(
'#type' => 'select',
'#title' => t('Priority'),
'#options' => xmlsitemap_get_priority_options(),
'#default_value' => \Drupal::config('xmlsitemap.settings')->get('frontpage_priority'),
);
$form['xmlsitemap_frontpage_changefreq'] = array(
'#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 $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(&$form, $entity, array $entity_info) {
$priorities = xmlsitemap_get_priority_options(NULL, FALSE);
$statuses = xmlsitemap_get_status_options(NULL);
$destination = drupal_get_destination();
$rows = array();
$totals = array('total' => 0, 'indexed' => 0, 'visible' => 0);
foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
// Fetch current per-bundle link total and indexed counts.
if (!\Drupal::config('xmlsitemap.settings')->get('xmlsitemap_entity_' . $entity . '_bundle_' . $bundle)) {
continue;
}
$status = xmlsitemap_get_link_type_indexed_status($entity, $bundle);
$totals['total'] += $status['total'];
$totals['indexed'] += $status['indexed'];
$totals['visible'] += $status['visible'];
$row = array();
if (drupal_valid_path("admin/config/search/xmlsitemap/settings/$entity/$bundle")) {
$edit_link = xmlsitemap_get_operation_link("admin/config/search/xmlsitemap/settings/$entity/$bundle", array('title' => $bundle_info['label'], 'modal' => TRUE));
$row[] = l($edit_link['title'], $edit_link['href'], $edit_link);
}
else {
// Bundle labels are assumed to be un-escaped input.
$row[] = check_plain($bundle_info['label']);
}
$row[] = $statuses[$bundle_info['xmlsitemap']['status'] ? 1 : 0];
$row[] = $priorities[number_format($bundle_info['xmlsitemap']['priority'], 1)];
$row[] = $status['total'];
$row[] = $status['indexed'];
$row[] = $status['visible'];
$rows[] = $row;
}
if ($rows) {
$header = array(
isset($entity_info['bundle label']) ? $entity_info['bundle label'] : '',
t('Inclusion'),
t('Priority'),
t('Available'),
t('Indexed'),
t('Visible'),
);
$rows[] = array(
array(
'data' => t('Totals'),
'colspan' => 3,
'header' => TRUE,
),
array(
'data' => $totals['total'],
'header' => TRUE,
),
array(
'data' => $totals['indexed'],
'header' => TRUE,
),
array(
'data' => $totals['visible'],
'header' => TRUE,
),
);
$form['summary'] = array(
'#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 array $form_state
* Form state array.
* @param string $entity
* Entity type id.
* @param string $bundle
* Bundle id.
*/
function xmlsitemap_add_link_bundle_settings(array &$form, array &$form_state, $entity, $bundle) {
$entity_info = xmlsitemap_get_link_info($entity);
$bundle_info = xmlsitemap_link_bundle_load($entity, $bundle);
$form['xmlsitemap'] = array(
'#type' => 'details',
'#title' => t('XML sitemap'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#access' => \Drupal::currentUser()->hasPermission('administer xmlsitemap'),
'#group' => 'advanced',
'#attached' => array(
'js' => array(
'vertical-tabs' => drupal_get_path('module', 'xmlsitemap') . '/js/xmlsitemap.js',
),
),
'#tree' => TRUE,
'#entity' => $entity,
'#bundle' => $bundle,
'#entity_info' => $entity_info,
'#bundle_info' => $bundle_info,
);
// Hack to remove fieldset summary if Vertical tabs is not enabled.
if (!isset($form['advanced'])) {
unset($form['xmlsitemap']['#attached']['js']['vertical-tabs']);
}
$form['xmlsitemap']['description'] = array(
'#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'] = array(
'#type' => 'select',
'#title' => t('Inclusion'),
'#options' => xmlsitemap_get_status_options(),
'#default_value' => $bundle_info['status'],
);
$form['xmlsitemap']['priority'] = array(
'#type' => 'select',
'#title' => t('Default priority'),
'#options' => xmlsitemap_get_priority_options(),
'#default_value' => $bundle_info['priority'],
'#states' => array(
'invisible' => array(
'select[name="xmlsitemap[status]"]' => array('value' => '0'),
),
),
);
$form['xmlsitemap']['changefreq'] = array(
'#type' => 'select',
'#title' => t('Default change frequency'),
'#options' => xmlsitemap_get_changefreq_options(),
'#default_value' => $bundle_info['changefreq'],
'#states' => array(
'invisible' => array(
'select[name="xmlsitemap[status]"]' => array('value' => '0'),
),
),
);
}
/**
* Get a list of priority options.
*
* @param $default
* Include a 'default' option.
* @param $guides
* Add helpful indicators for the highest, middle and lowest values.
* @return
* An array of options.
*/
function xmlsitemap_get_priority_options($default = NULL, $guides = TRUE) {
$options = array();
$priorities = array(
'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)', array('@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 $default
* Include a 'default' option.
* @return
* An array of options.
*
* @see _xmlsitemap_translation_strings()
*/
function xmlsitemap_get_status_options($default = NULL) {
$options = array();
$statuses = array(
1 => t('Included'),
0 => t('Excluded'),
);
if (isset($default)) {
$default = $default ? 1 : 0;
$options['default'] = t('Default (@value)', array('@value' => drupal_strtolower($statuses[$default])));
}
$options += $statuses;
return $options;
}
/**
* Get the sitemap chunk/page of the current request.
*
* @param \Drupal\xmlsitemap\XmlSitemapInterface $sitemap
* Sitemap entity.
*
* @return int|string
* Returns current chunk of the sitemap.
*/
function xmlsitemap_get_current_chunk(XmlSitemapInterface $sitemap) {
// Check if we should be displaing the index.
$query = \Drupal::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.
*
* @throws NotFoundHttpException
* Throws an exception when sitemap xml file does not exist.
*
* @return \Symfony\Component\HttpFoundation\Response
* Complete response object.
*/
function xmlsitemap_output_file(Response $response, $file, array $headers = array()) {
if (!file_exists($file) || !is_readable($file)) {
throw new NotFoundHttpException();
}
$mtime = filemtime($file);
$last_modified = gmdate(DATE_RFC1123, $mtime);
$etag = '"' . md5($last_modified) . '"';
// See if the client has provided the required HTTP headers.
$request = \Drupal::request();
$if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? stripslashes($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
$if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
if ($if_modified_since && $if_none_match && $if_none_match == $etag && $if_modified_since == $last_modified) {
$response->setNotModified();
// All 304 responses must send an etag if the 200 response for the same object contained an etag
$response->headers->set('Etag', $etag);
return $response;
}
$headers += array(
'Content-type' => 'text/xml; charset=utf-8',
'Content-length' => filesize($file),
'Last-modified' => $last_modified,
'Etag' => $etag,
'Expires' => gmdate(DATE_RFC1123, $mtime + \Drupal::config('xmlsitemap.settings')->get('minimum_lifetime')),
'Cache-Control' => 'must-revalidate',
'X-Robots-Tag' => 'noindex, follow',
);
$response = xmlsitemap_file_transfer($response, $file, $headers);
return $response;
}
/**
* Read a file and put content into response.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* Response object.
* @param string $uri
* File uri.
* @param array $headers
* Response headers.
*
* @throws NotFoundHttpException
* Throws an exception when file is not readable.
*
* @return \Symfony\Component\HttpFoundation\Response
* Updated response.
*/
function xmlsitemap_file_transfer(Response $response, $uri, $headers) {
if (ob_get_level()) {
ob_end_clean();
}
foreach ($headers as $name => $value) {
$response->headers->set($name, $value);
}
$response->sendHeaders();
$content = '';
// Attempt to increase time to transfer file.
drupal_set_time_limit(240);
$scheme = file_default_scheme();
// Transfer file in 16 KB chunks to save memory usage.
if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) {
while (!feof($fd)) {
$content .= fread($fd, 1024 * 16);
}
fclose($fd);
$response->setContent($content);
}
else {
throw new NotFoundHttpException();
}
return $response;
}
/**
* Fetch a short blurb string about module maintainership and sponsors.
* This message will be FALSE in 'official' releases.
*
* @static string $blurb
* Blurb message.
*
* @param type $check_version
* @return string
*/
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 = array(
l('Symantec', 'http://www.symantec.com/'),
l('WebWise Solutions', 'http://www.webwiseone.com/'),
l('Volacci', 'http://www.volacci.com/'),
l('lanetro', 'http://www.lanetro.com/'),
l('Coupons Dealuxe', 'http://couponsdealuxe.com/'),
);
// 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 = _system_rebuild_module_data();
$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 = array();
\Drupal::moduleHandler()->loadAllIncludes('install');
foreach (module_implements('requirements') as $module) {
if (strpos($module, 'xmlsitemap') !== FALSE) {
$modules[] = $module;
}
}
\Drupal::cache()->set('xmlsitemap:registry:requirements', $modules);
}
$messages = array();
foreach ($modules as $module) {
module_load_install($module);
$requirements = module_invoke($module, 'requirements', 'runtime');
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && max(REQUIREMENT_OK, $requirement['severity'])) {
$messages[] = $requirement['description'];
}
}
}
if ($messages) {
$messages = array(
'#type' => 'item_list',
'#items' => array($messages)
);
$message = t('One or more problems were detected with your XML sitemap configuration: !messages', array('!messages' => drupal_render($messages)));
drupal_set_message($message, 'warning', FALSE);
if (\Drupal::currentUser()->hasPermission('access site reports')) {
drupal_set_message(t('Check the status report for more information.', array('@status-report' => url('admin/reports/status'))), 'warning', FALSE);
}
}
}
return !empty($messages);
}
// BATCH OPERATIONS ------------------------------------------------------------
/**
* Perform operations before rebuilding the sitemap.
*/
function _xmlsitemap_regenerate_before() {
\Drupal::service('xmlsitemap_generator')->regenerateBefore();
}
/**
* Batch information callback for regenerating the sitemap files.
*
* @param $smids
* An optional array of XML sitemap IDs. If not provided, it will load all
* existing XML sitemaps.
*/
function xmlsitemap_regenerate_batch(array $smids = array()) {
if (empty($smids)) {
$sitemaps = \Drupal::entityManager()->getStorage('xmlsitemap')->loadMultiple();
foreach ($sitemaps as $sitemap) {
$smids[] = $sitemap->id();
}
}
$t = 't';
$batch = array(
'operations' => array(),
'error_message' => $t('An error has occurred.'),
'finished' => 'xmlsitemap_regenerate_batch_finished',
'title' => t('Regenerating Sitemap'),
'file' => drupal_get_path('module', 'xmlsitemap') . '/xmlsitemap.generate.inc',
);
// Set the regenerate flag in case something fails during file generation.
$batch['operations'][] = array('xmlsitemap_batch_variable_set', array(array('xmlsitemap_regenerate_needed' => TRUE)));
// @todo Get rid of this batch operation.
$batch['operations'][] = array('_xmlsitemap_regenerate_before', array());
// Generate all the sitemap pages for each context.
foreach ($smids as $smid) {
$batch['operations'][] = array('xmlsitemap_regenerate_batch_generate', array($smid));
$batch['operations'][] = array('xmlsitemap_regenerate_batch_generate_index', array($smid));
}
// Clear the regeneration flag.
$batch['operations'][] = array('xmlsitemap_batch_variable_set', array(array('xmlsitemap_regenerate_needed' => FALSE)));
return $batch;
}
/**
* Batch callback; generate all pages of a sitemap.
*
* @param string $smid
* Sitemap entity id.
* @param array $context
* Sitemap context.
*/
function xmlsitemap_regenerate_batch_generate($smid, array &$context = array()) {
\Drupal::service('xmlsitemap_generator')->regenerateBatchGenerate($smid, $context);
}
/**
* Batch callback; generate the index page of a sitemap.
*
* @param string $smid
* Sitemap entity id.
* @param array $context
* Sitemap context.
*/
function xmlsitemap_regenerate_batch_generate_index($smid, array &$context = array()) {
\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 integer $elapsed
* Time elapsed.
*/
function xmlsitemap_regenerate_batch_finished($success, $results, $operations, $elapsed) {
\Drupal::service('xmlsitemap_generator')->regenerateBatchFinished($success, $results, $operations, $elapsed);
}
/**
* Batch information callback for rebuilding the sitemap data.
*
* @param array $entities
* Entities to be rebuild.
* @param bool $save_custom
* Save custom data.
*
* @return array
* Batch array.
*/
function xmlsitemap_rebuild_batch(array $entities, $save_custom = FALSE) {
$batch = array(
'operations' => array(),
'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'][] = array('xmlsitemap_batch_variable_set', array(array('xmlsitemap_rebuild_needed' => TRUE)));
// Purge any links first.
$batch['operations'][] = array('xmlsitemap_rebuild_batch_clear', array($entities, (bool) $save_custom));
// Fetch all the sitemap links and save them to the {xmlsitemap} table.
foreach ($entities as $entity) {
$info = xmlsitemap_get_link_info($entity);
$batch['operations'][] = array($info['xmlsitemap']['rebuild callback'], array($entity));
}
// Clear the rebuild flag.
$batch['operations'][] = array('xmlsitemap_batch_variable_set', array(array('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 entites.
*
* @param array $entities
* Entities to rebuild.
* @param bool $save_custom
* Save custom data.
* @param array $context
* Context to be rebuilt.
*/
function xmlsitemap_rebuild_batch_clear(array $entities, $save_custom, &$context = array()) {
\Drupal::service('xmlsitemap_generator')->rebuildBatchClear($entities, $save_custom, $context);
}
/**
* Batch callback; fetch and add the sitemap links for a specific entity type.
*
* @param string $entity
* Entity type id.
* @param array context
* Sitemap context.
*/
function xmlsitemap_rebuild_batch_fetch($entity, &$context) {
if (!isset($context['sandbox']['info'])) {
$context['sandbox']['info'] = xmlsitemap_get_link_info($entity);
$context['sandbox']['progress'] = 0;
$context['sandbox']['last_id'] = 0;
}
$info = $context['sandbox']['info'];
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', $entity);
$query->entityCondition('entity_id', $context['sandbox']['last_id'], '>');
$query->addTag('xmlsitemap_link_bundle_access');
$query->addTag('xmlsitemap_rebuild');
$query->addMetaData('entity', $entity);
$query->addMetaData('entity_info', $info);
if (!isset($context['sandbox']['max'])) {
$count_query = clone $query;
$count_query->count();
$context['sandbox']['max'] = $count_query->execute();
if (!$context['sandbox']['max']) {
// If there are no items to process, skip everything else.
return;
}
}
// PostgreSQL cannot have the ORDERED BY in the count query.
$query->entityOrderBy('entity_id');
// get batch limit
$limit = \Drupal::config('xmlsitemap.settings')->get('batch_limit');
$query->range(0, $limit);
$result = $query->execute();
$ids = array_keys($result[$entity]);
$info['xmlsitemap']['process callback']($ids);
$context['sandbox']['last_id'] = end($ids);
$context['sandbox']['progress'] += count($ids);
$context['message'] = t('Now processing %entity @last_id (@progress of @count).', array('%entity' => $entity, '@last_id' => $context['sandbox']['last_id'], '@progress' => $context['sandbox']['progress'], '@count' => $context['sandbox']['max']));
if ($context['sandbox']['progress'] >= $context['sandbox']['max']) {
$context['finished'] = 1;
}
else {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}
}
/**
* 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 integer $elapsed
* Time elapsed.
*/
function xmlsitemap_rebuild_batch_finished($success, $results, $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 = array();
$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['entity keys']['bundle']) && !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;
}
else {
$rebuild_types[] = $entity;
}
}
return $rebuild_types;
}