diff --git a/xmlsitemap.admin.inc b/xmlsitemap.admin.inc
index e55549b..769987e 100644
--- a/xmlsitemap.admin.inc
+++ b/xmlsitemap.admin.inc
@@ -402,6 +402,18 @@ function xmlsitemap_settings_form($form, &$form_state) {
     '#default_value' => variable_get('xmlsitemap_disable_cron_regeneration', 0),
     '#description' => t('This can be disabled if other methods are being used to generate the sitemap files, like <em>drush xmlsitemap-regenerate</em>.'),
   );
+  $form['advanced']['xmlsitemap_language_fallback'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Include links to untranslated entities to localized sitemaps'),
+    '#description' => t('If the option disabled, it will not include links to localized sitemaps even the language fallback enabled.'),
+    '#default_value' => variable_get('xmlsitemap_language_fallback', TRUE),
+  );
+  $form['advanced']['xmlsitemap_language_remove_neutral'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Exclude links to language neutral entities to localized sitemaps'),
+    '#description' => t('If the option is enabled, it will not include links to language neutral to the localized sitemaps.'),
+    '#default_value' => variable_get('xmlsitemap_language_remove_neutral', FALSE),
+  );
   $form['advanced']['xmlsitemap_output_elements'] = array(
     '#type' => 'checkboxes',
     '#title' => t('Enable or disable the individual @loc elements from output', array('@loc' => '<loc>')),
diff --git a/xmlsitemap.admin.inc.orig b/xmlsitemap.admin.inc.orig
new file mode 100644
index 0000000..e55549b
--- /dev/null
+++ b/xmlsitemap.admin.inc.orig
@@ -0,0 +1,913 @@
+<?php
+
+/**
+ * @file
+ * Administrative page callbacks for the xmlsitemap module.
+ *
+ * @ingroup xmlsitemap
+ */
+
+/**
+ * Render a tableselect list of XML sitemaps for the main admin page.
+ */
+function xmlsitemap_sitemap_list_form() {
+  $destination = drupal_get_destination();
+
+  // Build the 'Update options' form.
+  $form['#operations'] = module_invoke_all('xmlsitemap_sitemap_operations');
+  $operations = array();
+  foreach ($form['#operations'] as $operation => $operation_info) {
+    $operations[$operation] = $operation_info['label'];
+  }
+  asort($operations);
+
+  $form['operations'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Update options'),
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $form['operations']['operation'] = array(
+    '#type' => 'select',
+    '#options' => $operations,
+    '#default_value' => 'update',
+  );
+  $form['operations']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update'),
+  );
+
+  $contexts = xmlsitemap_get_context_info();
+
+  $header = array();
+  $header['url'] = array('data' => t('URL'));
+  foreach ($contexts as $context_key => $context_info) {
+    if (!empty($context_info['summary callback'])) {
+      $header['context_' . $context_key] = $context_info['label'];
+    }
+  }
+  $header['updated'] = array(
+    'data' => t('Last updated'),
+    'field' => 'updated',
+    'sort' => 'asc',
+  );
+  $header['links'] = array('data' => t('Links'), 'field' => 'links');
+  $header['chunks'] = array('data' => t('Pages'), 'field' => 'chunks');
+  $header['operations'] = array('data' => t('Operations'));
+
+  $query = db_select('xmlsitemap_sitemap');
+  $query->fields('xmlsitemap_sitemap', array('smid'));
+  $query->extend('TableSort')->orderByHeader($header);
+  $smids = $query->execute()->fetchCol();
+  $sitemaps = $smids ? xmlsitemap_sitemap_load_multiple($smids) : array();
+
+  $options = array();
+  foreach ($sitemaps as $smid => $sitemap) {
+    $sitemap->url = url($sitemap->uri['path'], $sitemap->uri['options']);
+
+    $options[$smid]['url'] = array(
+      'data' => array(
+        '#type' => 'link',
+        '#title' => $sitemap->url,
+        '#href' => $sitemap->url,
+      ),
+    );
+
+    foreach ($contexts as $context_key => $context_info) {
+      if (!empty($context_info['summary callback'])) {
+        $options[$smid]['context_' . $context_key] = _xmlsitemap_sitemap_context_summary($sitemap, $context_key, $context_info);
+      }
+    }
+
+    $options[$smid]['updated'] = $sitemap->updated ? format_date($sitemap->updated, 'short') : t('Never');
+    $options[$smid]['links'] = $sitemap->updated ? $sitemap->links : '-';
+    $options[$smid]['chunks'] = $sitemap->updated ? $sitemap->chunks : '-';
+
+    // @todo Highlight sitemaps that need updating.
+    // $options[$smid]['#attributes']['class'][] = 'warning';
+    $operations = array();
+    $operations['edit'] = xmlsitemap_get_operation_link('admin/config/search/xmlsitemap/edit/' . $smid, array('title' => t('Edit'), 'modal' => TRUE));
+    $operations['delete'] = xmlsitemap_get_operation_link('admin/config/search/xmlsitemap/delete/' . $smid, array('title' => t('Delete'), 'modal' => TRUE));
+    if ($operations) {
+      $options[$smid]['operations'] = array(
+        'data' => array(
+          '#theme' => 'links',
+          '#links' => $operations,
+          '#attributes' => array('class' => array('links', 'inline')),
+        ),
+      );
+    }
+    else {
+      $options[$smid]['operations'] = t('None (sitemap locked)');
+    }
+  }
+
+  $form['sitemaps'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $options,
+    '#empty' => t('No XML sitemaps available.') . ' ' . l(t('Add a new XML sitemap'), 'admin/config/search/xmlsitemap/add'),
+  );
+  return $form;
+}
+
+/**
+ * Validate xmlsitemap_sitemap_list_form submissions.
+ */
+function xmlsitemap_sitemap_list_form_validate($form, &$form_state) {
+  $form_state['values']['sitemaps'] = array_filter($form_state['values']['sitemaps']);
+
+  // Error if there are no items to select.
+  if (!count($form_state['values']['sitemaps'])) {
+    form_set_error('', t('No sitemaps selected.'));
+  }
+}
+
+/**
+ * Process xmlsitemap_sitemap_list_form submissions.
+ *
+ * Execute the chosen 'Update option' on the selected sitemaps.
+ */
+function xmlsitemap_sitemap_list_form_submit($form, &$form_state) {
+  $operation = $form['#operations'][$form_state['values']['operation']];
+
+  // Filter out unchecked sitemaps.
+  $sitemaps = array_filter($form_state['values']['sitemaps']);
+
+  if (!empty($operation['confirm']) && empty($form_state['values']['confirm'])) {
+    // We need to rebuild the form to go to a second step. For example, to
+    // show the confirmation form for the deletion of redirects.
+    $form_state['rebuild'] = TRUE;
+  }
+  else {
+    $function = $operation['callback'];
+
+    // Add in callback arguments if present.
+    if (isset($operation['callback arguments'])) {
+      $args = array_merge(array($sitemaps), $operation['callback arguments']);
+    }
+    else {
+      $args = array($sitemaps);
+    }
+    call_user_func_array($function, $args);
+
+    $count = count($form_state['values']['sitemaps']);
+    drupal_set_message(
+      format_plural(
+        count($sitemaps), '@action @count XML sitemap.', '@action @count XML sitemaps.',
+        array('@action' => $operation['action past'], '@count' => $count)
+      )
+    );
+  }
+}
+
+/**
+ * Edit Form.
+ */
+function xmlsitemap_sitemap_edit_form(array $form, array &$form_state, stdClass $sitemap = NULL) {
+  _xmlsitemap_set_breadcrumb();
+
+  if (!isset($sitemap)) {
+    $sitemap = new stdClass();
+    $sitemap->smid = NULL;
+    $sitemap->context = array();
+  }
+
+  $form['#sitemap'] = $sitemap;
+
+  $form['smid'] = array(
+    '#type' => 'value',
+    '#value' => $sitemap->smid,
+  );
+  // The context settings should be form_alter'ed by the context modules.
+  $form['context'] = array(
+    '#tree' => TRUE,
+  );
+
+  $form['actions'] = array(
+    '#type' => 'actions',
+  );
+  $form['actions']['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+  $form['actions']['cancel'] = array(
+    '#type' => 'link',
+    '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/xmlsitemap',
+    '#title' => t('Cancel'),
+  );
+
+  // Let other modules alter this form with their context settings.
+  $form['#pre_render'][] = 'xmlsitemap_sitemap_edit_form_pre_render';
+
+  return $form;
+}
+
+/**
+ * Edit Form Pre Render.
+ */
+function xmlsitemap_sitemap_edit_form_pre_render($form) {
+  $visible_children = element_get_visible_children($form['context']);
+  if (empty($visible_children)) {
+    $form['context']['empty'] = array(
+      '#type' => 'markup',
+      '#markup' => '<p>' . t('There are currently no XML sitemap contexts available.') . '</p>',
+    );
+  }
+  return $form;
+}
+
+/**
+ * Edit form validate.
+ */
+function xmlsitemap_sitemap_edit_form_validate($form, &$form_state) {
+  // If there are no context options, the $form_state['values']['context']
+  // disappears.
+  $form_state['values'] += array('context' => array());
+  $existing = xmlsitemap_sitemap_load_by_context($form_state['values']['context']);
+  if ($existing && $existing->smid != $form_state['values']['smid']) {
+    form_set_error('context', t('A sitemap with the same context already exists.'));
+  }
+}
+
+/**
+ * Edit Form Submit.
+ */
+function xmlsitemap_sitemap_edit_form_submit($form, &$form_state) {
+  form_state_values_clean($form_state);
+  $sitemap = (object) $form_state['values'];
+  xmlsitemap_sitemap_save($sitemap);
+  drupal_set_message(t('The sitemap has been saved.'));
+  $form_state['redirect'] = 'admin/config/search/xmlsitemap';
+}
+
+/**
+ * Delete form.
+ */
+function xmlsitemap_sitemap_delete_form(array $form, array &$form_state, stdClass $sitemap) {
+  _xmlsitemap_set_breadcrumb();
+
+  $count = (int) db_query("SELECT COUNT(smid) FROM {xmlsitemap_sitemap}")->fetchField();
+  if ($count === 1 && empty($_POST)) {
+    drupal_set_message(t('It is not recommended to delete the only XML sitemap.'), 'error');
+  }
+
+  $form['#sitemap'] = $sitemap;
+  $form['smid'] = array(
+    '#type' => 'value',
+    '#value' => $sitemap->smid,
+  );
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the XML sitemap?'),
+    'admin/config/search/xmlsitemap',
+    '',
+    t('Delete'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Delete form submit.
+ */
+function xmlsitemap_sitemap_delete_form_submit($form, $form_state) {
+  xmlsitemap_sitemap_delete($form_state['values']['smid']);
+  drupal_set_message(t('The sitemap has been deleted.'));
+  $form_state['redirect'] = 'admin/config/search/xmlsitemap';
+}
+
+/**
+ * Form builder; Administration settings form.
+ *
+ * @see system_settings_form()
+ * @see xmlsitemap_settings_form_validate()
+ */
+function xmlsitemap_settings_form($form, &$form_state) {
+  global $base_url;
+  $form['xmlsitemap_minimum_lifetime'] = array(
+    '#type' => 'select',
+    '#title' => t('Minimum sitemap lifetime'),
+    '#options' => array(0 => t('No minimum')) + drupal_map_assoc(array(300,
+      900,
+      1800,
+      3600,
+      10800,
+      21600,
+      43200,
+      86400,
+      172800,
+      259200,
+      604800,
+    ), 'format_interval'),
+    '#description' => t('The minimum amount of time that will elapse before the sitemaps are regenerated. The sitemaps will also only be regenerated on cron if any links have been added, updated, or deleted.') . '<br />' . t('Recommended value: %value.', array('%value' => t('1 day'))),
+    '#default_value' => variable_get('xmlsitemap_minimum_lifetime', 0),
+  );
+  $form['xmlsitemap_xsl'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Include a stylesheet in the sitemaps for humans.'),
+    '#description' => t('When enabled, this will add formatting and tables with sorting to make it easier to view the XML sitemap data instead of viewing raw XML output. Search engines will ignore this.'),
+    '#default_value' => variable_get('xmlsitemap_xsl', 1),
+  );
+  $form['xmlsitemap_prefetch_aliases'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Prefetch URL aliases during sitemap generation.'),
+    '#description' => t('When enabled, this will fetch all URL aliases at once instead of one at a time during sitemap generation. For medium or large sites, it is recommended to disable this feature as it uses a lot of memory.'),
+    '#default_value' => variable_get('xmlsitemap_prefetch_aliases', 1),
+  );
+
+  $form['advanced'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Advanced settings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => !variable_get('xmlsitemap_developer_mode', 0),
+    '#weight' => 10,
+  );
+  $form['advanced']['xmlsitemap_chunk_size'] = array(
+    '#type' => 'select',
+    '#title' => t('Number of links in each sitemap page'),
+    '#options' => array('auto' => t('Automatic (recommended)')) + drupal_map_assoc(array(
+      100,
+      500,
+      1000,
+      2500,
+      5000,
+      10000,
+      25000,
+      XMLSITEMAP_MAX_SITEMAP_LINKS,
+    )),
+    '#default_value' => xmlsitemap_var('chunk_size'),
+    // @todo This description is not clear.
+    '#description' => t('If there are problems with rebuilding the sitemap, you may want to manually set this value. If you have more than @max links, an index with multiple sitemap pages will be generated. There is a maximum of @max sitemap pages.', array('@max' => XMLSITEMAP_MAX_SITEMAP_LINKS)),
+  );
+  $form['advanced']['xmlsitemap_batch_limit'] = array(
+    '#type' => 'select',
+    '#title' => t('Maximum number of sitemap links to process at once'),
+    '#options' => drupal_map_assoc(array(
+      5,
+      10,
+      25,
+      50,
+      100,
+      250,
+      500,
+      1000,
+      2500,
+      5000,
+    )),
+    '#default_value' => xmlsitemap_var('batch_limit'),
+    '#description' => t('If you have problems running cron or rebuilding the sitemap, you may want to lower this value.'),
+  );
+  if (!xmlsitemap_check_directory()) {
+    form_set_error('xmlsitemap_path', t('The directory %directory does not exist or is not writable.', array('%directory' => xmlsitemap_get_directory())));
+  }
+  $form['advanced']['xmlsitemap_path'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Sitemap cache directory'),
+    '#default_value' => variable_get('xmlsitemap_path', 'xmlsitemap'),
+    '#size' => 30,
+    '#maxlength' => 255,
+    '#description' => t('Subdirectory where the sitemap data will be stored. This folder <strong>must not be shared</strong> with any other Drupal site or install using XML sitemap.'),
+    '#field_prefix' => file_build_uri(''),
+    '#required' => TRUE,
+  );
+  $form['advanced']['xmlsitemap_base_url'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default base URL'),
+    '#default_value' => variable_get('xmlsitemap_base_url', $base_url),
+    '#size' => 30,
+    '#description' => t('This is the default base URL used for sitemaps and sitemap links.'),
+    '#required' => TRUE,
+  );
+  $form['advanced']['xmlsitemap_lastmod_format'] = array(
+    '#type' => 'select',
+    '#title' => t('Last modification date format'),
+    '#options' => array(
+      XMLSITEMAP_LASTMOD_SHORT => t('Short'),
+      XMLSITEMAP_LASTMOD_MEDIUM => t('Medium'),
+      XMLSITEMAP_LASTMOD_LONG => t('Long'),
+    ),
+    '#default_value' => variable_get('xmlsitemap_lastmod_format', XMLSITEMAP_LASTMOD_MEDIUM),
+  );
+  foreach ($form['advanced']['xmlsitemap_lastmod_format']['#options'] as $key => &$label) {
+    $label .= ' (' . gmdate($key, REQUEST_TIME) . ')';
+  }
+  $form['advanced']['xmlsitemap_developer_mode'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable developer mode to expose additional settings.'),
+    '#default_value' => variable_get('xmlsitemap_developer_mode', 0),
+  );
+  $form['advanced']['xmlsitemap_disable_cron_regeneration'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Disable cron generation of sitemap files.'),
+    '#default_value' => variable_get('xmlsitemap_disable_cron_regeneration', 0),
+    '#description' => t('This can be disabled if other methods are being used to generate the sitemap files, like <em>drush xmlsitemap-regenerate</em>.'),
+  );
+  $form['advanced']['xmlsitemap_output_elements'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Enable or disable the individual @loc elements from output', array('@loc' => '<loc>')),
+    '#options' => array(
+      'lastmod' => t('Last modification date: @lastmod', array('@lastmod' => '<lastmod>')),
+      'changefreq' => t('Change frequency: @changfreq', array('@changfreq' => '<changefreq>')),
+      'priority' => t('Priority: @priority', array('@priority' => '<priority>')),
+    ),
+    '#default_value' => drupal_map_assoc(variable_get('xmlsitemap_output_elements', array(
+      'lastmod',
+      'changefreq',
+      'priority',
+    ))),
+  );
+
+  $form['xmlsitemap_settings'] = array(
+    '#type' => 'vertical_tabs',
+    '#weight' => 20,
+  );
+
+  $entities = xmlsitemap_get_link_info(NULL, TRUE);
+  module_load_all_includes('xmlsitemap.inc');
+  foreach ($entities as $entity => $entity_info) {
+    $form[$entity] = array(
+      '#type' => 'fieldset',
+      '#title' => $entity_info['label'],
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'xmlsitemap_settings',
+    );
+
+    if (!empty($entity_info['bundles'])) {
+      // If this entity has bundles, show a bundle setting summary.
+      xmlsitemap_add_form_entity_summary($form[$entity], $entity, $entity_info);
+    }
+
+    if (!empty($entity_info['xmlsitemap']['settings callback'])) {
+      // Add any entity-specific settings.
+      $entity_info['xmlsitemap']['settings callback']($form[$entity]);
+    }
+
+    // Ensure that the entity fieldset is not shown if there are no accessible
+    // sub-elements.
+    $form[$entity]['#access'] = (bool) element_get_visible_children($form[$entity]);
+  }
+
+  $form['#validate'][] = 'xmlsitemap_settings_form_validate';
+  $form['#submit'][] = 'xmlsitemap_settings_form_submit';
+  array_unshift($form['#submit'], 'xmlsitemap_form_submit_flag_regenerate');
+  $form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
+
+  $form = system_settings_form($form);
+  return $form;
+}
+
+/**
+ * Form validator; Check the sitemap files directory.
+ *
+ * @see xmlsitemap_settings_form()
+ */
+function xmlsitemap_settings_form_validate($form, &$form_state) {
+  // Check that the chunk size will not create more than 1000 chunks.
+  $chunk_size = $form_state['values']['xmlsitemap_chunk_size'];
+  if ($chunk_size != 'auto' && $chunk_size != 50000 && (xmlsitemap_get_link_count() / $chunk_size) > 1000) {
+    form_set_error('xmlsitemap_chunk_size', t('The sitemap page link count of @size will create more than 1,000 sitemap pages. Please increase the link count.', array('@size' => $chunk_size)));
+  }
+
+  $base_url = &$form_state['values']['xmlsitemap_base_url'];
+  $base_url = rtrim($base_url, '/');
+  if ($base_url != '' && !valid_url($base_url, TRUE)) {
+    form_set_error('xmlsitemap_base_url', t('Invalid base URL.'));
+  }
+}
+
+/**
+ * Submit handler;.
+ *
+ * @see xmlsitemap_settings_form()
+ */
+function xmlsitemap_settings_form_submit($form, $form_state) {
+  // Save any changes to the frontpage link.
+  xmlsitemap_link_save(array('type' => 'frontpage', 'id' => 0, 'loc' => ''));
+}
+
+/**
+ * Menu callback; Confirm rebuilding of the sitemap.
+ *
+ * @see xmlsitemap_rebuild_form_submit()
+ */
+function xmlsitemap_rebuild_form() {
+  if (!$_POST && !variable_get('xmlsitemap_rebuild_needed', FALSE)) {
+    if (!variable_get('xmlsitemap_regenerate_needed', FALSE)) {
+      drupal_set_message(t('Your sitemap is up to date and does not need to be rebuilt.'), 'error');
+    }
+    else {
+      $_REQUEST += array('destination' => 'admin/config/search/xmlsitemap');
+      drupal_set_message(t('A rebuild is not necessary. If you are just wanting to regenerate the XML sitemap files, you can <a href="@link-cron">run cron manually</a>.', array('@link-cron' => url('admin/reports/status/run-cron', array('query' => drupal_get_destination())))), 'warning');
+    }
+  }
+
+  // Build a list of rebuildable link types.
+  module_load_include('generate.inc', 'xmlsitemap');
+  $rebuild_types = xmlsitemap_get_rebuildable_link_types();
+
+  $form['entities'] = array(
+    '#type' => 'select',
+    '#title' => t("Select which link types you would like to rebuild"),
+    '#description' => t('If no link types are selected, the sitemap files will just be regenerated.'),
+    '#multiple' => TRUE,
+    '#options' => drupal_map_assoc($rebuild_types),
+    '#default_value' => variable_get('xmlsitemap_rebuild_needed', FALSE) || !variable_get('xmlsitemap_developer_mode', 0) ? $rebuild_types : array(),
+    '#access' => variable_get('xmlsitemap_developer_mode', 0),
+  );
+  $form['save_custom'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Save and restore any custom inclusion and priority links.'),
+    '#default_value' => TRUE,
+  );
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to rebuild the XML sitemap?'),
+    'admin/config/search/xmlsitemap',
+    '',
+    t('Rebuild sitemap'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Submit handler; Starts the sitemap rebuild batch.
+ *
+ * @see xmlsitemap_rebuild_form()
+ * @see xmlsitemap_rebuild_batch()
+ */
+function xmlsitemap_rebuild_form_submit($form, &$form_state) {
+  module_load_include('generate.inc', 'xmlsitemap');
+  $batch = xmlsitemap_rebuild_batch($form_state['values']['entities'], $form_state['values']['save_custom']);
+  batch_set($batch);
+  $form_state['redirect'] = 'admin/config/search/xmlsitemap';
+}
+
+/**
+ * Add a table summary for an entity and its bundles.
+ */
+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.
+    $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.
+ */
+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' => 'fieldset',
+    '#title' => t('XML sitemap'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#access' => user_access('administer xmlsitemap'),
+    '#group' => 'additional_settings',
+    '#attached' => array(
+      'js' => array(
+        'vertical-tabs' => drupal_get_path('module', 'xmlsitemap') . '/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['additional_settings'])) {
+    unset($form['xmlsitemap']['#attached']['js']['vertical-tabs']);
+  }
+
+  $form['xmlsitemap']['description'] = array(
+    '#prefix' => '<div class="description">',
+    '#suffix' => '</div>',
+    '#markup' => t('Changing these type settings will affect any items of this type that have either inclusion or priority set to default.'),
+  );
+  $form['xmlsitemap']['status'] = 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 += array('#submit' => array());
+  array_unshift($form['#submit'], 'xmlsitemap_link_bundle_settings_form_submit');
+
+  if (isset($form['submit'])) {
+    $form['submit'] += array('#weight' => 40);
+  }
+  if (isset($form['delete'])) {
+    $form['delete'] += array('#weight' => 50);
+  }
+}
+
+/**
+ * Link bundle settings form.
+ */
+function xmlsitemap_link_bundle_settings_form(array $form, array &$form_state, array $bundle) {
+  if (empty($form_state['ajax']) && $admin_path = xmlsitemap_get_bundle_path($bundle['entity'], $bundle['bundle'])) {
+    // If this is a non-ajax form, redirect to the bundle administration page.
+    $destination = drupal_get_destination();
+    unset($_GET['destination']);
+    drupal_goto($admin_path, array('query' => $destination));
+  }
+  else {
+    drupal_set_title(t('@bundle XML sitemap settings', array('@bundle' => $bundle['info']['label'])));
+  }
+
+  $form = array();
+  xmlsitemap_add_link_bundle_settings($form, $form_state, $bundle['entity'], $bundle['bundle']);
+  $form['xmlsitemap']['#type'] = 'markup';
+  $form['xmlsitemap']['#value'] = '';
+  $form['xmlsitemap']['#access'] = TRUE;
+  $form['xmlsitemap']['#show_message'] = TRUE;
+
+  $form['actions'] = array(
+    '#type' => 'actions',
+  );
+  $form['actions']['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+  $form['actions']['cancel'] = array(
+    '#value' => l(t('Cancel'), isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/xmlsitemap/settings'),
+    '#weight' => 10,
+  );
+
+  return $form;
+}
+
+/**
+ * Add a link's XML sitemap options to the link's form.
+ *
+ * @todo Add changefreq overridability.
+ */
+function xmlsitemap_add_form_link_options(array &$form, $entity, $bundle, $id) {
+  $info = xmlsitemap_get_link_info($entity);
+
+  if (!$info || empty($info['bundles'][$bundle])) {
+    return;
+  }
+
+  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,
+  );
+
+  $form['xmlsitemap'] = array(
+    '#type' => 'fieldset',
+    '#tree' => TRUE,
+    '#title' => t('XML sitemap'),
+    '#collapsible' => TRUE,
+    '#collapsed' => !$link['status_override'] && !$link['priority_override'],
+    '#access' => user_access('administer xmlsitemap') || user_access('use xmlsitemap') || xmlsitemap_link_bundle_access($bundle_info),
+    '#group' => 'additional_settings',
+    '#attached' => array(
+      'js' => array(
+        'vertical-tabs' => drupal_get_path('module', 'xmlsitemap') . '/xmlsitemap.js',
+      ),
+    ),
+  );
+
+  // Hack to remove fieldset summary if Vertical tabs is not enabled.
+  if (!isset($form['additional_settings'])) {
+    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' => '<div class="description">',
+      '#suffix' => '</div>',
+      '#markup' => t('The default XML sitemap settings for this @bundle can be changed <a href="@link-type">here</a>.', 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' => '<p><strong>',
+      '#suffix' => '</strong></p>',
+      '#value' => ('This item is not currently visible to anonymous users, so it will not be included in the sitemap.'),
+    );
+  }
+
+  // Status field (inclusion/exclusion)
+  $form['xmlsitemap']['status'] = 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'),
+      ),
+    ),
+  );
+  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'],
+  );
+  // Add the submit handler to adjust the default values if selected.
+  $form += array('#submit' => array());
+  if (!in_array('xmlsitemap_process_form_link_options', $form['#submit'])) {
+    array_unshift($form['#submit'], 'xmlsitemap_process_form_link_options');
+  }
+}
+
+/**
+ * Get a list of priority options.
+ *
+ * @param string $default
+ *   Include a 'default' option.
+ * @param string $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 = 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 string $default
+ *   Include a 'default' option.
+ *
+ * @return array
+ *   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;
+}
diff --git a/xmlsitemap.install b/xmlsitemap.install
index 045eadb..2c899c3 100644
--- a/xmlsitemap.install
+++ b/xmlsitemap.install
@@ -246,7 +246,7 @@ function xmlsitemap_schema() {
         'default' => 0,
       ),
     ),
-    'primary key' => array('id', 'type'),
+    'primary key' => array('id', 'type', 'language'),
     'indexes' => array(
       'loc' => array('loc'),
       'access_status_loc' => array('access', 'status', 'loc'),
@@ -567,6 +567,16 @@ function xmlsitemap_update_7203() {
   _xmlsitemap_sitemap_rehash_all();
 }
 
+/**
+ * Add new primary key including the language.
+ */
+function xmlsitemap_update_7204() {
+  $table = 'xmlsitemap';
+  db_drop_primary_key($table);
+  db_drop_unique_key($table, 'language');
+  db_add_primary_key($table, array('id', 'type', 'language'));
+}
+
 /**
  * Rehash all.
  */
diff --git a/xmlsitemap.module b/xmlsitemap.module
index 52fee5c..7cede3e 100644
--- a/xmlsitemap.module
+++ b/xmlsitemap.module
@@ -659,7 +659,16 @@ function xmlsitemap_link_save(array $link, array $context = array()) {
     $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();
+  $link_keys = array('type', 'id');
+  // If entity translation exists and the link has a language we need to add the
+  // language to the query to fetch the unique / language specific link.
+  if (module_exists('entity_translation') && entity_get_info($link['type']) && entity_translation_enabled($link['type']) && !empty($link['language'])) {
+    $existing = db_query_range("SELECT loc, access, status, lastmod, priority, changefreq, changecount, language FROM {xmlsitemap} WHERE type = :type AND id = :id AND language = :language", 0, 1, array(':type' => $link['type'], ':id' => $link['id'], ':language' => $link['language']))->fetchAssoc();
+    $link_keys[] = 'language';
+  }
+  else {
+    $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 (!variable_get('xmlsitemap_regenerate_needed', FALSE)) {
@@ -668,7 +677,7 @@ function xmlsitemap_link_save(array $link, array $context = array()) {
 
   // 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_write_record('xmlsitemap', $link, $link_keys);
     module_invoke_all('xmlsitemap_link_update', $link, $context);
   }
   else {
@@ -723,12 +732,18 @@ function xmlsitemap_link_update_multiple($updates = array(), $conditions = array
  *   A string with the entity type.
  * @param int $entity_id
  *   An integer with the entity ID.
+ * @param $langcode
+ *   An optional language code for the link that should be deleted.
+ *   If omitted, links for that entity will be removed in all languages.
  *
  * @return int
  *   The number of links that were deleted.
  */
-function xmlsitemap_link_delete($entity_type, $entity_id) {
+function xmlsitemap_link_delete($entity_type, $entity_id, $langcode = NULL) {
   $conditions = array('type' => $entity_type, 'id' => $entity_id);
+  if ($langcode) {
+    $conditions['language'] = $langcode;
+  }
   return xmlsitemap_link_delete_multiple($conditions);
 }
 
@@ -1263,6 +1278,14 @@ function xmlsitemap_field_attach_delete_bundle($entity_type, $bundle, $instances
   xmlsitemap_link_bundle_delete($entity_type, $bundle, TRUE);
 }
 
+/**
+ * Implements hook_entity_translation_delete().
+ */
+function xmlsitemap_entity_translation_delete($entity_type, $entity, $langcode) {
+  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+  xmlsitemap_link_delete($entity_type, $id, $langcode);
+}
+
 /**
  * Determine the frequency of updates to a link.
  *
@@ -1734,3 +1757,48 @@ function xmlsitemap_link_enqueue($type, $ids) {
   $queue = DrupalQueue::get('xmlsitemap_link_process');
   $queue->createItem($data);
 }
+
+/**
+ * Determines the languages to process for an entity.
+ *
+ * If entity_translation is available the languages are determined by using the
+ * translation handler, otherwise it falls back to the default language.
+ *
+ * @param string $entity_type
+ *   The type of entity.
+ * @param object $entity
+ *   The entity.
+ * @param string $default_language
+ *   Set the default language to use if no translations / entity languages where
+ *   found.
+ *
+ * @return array
+ *   Array with the language codes of available languages.
+ *   Returns only languages of published translations. If entity isn't handled
+ *   by entity translation the default language is used.
+ */
+function xmlsitemap_get_entity_languages($entity_type, $entity, $default_language = LANGUAGE_NONE) {
+  if (module_exists('entity_translation')) {
+    if (entity_translation_enabled($entity_type, $entity)) {
+      $handler = entity_translation_get_handler($entity_type, $entity);
+      if (!empty($handler)) {
+        $languages = array();
+        $translations = $handler->getTranslations();
+        foreach ($translations->data as $translation_lang => $translation_data) {
+          $languages[$translation_lang] = (int) $translation_data['status'];
+        }
+        // Detect allowing fallback for languages for entities not yet translated.
+        $fallback = (int) (variable_get('locale_field_language_fallback', TRUE) && variable_get('xmlsitemap_language_fallback', TRUE));
+        // Retrieve all active languages in the site.
+        $available_languages = locale_language_list('language');
+        // Add languages for entities not yet translated.
+        $languages += array_fill_keys(array_diff_key($available_languages, $languages), $fallback);
+        if (variable_get('xmlsitemap_language_remove_neutral', FALSE) && !empty($languages[LANGUAGE_NONE])) {
+          $languages[LANGUAGE_NONE] = 0;
+        }
+        return $languages;
+      }
+    }
+  }
+  return array($default_language => 1);
+}
diff --git a/xmlsitemap.module.orig b/xmlsitemap.module.orig
new file mode 100644
index 0000000..52fee5c
--- /dev/null
+++ b/xmlsitemap.module.orig
@@ -0,0 +1,1736 @@
+<?php
+
+/**
+ * @file
+ * @defgroup xmlsitemap XML sitemap
+ */
+
+/**
+ * @file
+ * Main file for the xmlsitemap module.
+ */
+
+/**
+ * The maximum number of links in one sitemap chunk file.
+ */
+define('XMLSITEMAP_MAX_SITEMAP_LINKS', 50000);
+
+/**
+ * The maximum filesize of a sitemap chunk file.
+ */
+define('XMLSITEMAP_MAX_SITEMAP_FILESIZE', 10485760);
+
+// 60 * 60 * 24 * 7 * 52.
+define('XMLSITEMAP_FREQUENCY_YEARLY', 31449600);
+// 60 * 60 * 24 * 7 * 4.
+define('XMLSITEMAP_FREQUENCY_MONTHLY', 2419200);
+// 60 * 60 * 24 * 7.
+define('XMLSITEMAP_FREQUENCY_WEEKLY', 604800);
+// 60 * 60 * 24.
+define('XMLSITEMAP_FREQUENCY_DAILY', 86400);
+// 60 * 60.
+define('XMLSITEMAP_FREQUENCY_HOURLY', 3600);
+define('XMLSITEMAP_FREQUENCY_ALWAYS', 60);
+
+/**
+ * Short lastmod timestamp format.
+ */
+define('XMLSITEMAP_LASTMOD_SHORT', 'Y-m-d');
+
+/**
+ * Medium lastmod timestamp format.
+ */
+define('XMLSITEMAP_LASTMOD_MEDIUM', 'Y-m-d\TH:i\Z');
+
+/**
+ * Long lastmod timestamp format.
+ */
+define('XMLSITEMAP_LASTMOD_LONG', 'c');
+
+/**
+ * The default inclusion status for link types in the sitemaps.
+ */
+define('XMLSITEMAP_STATUS_DEFAULT', 0);
+
+/**
+ * The default priority for link types in the sitemaps.
+ */
+define('XMLSITEMAP_PRIORITY_DEFAULT', 0.5);
+
+/**
+ * Implements hook_hook_info().
+ */
+function xmlsitemap_hook_info() {
+  $hooks = array(
+    'xmlsitemap_link_info',
+    'xmlsitemap_link_info_alter',
+    'xmlsitemap_link_alter',
+    'xmlsitemap_index_links',
+    'xmlsitemap_context_info',
+    'xmlsitemap_context_info_alter',
+    'xmlsitemap_context_url_options',
+    'xmlsitemap_context',
+    'xmlsitemap_element_alter',
+    'xmlsitemap_root_attributes_alter',
+    'xmlsitemap_sitemap_insert',
+    'xmlsitemap_sitemap_update',
+    'xmlsitemap_sitemap_operations',
+    'xmlsitemap_sitemap_delete',
+    'xmlsitemap_sitemap_link_url_options_alter',
+    'query_xmlsitemap_generate_alter',
+    'query_xmlsitemap_link_bundle_access_alter',
+    'form_xmlsitemap_sitemap_edit_form_alter',
+    'xmlsitemap_rebuild_clear',
+  );
+
+  $hooks = array_combine($hooks, $hooks);
+  foreach ($hooks as $hook => $info) {
+    $hooks[$hook] = array('group' => 'xmlsitemap');
+  }
+
+  return $hooks;
+}
+
+/**
+ * Implements hook_help().
+ */
+function xmlsitemap_help($path, $arg) {
+  $output = '';
+
+  switch ($path) {
+    case 'admin/help/xmlsitemap':
+    case 'admin/config/search/xmlsitemap/settings/%/%/%':
+    case 'admin/config/search/xmlsitemap/edit/%':
+    case 'admin/config/search/xmlsitemap/delete/%':
+      return;
+
+    case 'admin/help#xmlsitemap':
+      break;
+
+    case 'admin/config/search/xmlsitemap':
+      break;
+
+    case 'admin/config/search/xmlsitemap/rebuild':
+      $output .= '<p>' . t("This action rebuilds your site's XML sitemap and regenerates the cached files, and may be a lengthy process. If you just installed XML sitemap, this can be helpful to import all your site's content into the sitemap. Otherwise, this should only be used in emergencies.") . '</p>';
+  }
+
+  if (arg(0) == 'admin' && strpos($path, 'xmlsitemap') !== FALSE && user_access('administer xmlsitemap')) {
+    module_load_include('inc', 'xmlsitemap');
+    if ($arg[1] == 'config') {
+      // Alert the user to any potential problems detected by hook_requirements.
+      xmlsitemap_check_status();
+    }
+    $output .= _xmlsitemap_get_blurb();
+  }
+
+  return $output;
+}
+
+/**
+ * Implements hook_perm().
+ */
+function xmlsitemap_permission() {
+  $permissions['administer xmlsitemap'] = array(
+    'title' => t('Administer XML sitemap settings'),
+  );
+  $permissions['use xmlsitemap'] = array(
+    'title' => t('Use XML sitemap'),
+    'description' => t('Users can change individually the default XML Sitemap settings.'),
+  );
+  return $permissions;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function xmlsitemap_menu() {
+  $items['admin/config/search/xmlsitemap'] = array(
+    'title' => 'XML sitemap',
+    'description' => "Configure your site's XML sitemaps to help search engines find and index pages on your site.",
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_sitemap_list_form'),
+    'access arguments' => array('administer xmlsitemap'),
+    'file' => 'xmlsitemap.admin.inc',
+  );
+  $items['admin/config/search/xmlsitemap/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/config/search/xmlsitemap/add'] = array(
+    'title' => 'Add new XML sitemap',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_sitemap_edit_form'),
+    'access arguments' => array('administer xmlsitemap'),
+    'type' => MENU_LOCAL_ACTION,
+    'file' => 'xmlsitemap.admin.inc',
+    'modal' => TRUE,
+    'options' => array('modal' => TRUE),
+  );
+  $items['admin/config/search/xmlsitemap/edit/%xmlsitemap_sitemap'] = array(
+    'title' => 'Edit XML sitemap',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_sitemap_edit_form', 5),
+    'access arguments' => array('administer xmlsitemap'),
+    'file' => 'xmlsitemap.admin.inc',
+    'modal' => TRUE,
+  );
+  $items['admin/config/search/xmlsitemap/delete/%xmlsitemap_sitemap'] = array(
+    'title' => 'Delete XML sitemap',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_sitemap_delete_form', 5),
+    'access arguments' => array('administer xmlsitemap'),
+    'file' => 'xmlsitemap.admin.inc',
+    'modal' => TRUE,
+  );
+
+  $items['admin/config/search/xmlsitemap/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_settings_form'),
+    'access arguments' => array('administer xmlsitemap'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'xmlsitemap.admin.inc',
+    'weight' => 10,
+  );
+  $items['admin/config/search/xmlsitemap/settings/%xmlsitemap_link_bundle/%'] = array(
+    'load arguments' => array(6),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_link_bundle_settings_form', 5),
+    'access callback' => 'xmlsitemap_link_bundle_access',
+    'access arguments' => array(5),
+    'file' => 'xmlsitemap.admin.inc',
+    'modal' => TRUE,
+  );
+
+  $items['admin/config/search/xmlsitemap/rebuild'] = array(
+    'title' => 'Rebuild links',
+    'description' => 'Rebuild the site map.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('xmlsitemap_rebuild_form'),
+    'access callback' => '_xmlsitemap_rebuild_form_access',
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'xmlsitemap.admin.inc',
+    'weight' => 20,
+  );
+
+  $items['sitemap.xml'] = array(
+    'page callback' => 'xmlsitemap_output_chunk',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'xmlsitemap.pages.inc',
+  );
+  $items['sitemap.xsl'] = array(
+    'page callback' => 'xmlsitemap_output_xsl',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'xmlsitemap.pages.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Menu access callback; determines if the user can use the rebuild links page.
+ */
+function _xmlsitemap_rebuild_form_access() {
+  module_load_include('generate.inc', 'xmlsitemap');
+  $rebuild_types = xmlsitemap_get_rebuildable_link_types();
+  return !empty($rebuild_types) && user_access('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 (!variable_get('xmlsitemap_regenerate_needed', FALSE)) {
+    return;
+  }
+  // If cron sitemap file regeneration is disabled, stop.
+  if (variable_get('xmlsitemap_disable_cron_regeneration', 0)) {
+    return;
+  }
+
+  // If the minimum sitemap lifetime hasn't been passed, skip.
+  $lifetime = REQUEST_TIME - variable_get('xmlsitemap_generated_last', 0);
+  if ($lifetime < variable_get('xmlsitemap_minimum_lifetime', 0)) {
+    return;
+  }
+
+  // Regenerate the sitemap XML files.
+  module_load_include('generate.inc', 'xmlsitemap');
+  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() {
+  if ($sitemap = xmlsitemap_sitemap_load_by_context()) {
+    $robotstxt[] = 'Sitemap: ' . url($sitemap->uri['path'], $sitemap->uri['options']);
+    return $robotstxt;
+  }
+}
+
+/**
+ * Internal default variables for xmlsitemap_var().
+ */
+function xmlsitemap_variables() {
+  global $base_url;
+  return array(
+    'xmlsitemap_rebuild_needed' => FALSE,
+    'xmlsitemap_regenerate_needed' => FALSE,
+    'xmlsitemap_minimum_lifetime' => 0,
+    'xmlsitemap_generated_last' => 0,
+    'xmlsitemap_xsl' => 1,
+    'xmlsitemap_prefetch_aliases' => 1,
+    'xmlsitemap_chunk_size' => 'auto',
+    'xmlsitemap_batch_limit' => 100,
+    'xmlsitemap_path' => 'xmlsitemap',
+    'xmlsitemap_base_url' => $base_url,
+    'xmlsitemap_developer_mode' => 0,
+    'xmlsitemap_frontpage_priority' => 1.0,
+    'xmlsitemap_frontpage_changefreq' => XMLSITEMAP_FREQUENCY_DAILY,
+    'xmlsitemap_lastmod_format' => XMLSITEMAP_LASTMOD_MEDIUM,
+    'xmlsitemap_gz' => FALSE,
+    'xmlsitemap_disable_cron_regeneration' => 0,
+    'xmlsitemap_output_elements' => array('lastmod', 'changefreq', 'priority'),
+    // Removed variables are set to NULL so they can still be deleted.
+    'xmlsitemap_regenerate_last' => NULL,
+    'xmlsitemap_custom_links' => NULL,
+    'xmlsitemap_priority_default' => NULL,
+    'xmlsitemap_languages' => NULL,
+    'xmlsitemap_max_chunks' => NULL,
+    'xmlsitemap_max_filesize' => NULL,
+  );
+}
+
+/**
+ * Internal implementation of variable_get().
+ */
+function xmlsitemap_var($name, $default = NULL) {
+  $defaults = &drupal_static(__FUNCTION__);
+  if (!isset($defaults)) {
+    $defaults = xmlsitemap_variables();
+  }
+
+  $name = 'xmlsitemap_' . $name;
+
+  // @todo Remove when stable.
+  if (!isset($defaults[$name])) {
+    trigger_error(strtr('Default variable for %variable not found.', array('%variable' => drupal_placeholder($name))));
+  }
+
+  return variable_get($name, isset($default) || !isset($defaults[$name]) ? $default : $defaults[$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 array $smid
+ *   An XML sitemap ID.
+ *
+ * @return object
+ *   The XML sitemap object.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function xmlsitemap_sitemap_load($smid) {
+  // @codingStandardsIgnoreEnd
+  $sitemap = xmlsitemap_sitemap_load_multiple(array($smid));
+  return $sitemap ? reset($sitemap) : FALSE;
+}
+
+/**
+ * Load multiple XML sitemaps from the database.
+ *
+ * @param array $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 array
+ *   An array of XML sitemap objects.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function xmlsitemap_sitemap_load_multiple($smids = array(), array $conditions = array()) {
+  // @codingStandardsIgnoreEnd
+  if ($smids !== FALSE) {
+    $conditions['smid'] = $smids;
+  }
+
+  $query = db_select('xmlsitemap_sitemap');
+  $query->fields('xmlsitemap_sitemap');
+  foreach ($conditions as $field => $value) {
+    $query->condition($field, $value);
+  }
+
+  $sitemaps = $query->execute()->fetchAllAssoc('smid');
+  foreach ($sitemaps as $smid => $sitemap) {
+    $sitemaps[$smid]->context = unserialize($sitemap->context);
+    $sitemaps[$smid]->uri = xmlsitemap_sitemap_uri($sitemaps[$smid]);
+  }
+
+  return $sitemaps;
+}
+
+/**
+ * Load an XML sitemap array from the database based on its context.
+ *
+ * @param array $context
+ *   An optional XML sitemap context array to use to find the correct XML
+ *   sitemap. If not provided, the current site's context will be used.
+ *
+ * @see xmlsitemap_get_current_context()
+ */
+function xmlsitemap_sitemap_load_by_context(array $context = NULL) {
+  if (!isset($context)) {
+    $context = xmlsitemap_get_current_context();
+  }
+  $hash = xmlsitemap_sitemap_get_context_hash($context);
+  $smid = db_query_range("SELECT smid FROM {xmlsitemap_sitemap} WHERE smid = :hash", 0, 1, array(':hash' => $hash))->fetchField();
+  return xmlsitemap_sitemap_load($smid);
+}
+
+/**
+ * Save changes to an XML sitemap or add a new XML sitemap.
+ *
+ * @param object $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(stdClass $sitemap) {
+  if (!isset($sitemap->context)) {
+    $sitemap->context = array();
+  }
+
+  // Make sure context is sorted before saving the hash.
+  $sitemap->is_new = empty($sitemap->smid);
+  $sitemap->old_smid = $sitemap->is_new ? NULL : $sitemap->smid;
+  $sitemap->smid = xmlsitemap_sitemap_get_context_hash($sitemap->context);
+
+  // If the context was changed, we need to perform additional actions.
+  if (!$sitemap->is_new && $sitemap->smid != $sitemap->old_smid) {
+    // 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);
+
+    // Change the smid field so drupal_write_record() does not fail.
+    db_update('xmlsitemap_sitemap')
+      ->fields(array('smid' => $sitemap->smid))
+      ->condition('smid', $sitemap->old_smid)
+      ->execute();
+
+    // Mark the sitemaps as needing regeneration.
+    variable_set('xmlsitemap_regenerate_needed', TRUE);
+  }
+
+  if ($sitemap->is_new) {
+    drupal_write_record('xmlsitemap_sitemap', $sitemap);
+    module_invoke_all('xmlsitemap_sitemap_insert', $sitemap);
+  }
+  else {
+    drupal_write_record('xmlsitemap_sitemap', $sitemap, array('smid'));
+    module_invoke_all('xmlsitemap_sitemap_update', $sitemap);
+  }
+
+  return $sitemap;
+}
+
+/**
+ * Delete an XML sitemap.
+ *
+ * @param array $smid
+ *   An XML sitemap ID.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function xmlsitemap_sitemap_delete($smid) {
+  // @codingStandardsIgnoreEnd
+  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);
+    db_delete('xmlsitemap_sitemap')
+      ->condition('smid', $smids)
+      ->execute();
+
+    foreach ($sitemaps as $sitemap) {
+      xmlsitemap_clear_directory($sitemap, TRUE);
+      module_invoke_all('xmlsitemap_sitemap_delete', $sitemap);
+    }
+  }
+}
+
+/**
+ * Return the expected file path for a specific sitemap chunk.
+ *
+ * @param object $sitemap
+ *   An XML sitemap array.
+ * @param string $chunk
+ *   An optional specific chunk in the sitemap. Defaults to the index page.
+ */
+function xmlsitemap_sitemap_get_file(stdClass $sitemap, $chunk = 'index') {
+  return xmlsitemap_get_directory($sitemap) . "/{$chunk}.xml";
+}
+
+/**
+ * Find the maximum file size of all a sitemap's XML files.
+ *
+ * @param object $sitemap
+ *   The XML sitemap array.
+ */
+function xmlsitemap_sitemap_get_max_filesize(stdClass $sitemap) {
+  $dir = xmlsitemap_get_directory($sitemap);
+  $sitemap->max_filesize = 0;
+  foreach (file_scan_directory($dir, '/\.xml$/') as $file) {
+    $sitemap->max_filesize = max($sitemap->max_filesize, filesize($file->uri));
+  }
+  return $sitemap->max_filesize;
+}
+
+/**
+ * Get context.
+ */
+function xmlsitemap_sitemap_get_context_hash(array &$context) {
+  asort($context);
+  return drupal_hash_base64(serialize($context));
+}
+
+/**
+ * Returns the uri elements of an XML sitemap.
+ *
+ * @param object $sitemap
+ *   An unserialized data array for an XML sitemap.
+ *
+ * @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(stdClass $sitemap) {
+  global $base_url;
+  $uri['path'] = 'sitemap.xml';
+  $uri['options'] = module_invoke_all('xmlsitemap_context_url_options', $sitemap->context);
+  drupal_alter('xmlsitemap_context_url_options', $uri['options'], $sitemap->context);
+  $uri['options'] += array(
+    'absolute' => TRUE,
+    'base_url' => variable_get('xmlsitemap_base_url', $base_url),
+  );
+  return $uri;
+}
+
+/**
+ * Load a specific sitemap link from the database.
+ *
+ * @param string $entity_type
+ *   A string with the entity type.
+ * @param int $entity_id
+ *   An integer with the entity ID.
+ *
+ * @return array
+ *   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 array $conditions
+ *   An array of conditions on the {xmlsitemap} table in the form
+ *   'field' => $value.
+ *
+ * @return array
+ *   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;
+}
+
+/**
+ * Presave a sitemap link.
+ *
+ * @param array $link
+ *   An array with a sitemap link.
+ * @param array $context
+ *   An optional context array containing data related to the link.
+ */
+function xmlsitemap_link_presave(array $link, array $context = array()) {
+  // Force link access to 0 in presave so that the link is saved with revoked
+  // access until the node permissions are checked in the cron.
+  $link['access'] = 0;
+
+  // Allow other modules to alter the sitemap link presave.
+  drupal_alter('xmlsitemap_link_presave', $link, $context);
+
+  // Save or update a sitemap link which will be overwritten in Drupal cron job.
+  xmlsitemap_link_save($link, $context);
+}
+
+/**
+ * Saves or updates a sitemap link.
+ *
+ * @param array $link
+ *   An array with a sitemap link.
+ * @param array $context
+ *   An optional context array containing data related to the link.
+ *
+ * @return array
+ *   The saved sitemap link.
+ */
+function xmlsitemap_link_save(array $link, array $context = array()) {
+  $link += array(
+    'access' => 1,
+    'status' => 1,
+    'status_override' => 0,
+    'lastmod' => 0,
+    'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
+    'priority_override' => 0,
+    'changefreq' => 0,
+    'changecount' => 0,
+    'language' => LANGUAGE_NONE,
+  );
+
+  // Allow other modules to alter the link before saving.
+  drupal_alter('xmlsitemap_link', $link, $context);
+
+  // Temporary validation checks.
+  // @todo Remove in final?
+  if ($link['priority'] < 0 || $link['priority'] > 1) {
+    trigger_error(t('Invalid sitemap link priority %priority.<br />@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 <a href="@516928">@516928</a>.<br />@link', array(
+      '@516928' => 'https://www.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 (!variable_get('xmlsitemap_regenerate_needed', FALSE)) {
+    _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'));
+    module_invoke_all('xmlsitemap_link_update', $link, $context);
+  }
+  else {
+    drupal_write_record('xmlsitemap', $link);
+    module_invoke_all('xmlsitemap_link_insert', $link, $context);
+  }
+
+  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 array $updates
+ *   An array of values to update fields to, keyed by field name.
+ * @param array $conditions
+ *   An array of values to match keyed by field.
+ *
+ * @return int
+ *   The number of links that were updated.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function xmlsitemap_link_update_multiple($updates = array(), $conditions = array(), $check_flag = TRUE) {
+  // @codingStandardsIgnoreEnd
+  // If we are going to modify a visible sitemap link, we will need to set
+  // the regenerate needed flag.
+  if ($check_flag && !variable_get('xmlsitemap_regenerate_needed', FALSE)) {
+    _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 string $entity_type
+ *   A string with the entity type.
+ * @param int $entity_id
+ *   An integer with the entity ID.
+ *
+ * @return int
+ *   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 array $conditions
+ *   An array of conditions on the {xmlsitemap} table in the form
+ *   'field' => $value.
+ *
+ * @return int
+ *   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 https://www.drupal.org/node/151452 is fixed.
+  if (!db_table_exists('xmlsitemap')) {
+    return FALSE;
+  }
+
+  if (!variable_get('xmlsitemap_regenerate_needed', TRUE)) {
+    _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 array $conditions
+ *   An array of values to match keyed by field.
+ * @param string $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 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) {
+    variable_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.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function _xmlsitemap_check_changed_link(array $link, $original_link = NULL, $flag = FALSE) {
+  // @codingStandardsIgnoreEnd
+  $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) {
+    variable_set('xmlsitemap_regenerate_needed', TRUE);
+  }
+
+  return $changed;
+}
+
+/**
+ * @} End of "defgroup xmlsitemap_api"
+ */
+function xmlsitemap_get_directory(stdClass $sitemap = NULL) {
+  $directory = &drupal_static(__FUNCTION__);
+
+  if (!isset($directory)) {
+    $directory = variable_get('xmlsitemap_path', 'xmlsitemap');
+  }
+
+  if (empty($directory)) {
+    return FALSE;
+  }
+  elseif (!empty($sitemap->smid)) {
+    return file_build_uri($directory . '/' . $sitemap->smid);
+  }
+  else {
+    return file_build_uri($directory);
+  }
+}
+
+/**
+ * Check that the sitemap files directory exists and is writable.
+ */
+function xmlsitemap_check_directory(stdClass $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;
+}
+
+/**
+ * Check all directories.
+ */
+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;
+}
+
+/**
+ * Clear Directory.
+ */
+function xmlsitemap_clear_directory(stdClass $sitemap = NULL, $delete = FALSE) {
+  if ($directory = xmlsitemap_get_directory($sitemap)) {
+    return _xmlsitemap_delete_recursive($directory, $delete);
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * 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 string $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 7'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.
+ */
+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, TRUE);
+    }
+    $dir->close();
+    return $delete_root ? drupal_rmdir($path) : TRUE;
+  }
+  return file_unmanaged_delete($path);
+}
+
+/**
+ * Returns information about supported sitemap link types.
+ *
+ * @param string $type
+ *   (optional) The link type to return information for. If omitted,
+ *   information for all link types is returned.
+ * @param bool $reset
+ *   (optional) Boolean whether to reset the static cache and do nothing. Only
+ *   used for tests.
+ *
+ * @see hook_xmlsitemap_link_info()
+ * @see hook_xmlsitemap_link_info_alter()
+ */
+function xmlsitemap_get_link_info($type = NULL, $reset = FALSE) {
+  global $language;
+  $link_info = &drupal_static(__FUNCTION__);
+
+  if ($reset) {
+    $link_info = NULL;
+    cache_clear_all('xmlsitemap:link_info:', 'cache', TRUE);
+  }
+
+  if (!isset($link_info)) {
+    $cid = 'xmlsitemap:link_info:' . $language->language;
+    if ($cache = cache_get($cid)) {
+      $link_info = $cache->data;
+    }
+    else {
+      entity_info_cache_clear();
+      $link_info = entity_get_info();
+      foreach ($link_info as $key => $info) {
+        if (empty($info['uri callback']) || !isset($info['xmlsitemap'])) {
+          // Remove any non URL-able or XML sitemap un-supported entites.
+          unset($link_info[$key]);
+        }
+        foreach ($info['bundles'] as $bundle_key => $bundle) {
+          if (!isset($bundle['xmlsitemap'])) {
+            // Remove any un-supported entity bundles.
+            // unset($link_info[$key]['bundles'][$bundle_key]);.
+          }
+        }
+      }
+      $link_info = array_merge($link_info, module_invoke_all('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_alter('xmlsitemap_link_info', $link_info);
+      ksort($link_info);
+      // Cache by language since this info contains translated strings.
+      cache_set($cid, $link_info);
+    }
+  }
+
+  if (isset($type)) {
+    return isset($link_info[$type]) ? $link_info[$type] : NULL;
+  }
+
+  return $link_info;
+}
+
+/**
+ * Enabled Bundles.
+ */
+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'])) {
+      // If (!empty($bundle_info['xmlsitemap']['status'])) {.
+      $bundles[] = $bundle;
+    }
+  }
+  return $bundles;
+}
+
+/**
+ * Indexed Status.
+ */
+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();
+
+  $total = new EntityFieldQuery();
+  $total->entityCondition('entity_type', $entity_type);
+  $total->entityCondition('bundle', $bundle);
+  $total->entityCondition('entity_id', 0, '>');
+  // $total->addTag('xmlsitemap_link_bundle_access');.
+  $total->addTag('xmlsitemap_link_indexed_status');
+  $total->addMetaData('entity', $entity_type);
+  $total->addMetaData('bundle', $bundle);
+  $total->addMetaData('entity_info', $info);
+  $total->count();
+  $status['total'] = $total->execute();
+
+  return $status;
+}
+
+/**
+ * Implements hook_entity_query_alter().
+ *
+ * @todo Remove when https://www.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']);
+  }
+}
+
+/**
+ * Budle Settings.
+ */
+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,
+      ));
+    }
+  }
+
+  variable_set("xmlsitemap_settings_{$entity}_{$bundle}", $settings);
+  cache_clear_all('xmlsitemap:link_info:', 'cache', TRUE);
+  // xmlsitemap_get_link_info(NULL, TRUE);.
+}
+
+/**
+ * Bundle Rename.
+ */
+function xmlsitemap_link_bundle_rename($entity, $bundle_old, $bundle_new) {
+  if ($bundle_old != $bundle_new) {
+    $settings = xmlsitemap_link_bundle_load($entity, $bundle_old);
+    variable_del("xmlsitemap_settings_{$entity}_{$bundle_old}");
+    xmlsitemap_link_bundle_settings_save($entity, $bundle_new, $settings, FALSE);
+    xmlsitemap_link_update_multiple(array('subtype' => $bundle_new), array('type' => $entity, 'subtype' => $bundle_old));
+  }
+}
+
+/**
+ * Rename a link type.
+ */
+function xmlsitemap_link_type_rename($entity_old, $entity_new, $bundles = NULL) {
+  $variables = db_query("SELECT name FROM {variable} WHERE name LIKE :pattern", array(':pattern' => db_like('xmlsitemap_settings_' . $entity_old . '_') . '%'))->fetchCol();
+  foreach ($variables as $variable) {
+    $value = variable_get($variable);
+    variable_del($variable);
+    if (isset($value)) {
+      $variable_new = str_replace('xmlsitemap_settings_' . $entity_old, 'xmlsitemap_settings_' . $entity_new, $variable);
+      variable_set($variable_new, $value);
+    }
+  }
+
+  xmlsitemap_link_update_multiple(array('type' => $entity_new), array('type' => $entity_old), FALSE);
+  xmlsitemap_get_link_info(NULL, TRUE);
+}
+
+/**
+ * Bundle Load.
+ */
+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];
+    }
+  }
+  $info += variable_get("xmlsitemap_settings_{$entity}_{$bundle}", array());
+  $info += array(
+    'status' => XMLSITEMAP_STATUS_DEFAULT,
+    'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
+  );
+  return $info;
+}
+
+/**
+ * Bundle Delete.
+ */
+function xmlsitemap_link_bundle_delete($entity, $bundle, $delete_links = TRUE) {
+  variable_del("xmlsitemap_settings_{$entity}_{$bundle}");
+  if ($delete_links) {
+    xmlsitemap_link_delete_multiple(array('type' => $entity, 'subtype' => $bundle));
+  }
+  cache_clear_all('xmlsitemap:link_info:', 'cache', TRUE);
+  // xmlsitemap_get_link_info(NULL, TRUE);.
+}
+
+/**
+ * Bundle Access.
+ */
+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 Bundle.
+ */
+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_field_attach_rename_bundle().
+ */
+function xmlsitemap_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
+  xmlsitemap_link_bundle_rename($entity_type, $bundle_old, $bundle_new);
+}
+
+/**
+ * Implements hook_field_attach_delete_bundle().
+ */
+function xmlsitemap_field_attach_delete_bundle($entity_type, $bundle, $instances) {
+  xmlsitemap_link_bundle_delete($entity_type, $bundle, TRUE);
+}
+
+/**
+ * Determine the frequency of updates to a link.
+ *
+ * @param string $interval
+ *   An interval value in seconds.
+ *
+ * @return string
+ *   A string representing the update frequency according to the sitemaps.org
+ *   protocol.
+ */
+function xmlsitemap_get_changefreq($interval) {
+  if ($interval <= 0 || !is_numeric($interval)) {
+    return FALSE;
+  }
+
+  foreach (xmlsitemap_get_changefreq_options() as $value => $frequency) {
+    if ($interval <= $value) {
+      return $frequency;
+    }
+  }
+
+  return 'never';
+}
+
+/**
+ * Get the current number of sitemap chunks.
+ */
+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.
+ */
+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 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.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function xmlsitemap_recalculate_changefreq(&$link) {
+  // @codingStandardsIgnoreEnd
+  $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 array $timestamps
+ *   An array of UNIX timestamp integers.
+ *
+ * @return int
+ *   An integer of the average interval.
+ *
+ * @codingStandardsIgnoreStart
+ */
+function xmlsitemap_calculate_changefreq($timestamps) {
+  // @codingStandardsIgnoreEnd
+  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) {
+    $stored_value = variable_get($variable, 'not_a_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) {
+      variable_set('xmlsitemap_regenerate_needed', TRUE);
+      drupal_set_message(t('XML sitemap settings have been modified and the files should be regenerated. You can <a href="@run-cron">run cron manually</a> to regenerate the cached files.', array('@run-cron' => url('admin/reports/status/run-cron', array('query' => drupal_get_destination())))), 'warning', FALSE);
+      return;
+    }
+  }
+}
+
+/**
+ * Set the current user stored in $GLOBALS['user'].
+ *
+ * @todo Remove when https://www.drupal.org/node/287292 is fixed.
+ */
+function xmlsitemap_switch_user($new_user = NULL) {
+  global $user;
+  $user_original = &drupal_static(__FUNCTION__);
+
+  if (!isset($new_user)) {
+    if (isset($user_original)) {
+      // Restore the original user.
+      $user = $user_original;
+      $user_original = NULL;
+      drupal_save_session(TRUE);
+    }
+    else {
+      return FALSE;
+    }
+  }
+  elseif (is_numeric($new_user) && $user->uid != $new_user) {
+    // Get the full user object.
+    if (!$new_user) {
+      $new_user = drupal_anonymous_user();
+    }
+    elseif (!$new_user = user_load($new_user)) {
+      return FALSE;
+    }
+
+    // Backup the original user object.
+    if (!isset($user_original)) {
+      $user_original = $user;
+      drupal_save_session(FALSE);
+    }
+
+    $user = $new_user;
+  }
+  elseif (is_object($new_user) && $user->uid != $new_user->uid) {
+    // Backup the original user object.
+    if (!isset($user_original)) {
+      $user_original = $user;
+      drupal_save_session(FALSE);
+    }
+
+    $user = $new_user;
+  }
+  else {
+    return FALSE;
+  }
+
+  return $user;
+}
+
+/**
+ * Restore the user that was originally loaded.
+ *
+ * @codingStandardsIgnoreLine
+ * @return object.
+ *   Current user.
+ */
+function xmlsitemap_restore_user() {
+  return xmlsitemap_switch_user();
+}
+
+/**
+ * Form Link.
+ */
+function xmlsitemap_process_form_link_options($form, &$form_state) {
+  $link = &$form_state['values']['xmlsitemap'];
+  $fields = array('status' => XMLSITEMAP_STATUS_DEFAULT, 'priority' => XMLSITEMAP_PRIORITY_DEFAULT);
+
+  if (empty($link)) {
+    return;
+  }
+  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;
+    }
+  }
+}
+
+/**
+ * Link bundle settings form submit.
+ */
+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']);
+}
+
+/**
+ * Get Freq.
+ *
+ * @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.
+ *
+ * @todo Remove when https://www.drupal.org/node/660736 is fixed in Drupal core.
+ *
+ * @param string $language
+ *   A language code. If not provided the default language will be returned.
+ *
+ * @return object
+ *   A language object.
+ */
+function xmlsitemap_language_load($language = LANGUAGE_NONE) {
+  $languages = &drupal_static(__FUNCTION__);
+
+  if (!isset($languages)) {
+    $languages = language_list();
+    $languages[LANGUAGE_NONE] = NULL;
+  }
+
+  return isset($languages[$language]) ? $languages[$language] : NULL;
+}
+
+/**
+ * @defgroup xmlsitemap_context_api XML sitemap API for sitemap contexts.
+ * @{
+ */
+function xmlsitemap_get_context_info($context = NULL, $reset = FALSE) {
+  global $language;
+  $info = &drupal_static(__FUNCTION__);
+
+  if ($reset) {
+    $info = NULL;
+  }
+  elseif ($cached = cache_get('xmlsitemap:context_info:' . $language->language)) {
+    $info = $cached->data;
+  }
+
+  if (!isset($info)) {
+    $info = module_invoke_all('xmlsitemap_context_info');
+    drupal_alter('xmlsitemap_context_info', $info);
+    ksort($info);
+    // Cache by language since this info contains translated strings.
+    cache_set('xmlsitemap:context_info:' . $language->language, $info);
+  }
+
+  if (isset($context)) {
+    return isset($info[$context]) ? $info[$context] : NULL;
+  }
+
+  return $info;
+}
+
+/**
+ * Get the sitemap context of the current request.
+ */
+function xmlsitemap_get_current_context() {
+  $context = &drupal_static(__FUNCTION__);
+
+  if (!isset($context)) {
+    $context = module_invoke_all('xmlsitemap_context');
+    drupal_alter('xmlsitemap_context', $context);
+    asort($context);
+  }
+
+  return $context;
+}
+
+/**
+ * Context summary.
+ */
+function _xmlsitemap_sitemap_context_summary(stdClass $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();
+  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 https://www.drupal.org/node/638712 is fixed.
+  $batch =& batch_get();
+  $batch['progressive'] = FALSE;
+
+  // Run the batch process.
+  batch_process();
+
+  lock_release($batch_callback);
+  return TRUE;
+}
+
+/**
+ * Workaround for missing breadcrumbs on callback and action paths.
+ *
+ * @todo Remove when https://www.drupal.org/node/576290 is fixed.
+ */
+function _xmlsitemap_set_breadcrumb($path = 'admin/config/search/xmlsitemap') {
+  $breadcrumb = array();
+  $path = explode('/', $path);
+  do {
+    $menu_path = implode('/', $path);
+    $menu_item = menu_get_item($menu_path);
+    array_unshift($breadcrumb, l($menu_item['title'], $menu_path));
+  } while (array_pop($path) && !empty($path));
+  array_unshift($breadcrumb, l(t('Home'), NULL));
+  drupal_set_breadcrumb($breadcrumb);
+}
+
+/**
+ * Get operation link.
+ */
+function xmlsitemap_get_operation_link($url, $options = array()) {
+  static $destination;
+
+  if (!isset($destination)) {
+    $destination = drupal_get_destination();
+  }
+
+  $link = array('href' => $url) + $options;
+
+  // Fetch the item's menu router link info and title.
+  if (!isset($link['title'])) {
+    $item = menu_get_item($url);
+    $link['title'] = $item['title'];
+  }
+
+  $link += array('query' => $destination);
+  drupal_alter('xmlsitemap_operation_link', $link);
+  return $link;
+}
+
+/**
+ * Implements hook_cron_queue_info().
+ */
+function xmlsitemap_cron_queue_info() {
+  $info['xmlsitemap_link_process'] = array(
+    'worker callback' => 'xmlsitemap_link_queue_process',
+    'time' => 60,
+  );
+
+  return $info;
+}
+
+/**
+ * Queue callback for processing sitemap links.
+ */
+function xmlsitemap_link_queue_process($data) {
+  $info = xmlsitemap_get_link_info($data['type']);
+  $ids = isset($data['ids']) ? $data['ids'] : array($data['id']);
+  if (function_exists($info['xmlsitemap']['process callback'])) {
+    $info['xmlsitemap']['process callback']($ids);
+  }
+}
+
+/**
+ * Enqueue sitemap links to be updated via the xmlsitemap_link_process queue.
+ *
+ * @param string $type
+ *   The link type.
+ * @param array|int $ids
+ *   An array of link IDs or a singular link ID.
+ */
+function xmlsitemap_link_enqueue($type, $ids) {
+  $data = array();
+  $data['type'] = $type;
+  $data['ids'] = is_array($ids) ? $ids : array($ids);
+
+  /** @var DrupalReliableQueueInterface $queue */
+  $queue = DrupalQueue::get('xmlsitemap_link_process');
+  $queue->createItem($data);
+}
diff --git a/xmlsitemap_i18n/xmlsitemap_i18n.install b/xmlsitemap_i18n/xmlsitemap_i18n.install
new file mode 100644
index 0000000..5a56452
--- /dev/null
+++ b/xmlsitemap_i18n/xmlsitemap_i18n.install
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the xmlsitemap_i18n module.
+ *
+ * @ingroup xmlsitemap
+ */
+
+/**
+ * Implements hook_install().
+ *
+ * If there are any languages enabled, install a sitemap for each, and delete
+ * the default sitemap (created by xmlsitemap) with no language context.
+ */
+function xmlsitemap_i18n_install() {
+  if (module_exists('locale') && ($languages = array_keys(locale_language_list()))) {
+    // Delete the default sitemap with no language context. It should be
+    // removed so that it doesn't conflict with the to-be-created sitemap for
+    // the default language.
+    $context = array();
+    db_delete('xmlsitemap_sitemap')
+      ->condition('context', serialize($context))
+      ->execute();
+
+    // Add a sitemap for each language.
+    foreach ($languages as $language) {
+      $context = array('language' => $language);
+      db_insert('xmlsitemap_sitemap')
+        ->fields(array(
+          'smid' => xmlsitemap_sitemap_get_context_hash($context),
+          'context' => serialize($context),
+        ))
+        ->execute();
+    }
+  }
+}
+
+/**
+ * Implements hook_uninstall().
+ *
+ * Delete sitemaps related to languages and set 1 default sitemap.
+ */
+function xmlsitemap_i18n_uninstall() {
+  if (module_exists('locale') && ($languages = array_keys(locale_language_list()))) {
+    // Delete sitemap for each language.
+    foreach ($languages as $language) {
+      $context = array('language' => $language);
+      db_delete('xmlsitemap_sitemap')
+        ->condition('context', serialize($context))
+        ->execute();
+    }
+
+    // Add default sitemap.
+    db_insert('xmlsitemap_sitemap')
+      ->fields(array(
+        'smid' => xmlsitemap_sitemap_get_context_hash($context),
+        'context' => serialize($context),
+      ))
+      ->execute();
+  }
+}
diff --git a/xmlsitemap_node/xmlsitemap_node.module b/xmlsitemap_node/xmlsitemap_node.module
index c287f26..83ee607 100644
--- a/xmlsitemap_node/xmlsitemap_node.module
+++ b/xmlsitemap_node/xmlsitemap_node.module
@@ -34,7 +34,19 @@ function xmlsitemap_node_cron() {
       // updates in this case.
       if ($node) {
         $link = xmlsitemap_node_create_link($node);
-        xmlsitemap_link_save($link, array($link['type'] => $node));
+        if (!empty($link['languages'])) {
+          $bundle_info = xmlsitemap_link_bundle_load('node', $node->type);
+          $link_status = (int) $link['status'];
+          foreach ($link['languages'] as $lang => $lang_status) {
+            $lang_link = $link;
+            $lang_link['language'] = $lang;
+            $lang_link['status'] = (int) ($link_status && $lang_status);
+            xmlsitemap_link_save($lang_link, array($link['type'] => $node));
+          }
+        }
+        else {
+          xmlsitemap_link_save($link, array($link['type'] => $node));
+        }
       }
       $queue->deleteItem($item);
     }
@@ -73,8 +85,7 @@ function xmlsitemap_node_xmlsitemap_process_node_links(array $nids) {
     foreach ($nids_chunks as $chunk) {
       $nodes = node_load_multiple($chunk);
       foreach ($nodes as $node) {
-        $link = xmlsitemap_node_create_link($node);
-        xmlsitemap_link_save($link, array($link['type'] => $node));
+        xmlsitemap_node_node_update($node);
       }
       // Flush each entity from the load cache after processing, to avoid
       // exceeding PHP memory limits if $nids is large.
@@ -248,7 +259,10 @@ function xmlsitemap_node_create_link(stdClass $node) {
   $node->xmlsitemap['loc'] = $uri['path'];
   $node->xmlsitemap['lastmod'] = count($timestamps) ? max($timestamps) : 0;
   $node->xmlsitemap['access'] = $node->nid ? xmlsitemap_node_view_access($node, drupal_anonymous_user()) : 1;
-  $node->xmlsitemap['language'] = isset($node->language) ? $node->language : LANGUAGE_NONE;
+  // Fetches the languages for the sitemap link / links.
+  $language = isset($node->language) ? $node->language : LANGUAGE_NONE;
+  $node->xmlsitemap['language'] = $language;
+  $node->xmlsitemap['languages'] = xmlsitemap_get_entity_languages('node', $node, $language);
 
   return $node->xmlsitemap;
 }
diff --git a/xmlsitemap_taxonomy/xmlsitemap_taxonomy.module b/xmlsitemap_taxonomy/xmlsitemap_taxonomy.module
index d97f9c3..1c3054f 100644
--- a/xmlsitemap_taxonomy/xmlsitemap_taxonomy.module
+++ b/xmlsitemap_taxonomy/xmlsitemap_taxonomy.module
@@ -54,8 +54,7 @@ function xmlsitemap_taxonomy_xmlsitemap_index_links($limit) {
 function xmlsitemap_taxonomy_xmlsitemap_process_taxonomy_term_links(array $tids) {
   $terms = taxonomy_term_load_multiple($tids);
   foreach ($terms as $term) {
-    $link = xmlsitemap_taxonomy_create_link($term);
-    xmlsitemap_link_save($link, array($link['type'] => $term));
+    xmlsitemap_taxonomy_term_update($term);
   }
 }
 
@@ -111,8 +110,7 @@ function xmlsitemap_taxonomy_vocabulary_update(stdClass $vocabulary) {
  * Implements hook_taxonomy_term_insert().
  */
 function xmlsitemap_taxonomy_term_insert(stdClass $term) {
-  $link = xmlsitemap_taxonomy_create_link($term);
-  xmlsitemap_link_save($link, array($link['type'] => $term));
+  xmlsitemap_taxonomy_term_update($term);
 }
 
 /**
@@ -120,7 +118,19 @@ function xmlsitemap_taxonomy_term_insert(stdClass $term) {
  */
 function xmlsitemap_taxonomy_term_update(stdClass $term) {
   $link = xmlsitemap_taxonomy_create_link($term);
-  xmlsitemap_link_save($link, array($link['type'] => $term));
+  if (!empty($link['languages'])) {
+    $bundle_info = xmlsitemap_link_bundle_load('taxonomy_term', $term->vocabulary_machine_name);
+    $link_status = (int) $bundle_info['status'];
+    foreach ($link['languages'] as $lang => $lang_status) {
+      $lang_link = $link;
+      $lang_link['language'] = $lang;
+      $lang_link['status'] = (int) ($link_status && $lang_status);
+      xmlsitemap_link_save($lang_link, array($link['type'] => $term));
+    }
+  }
+  else {
+    xmlsitemap_link_save($link, array($link['type'] => $term));
+  }
 }
 
 /**
@@ -181,7 +191,10 @@ function xmlsitemap_taxonomy_create_link(stdClass &$term) {
   // @todo How can/should we check taxonomy term access?
   $term->xmlsitemap['loc'] = $uri['path'];
   $term->xmlsitemap['access'] = 1;
-  $term->xmlsitemap['language'] = isset($term->language) ? $term->language : LANGUAGE_NONE;
+  // Fetches the languages for the sitemap link / links.
+  $language = isset($term->language) ? $term->language : LANGUAGE_NONE;
+  $term->xmlsitemap['language'] = $language;
+  $term->xmlsitemap['languages'] = xmlsitemap_get_entity_languages('taxonomy_term', $term, $language);
 
   return $term->xmlsitemap;
 }
