You are here

xmlsitemap.module in XML sitemap 5

Creates a site map compatible with the sitemaps.org schema.

File

xmlsitemap.module
View source
<?php

/**
 * @file Creates a site map compatible with the sitemaps.org schema.
 */

/**
 * @addtogroup xmlsitemap
 * @{
 */

/**
 * Implementation of hook_help().
 */
function xmlsitemap_help($section) {
  switch ($section) {
    case 'admin/settings/xmlsitemap':
    case 'admin/settings/xmlsitemap/settings':
      return t('Configure the site map. Your site map is at !url.', array(
        '!url' => '<a href="' . xmlsitemap_url('sitemap.xml', drupal_lookup_path('alias', 'sitemap.xml') ? drupal_lookup_path('alias', 'sitemap.xml') : NULL, NULL, NULL, TRUE) . '">' . xmlsitemap_url('sitemap.xml', drupal_lookup_path('alias', 'sitemap.xml') ? drupal_lookup_path('alias', 'sitemap.xml') : NULL, NULL, NULL, TRUE) . '</a>',
      ));
    case 'admin/settings/xmlsitemap/engines':
      return t('Configure behavior for search engines.');
    case 'admin/settings/xmlsitemap/additional':
      return t('Set up additional links for your site map.');
  }
}

/**
 * Implementation of hook_menu().
 */
function xmlsitemap_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/xmlsitemap',
      'title' => t('XML Sitemap'),
      'description' => t('Configure site map.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'xmlsitemap_settings_sitemap',
      ),
      'access' => user_access('administer site configuration'),
    );
    $items[] = array(
      'path' => 'admin/settings/xmlsitemap/settings',
      'title' => t('Site map'),
      'description' => t('Configure site map.'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -1,
    );
    $items[] = array(
      'path' => 'admin/settings/xmlsitemap/engines',
      'title' => t('Search engines'),
      'description' => t('Configure search engines.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'xmlsitemap_settings_engines',
      ),
      'type' => MENU_LOCAL_TASK,
      'access' => user_access('administer site configuration'),
    );
    $items[] = array(
      'path' => 'admin/settings/xmlsitemap/additional',
      'title' => t('Additional'),
      'description' => t('Configure additional links.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'xmlsitemap_settings_additional',
      ),
      'type' => MENU_LOCAL_TASK,
      'access' => user_access('administer site configuration'),
      'weight' => 1,
    );
    $items[] = array(
      'path' => 'sitemap.xml',
      'title' => t('Site map index'),
      'callback' => '_xmlsitemap_output',
      'type' => MENU_CALLBACK,
      'access' => user_access('access content'),
    );
  }
  else {
    $chunk_count = variable_get('xmlsitemap_chunk_count', 0);
    for ($chunk = 0; $chunk < $chunk_count; ++$chunk) {
      $items[] = array(
        'path' => "sitemap{$chunk}.xml",
        'title' => t('Site map !number', array(
          '!number' => $chunk,
        )),
        'callback' => '_xmlsitemap_output',
        'callback arguments' => array(
          $chunk,
        ),
        'type' => MENU_CALLBACK,
        'access' => user_access('access content'),
      );
    }
  }
  return $items;
}

/**
 * Menu callback; return site map settings form.
 */
function xmlsitemap_settings_sitemap() {
  $form['xmlsitemap_chunk_size'] = array(
    '#type' => 'textfield',
    '#title' => t('Chunk size'),
    '#default_value' => variable_get('xmlsitemap_chunk_size', 50000),
    '#size' => 10,
    '#maxlength' => 5,
    '#description' => t('This is the number of links to include in one site map. Values can range between 1 and 50,000. If the total number of links exceeds the chunk size, multiple site maps will be generated.'),
    '#weight' => -1,
  );
  $form['xmlsitemap_front_page_priority'] = array(
    '#type' => 'select',
    '#title' => t('Front page priority'),
    '#default_value' => variable_get('xmlsitemap_front_page_priority', 1),
    '#options' => xmlsitemap_priority_options(),
    '#description' => t('This is the absolute priority for the front page.'),
    '#weight' => -1,
  );
  return system_settings_form($form);
}

/**
 * Validate site map settings form.
 */
function xmlsitemap_settings_sitemap_validate($form_id, $form_values) {
  if ($form_values['xmlsitemap_chunk_size'] > 50000) {
    form_set_error('xmlsitemap_chunk_size', t('Cannot send more than 50,000 links at one time.'));
  }
}

/**
 * Submit site map settings form.
 */
function xmlsitemap_settings_sitemap_submit($form_id, $form_values) {
  system_settings_form_submit($form_id, $form_values);
  xmlsitemap_update_sitemap();
}

/**
 * Menu callback; return search engine settings form.
 */
function xmlsitemap_settings_engines() {
  $form['submission'] = array(
    '#type' => 'fieldset',
    '#title' => t('Submission settings'),
  );
  $form['submission']['xmlsitemap_submit'] = array(
    '#type' => 'checkbox',
    '#title' => t('Submit site map when updated.'),
    '#default_value' => variable_get('xmlsitemap_submit', FALSE),
    '#description' => t('If enabled, search engines will be notified of changes to the site map each time it is updated.'),
  );
  $form['submission']['xmlsitemap_cron_submit'] = array(
    '#type' => 'checkbox',
    '#title' => t('Submit site map on cron run.'),
    '#default_value' => variable_get('xmlsitemap_cron_submit', FALSE),
    '#description' => t('If enabled, search engines will be notified of changes to the site map each time cron is run.'),
  );
  $form['submission']['xmlsitemap_log_access'] = array(
    '#type' => 'checkbox',
    '#title' => t('Log access.'),
    '#default_value' => variable_get('xmlsitemap_log_access', FALSE),
    '#description' => t('If enabled, a watchdog entry will be made each time the site map is accessed, containing information about the requestor.'),
  );
  $form = array_merge($form, module_invoke_all('xmlsitemap_engines', 'form'));
  menu_rebuild();
  return system_settings_form($form);
}

/**
 * Submit search engine settings form.
 */
function xmlsitemap_settings_engines_submit($form_id, $form_values) {
  if ($form_values['xmlsitemap_root']) {
    $form_values['xmlsitemap_submit'] = FALSE;
    $form_values['xmlsitemap_log_access'] = FALSE;
  }
  system_settings_form_submit($form_id, $form_values);
}

/**
 * Menu callback; return additional links form.
 */
function xmlsitemap_settings_additional() {
  $form['xmlsitemap_additional_links_priority'] = array(
    '#type' => 'select',
    '#title' => t('Link priority'),
    '#default_value' => variable_get('xmlsitemap_additional_links_priority', 0.5),
    '#options' => xmlsitemap_priority_options(),
    '#description' => t('This is the default priority for additional links.'),
  );
  $form['delete']['#tree'] = TRUE;
  $form['path']['#tree'] = TRUE;
  $form['link']['#tree'] = TRUE;
  $form['link']['new'] = array(
    '#type' => 'textfield',
    '#size' => 40,
    '#description' => t('Enter a Drupal path to add to the site map.'),
    '#attributes' => array(
      'tabindex' => 1,
    ),
  );
  $form['old_priority']['#tree'] = TRUE;
  $form['priority']['#tree'] = TRUE;
  $form['priority']['new'] = array(
    '#type' => 'select',
    '#default_value' => 'NULL',
    '#options' => xmlsitemap_priority_options('default'),
    '#attributes' => array(
      'tabindex' => 2,
    ),
  );
  $result = pager_query("SELECT path, priority FROM {xmlsitemap_additional} ORDER BY last_changed DESC", 50);
  while ($link = db_fetch_object($result)) {
    $id = $link->path == 'new' ? '_new' : str_replace(array(
      '_',
      '%',
    ), array(
      '__',
      '_',
    ), urlencode($link->path));
    $form['delete'][$id] = array(
      '#type' => 'checkbox',
      '#default_value' => FALSE,
    );
    $form['path'][$id] = array(
      '#type' => 'value',
      '#value' => $link->path,
    );
    $form['link'][$id] = array(
      '#value' => l($link->path, $link->path),
    );
    $priority = isset($link->priority) ? $link->priority : 'NULL';
    $form['old_priority'][$id] = array(
      '#type' => 'value',
      '#value' => $priority,
    );
    $form['priority'][$id] = array(
      '#type' => 'select',
      '#default_value' => $priority,
      '#options' => xmlsitemap_priority_options('default'),
    );
  }
  $form['pager'] = array(
    '#value' => theme('pager', NULL, 50),
  );
  return system_settings_form($form);
}

/**
 * Theme additional links form.
 * @ingroup themeable
 */
function theme_xmlsitemap_settings_additional($form) {
  $output = '';
  $output .= drupal_render($form['xmlsitemap_additional_links_priority']);
  $header = array(
    t('Delete'),
    t('Path'),
    t('Priority'),
  );
  foreach (element_children($form['link']) as $id) {
    $row = array();
    $row[] = isset($form['delete'][$id]) ? drupal_render($form['delete'][$id]) : array_merge(array(
      'header' => TRUE,
      'valign' => 'bottom',
    ), theme('table_select_header_cell'));
    $row[] = drupal_render($form['link'][$id]);
    $row[] = drupal_render($form['priority'][$id]);
    $rows[$id] = array(
      'data' => $row,
      'valign' => 'top',
    );
  }
  if (!empty($rows)) {
    $output .= theme('table', $header, $rows);
  }
  $output .= drupal_render($form);
  drupal_add_js('document.getElementById("edit-link-new").focus()', 'inline', 'footer');
  return $output;
}

/**
 * Submit additional links form.
 */
function xmlsitemap_settings_additional_submit($form_id, $form_values) {
  $update = FALSE;
  if ($form_values['op'] == t('Save configuration')) {
    if ($form_values['xmlsitemap_additional_links_priority'] != variable_get('xmlsitemap_additional_links_priority', 0.1)) {
      $update = TRUE;
    }
    if (!empty($form_values['delete'])) {
      foreach ($form_values['delete'] as $id => $delete) {
        if ($delete || $form_values['path'][$id] == trim($form_values['link']['new'])) {
          db_query("DELETE FROM {xmlsitemap_additional} WHERE path = '%s'", $form_values['path'][$id]);
          unset($form_values['priority'][$id]);
          $update = TRUE;
        }
      }
      unset($form_values['delete']);
    }
    $path = trim($form_values['link']['new']);
    $pid = db_result(db_query("SELECT pid FROM {url_alias} WHERE src = '%s'", $path));
    if (!empty($path)) {
      db_query("\n        INSERT INTO {xmlsitemap_additional} (path, pid, last_changed, priority) VALUES ('%s', %s, %d, %s)\n      ", $path, empty($pid) ? 'NULL' : $pid, time(), $form_values['priority']['new']);
      unset($form_values['link'], $form_values['priority']['new']);
      $update = TRUE;
    }
    if (!empty($form_values['priority'])) {
      foreach ($form_values['priority'] as $id => $priority) {
        if ($priority != $form_values['old_priority'][$id]) {
          $pid = db_result(db_query("SELECT pid FROM {url_alias} WHERE src = '%s'", $form_values['path'][$id]));
          db_query("\n            UPDATE {xmlsitemap_additional}\n            SET pid = %s, previously_changed = last_changed, last_changed = %d, priority = %s\n            WHERE path = '%s'\n          ", empty($pid) ? 'NULL' : $pid, time(), $priority, $form_values['path'][$id]);
          $update = TRUE;
        }
      }
      unset($form_values['path'], $form_values['priority'], $form_values['old_priority']);
    }
  }
  elseif (variable_get('xmlsitemap_additional_links_priority', 0.1) != 0.1) {
    if (in_array('NULL', $form_values['old_priority'])) {
      $update = TRUE;
    }
    unset($form_values['delete'], $form_values['path'], $form_values['link'], $form_values['old_priority'], $form_values['priority']);
  }
  system_settings_form_submit($form_id, $form_values);
  if ($update) {
    xmlsitemap_update_sitemap();
  }
}

/**
 * Get an array of site map priority options.
 * @param $option:
 * If not given, the array will include priority values from 0.0 to 1.0.
 * - exclude: Add option to exclude item from site map.
 * - default: Add option to use default priority. Only for cases where a
 *   default priority exists.
 * - both: Add both the default and exclude options.
 * @return An array of priority options.
 */
function xmlsitemap_priority_options($option = NULL) {
  if ($option == 'default' || $option == 'both') {
    $options['NULL'] = t('Default');
  }
  $options['1'] = '1.0';
  $values = array(
    '0.9',
    '0.8',
    '0.7',
    '0.6',
    '0.5',
    '0.4',
    '0.3',
    '0.2',
    '0.1',
  );
  foreach ($values as $value) {
    $options[$value] = $value;
  }
  $options['0'] = '0.0';
  if ($option == 'exclude' || $option == 'both') {
    $options['-1'] = t('Not in site map');
  }
  return $options;
}

/**
 * Implementation of hook_robotstxt().
 */
function xmlsitemap_robotstxt() {
  return array(
    "Sitemap: " . xmlsitemap_url('sitemap.xml', drupal_lookup_path('alias', 'sitemap.xml') ? drupal_lookup_path('alias', 'sitemap.xml') : NULL, NULL, NULL, TRUE),
  );
}

/**
 * Menu callback; display the site map.
 * @param $chunk:
 * An integer specifying which chunk of the site map is being requested. If
 * not set and there is more than one chunk, display the site map index.
 * @return None
 */
function _xmlsitemap_output($chunk = NULL) {
  drupal_set_header('Content-type: text/xml; charset=utf-8');
  global $user;
  $dest = file_directory_path() . '/xmlsitemap';
  file_check_directory($dest, FILE_CREATE_DIRECTORY);
  if (isset($chunk)) {
    $dest .= "/sitemap{$chunk}.xml.gz";
    $type = t('Site map @chunk', array(
      '@chunk' => $chunk,
    ));
  }
  else {
    $dest .= '/sitemap.xml.gz';
    $link_count = _xmlsitemap_link_count();
    $chunk_size = variable_get('xmlsitemap_chunk_size', 50000);
    $type = $link_count > $chunk_size ? t('Site map index') : t('Site map');
  }
  $status = TRUE;
  if (!file_exists($dest) || variable_get('xmlsitemap_update', FALSE)) {
    $page = isset($chunk) ? $chunk : 'index';
    $status = _xmlsitemap_update_cache($page);
  }
  if ($status) {
    if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE || zlib_get_coding_type() !== FALSE || extension_loaded('eAccelerator') || variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && empty($user->uid)) {
      readgzfile($dest);
    }
    else {
      drupal_set_header('Content-Encoding: gzip');
      print file_get_contents($dest);
    }
    if (variable_get('xmlsitemap_log_access', FALSE)) {
      $message = array_shift(module_invoke_all('xmlsitemap_engines', 'access', $type));
      $message = isset($message) ? $message : t('!sitemap downloaded by @user-agent at @address.', array(
        '!sitemap' => $type,
        '@user-agent' => $_SERVER['HTTP_USER_AGENT'],
        '@address' => $_SERVER['REMOTE_ADDR'],
      ));
      watchdog('xmlsitemap', $message);
    }
  }
  else {
    drupal_not_found();
  }
}

/**
 * Count the total number of links in the site.
 * @return An integer containing the total number of links
 */
function _xmlsitemap_link_count() {
  static $count;
  $count = isset($count) ? $count : count(_xmlsitemap_links());
  return $count;
}

/**
 * Update the cached site map files.
 * @return TRUE if the update was successful, FALSE otherwise.
 */
function _xmlsitemap_update_cache($page = NULL) {
  global $user;
  $current_user = $user;
  $user = user_load(array(
    'uid' => 0,
  ));
  $path = file_directory_path() . '/xmlsitemap';
  file_check_directory($path, FILE_CREATE_DIRECTORY);
  $node_count = db_result(db_query("SELECT COUNT(*) FROM {node}"));
  $dest = $path . '/sitemap.xml.gz';
  $link_count = _xmlsitemap_link_count();
  $chunk_size = variable_get('xmlsitemap_chunk_size', 50000);
  $status = TRUE;
  if ($link_count > $chunk_size) {
    $data = gzencode(_xmlsitemap_output_index($link_count));
    if (file_save_data($data, $dest, FILE_EXISTS_REPLACE) === 0 && ($page == 'index' || !isset($page))) {
      $status = FALSE;
    }
    for ($chunk = 0; $chunk < $link_count / $chunk_size; ++$chunk) {
      $dest = $path . "/sitemap{$chunk}.xml.gz";
      $data = gzencode(_xmlsitemap_output_chunk($chunk));
      if (file_save_data($data, $dest, FILE_EXISTS_REPLACE) === 0 && ($page == $chunk || !isset($page))) {
        $status = FALSE;
      }
    }
  }
  else {
    $chunk = 0;
    $data = gzencode(_xmlsitemap_output_chunk($chunk));
    if (file_save_data($data, $dest, FILE_EXISTS_REPLACE) === 0 && ($page == 'index' || !isset($page))) {
      $status = FALSE;
    }
  }
  variable_set('xmlsitemap_chunk_count', $chunk);
  variable_set('xmlsitemap_update', FALSE);
  if (!$status) {
    drupal_set_message(t('Unable to load site map. Make sure that there is an xmlsitemap directory in your files directory and that it is writable by Drupal.'), 'error');
  }
  $user = $current_user;
  return $status;
}

/**
 * Generate the site map index.
 * @param $link_count:
 * An integer containing the total number of links in the site
 * @return A string containing the site map index
 */
function _xmlsitemap_output_index($link_count) {
  $output = '';
  $xsl_path = file_directory_path() . '/xmlsitemap/gss.xsl';
  $xsl_path = file_exists($xsl_path) ? _xmlsitemap_file_create_url($xsl_path) : base_path() . drupal_get_path('module', 'xmlsitemap') . '/gss/gss.xsl';
  $output .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
  $output .= '<?xml-stylesheet type="text/xsl" href="' . $xsl_path . '" ?>' . "\n";
  $output .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' . "\n";
  $output .= '              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . "\n";
  $output .= '              xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9' . "\n";
  $output .= '                                  http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd">' . "\n";
  $chunk_size = variable_get('xmlsitemap_chunk_size', 50000);
  for ($chunk = 0; $chunk < $link_count / $chunk_size; ++$chunk) {
    $output .= '<sitemap><loc>' . xmlsitemap_url("sitemap{$chunk}.xml", NULL, NULL, NULL, TRUE) . '</loc>';
    $previous = $chunk * $chunk_size;
    $links = array_slice(_xmlsitemap_links(), $previous, $chunk_size);
    $output .= '<lastmod>' . gmdate('Y-m-d\\TH:i:s+00:00', array_reduce($links, '_xmlsitemap_chunk_last_change')) . '</lastmod>';
    $output .= "</sitemap>\n";
  }
  $output .= '</sitemapindex>';
  return $output;
}

/**
 * Compare link modification time to modification time of previous link.
 * @param $time:
 * Modification time of previous link
 * @param $value:
 * Link array
 * @return Most recent time
 */
function _xmlsitemap_chunk_last_change($time, $value) {
  if ($time < $value['#lastmod']) {
    $time = $value['#lastmod'];
  }
  return $time;
}

/**
 * Generate a chunk of the site map.
 * @param $chunk:
 * An integer specifying which chunk of the site map to display
 * @return A string containing a chunk of the site map
 */
function _xmlsitemap_output_chunk($chunk) {
  $output = '';
  if (!ini_get('safe_mode')) {
    set_time_limit(240);
  }
  $xsl_path = file_directory_path() . '/xmlsitemap/gss.xsl';
  $xsl_path = file_exists($xsl_path) ? _xmlsitemap_file_create_url($xsl_path) : base_path() . drupal_get_path('module', 'xmlsitemap') . '/gss/gss.xsl';
  $output .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
  $output .= '<?xml-stylesheet type="text/xsl" href="' . $xsl_path . '" ?>' . "\n";
  $output .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' . "\n";
  $output .= '        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . "\n";
  $output .= '        xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9' . "\n";
  $output .= '                            http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">' . "\n";
  $chunk_size = variable_get('xmlsitemap_chunk_size', 50000);
  $previous = $chunk * $chunk_size;
  $output .= implode("\n", _xmlsitemap_format(array_slice(_xmlsitemap_links(), $previous, $chunk_size)));
  $output .= "\n</urlset>";
  return $output;
}

/**
 * Modified version of file_create_url(). Allows us to remove language
 * prefixes.
 * @param $path: the path to the file
 * @return A URL to the file
 */
function _xmlsitemap_file_create_url($path) {

  // Strip file_directory_path from $path. We only include relative paths in urls.
  if (strpos($path, file_directory_path() . '/') === 0) {
    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
  }
  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
    case FILE_DOWNLOADS_PUBLIC:
      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path);
    case FILE_DOWNLOADS_PRIVATE:
      return xmlsitemap_url('system/files/' . $path, NULL, NULL, NULL, TRUE);
  }
}

/**
 * Implementation of hook_file_download().
 */
function xmlsitemap_file_download($file) {
  if ($file == 'xmlsitemap/gss.xsl') {
    return array(
      'Content-Type:',
    );
  }
}

/**
 * Get all site map links.
 * @return An array of links from hook_xmlsitemap_links().
 */
function _xmlsitemap_links() {
  static $links;
  if (!isset($links)) {
    if (module_exists('i18n')) {
      i18n_selection_mode('off');
    }
    $entries = module_invoke_all('xmlsitemap_links');
    if (module_exists('i18n')) {
      i18n_selection_mode('reset');
    }
    if (!empty($entries)) {
      foreach ($entries as $key => $link) {
        $lastmod[$key] = $link['#lastmod'];
      }
      array_multisort($lastmod, $entries);
    }
    $links = $entries;
  }
  return $links;
}

/**
 * Process an array of links.
 * @param $entries: An array of links to process
 * @return A string of formatted links
 */
function _xmlsitemap_format($entries) {
  $links = array();
  foreach ($entries as $entry) {
    if (isset($entry['#loc'])) {
      $link = '  <url>' . "\n";
      $link .= '    <loc>' . check_url($entry['#loc']) . '</loc>' . "\n";
      if (isset($entry['#lastmod'])) {
        $link .= '    <lastmod>' . gmdate('Y-m-d\\TH:i:s+00:00', $entry['#lastmod']) . '</lastmod>' . "\n";
      }
      if (isset($entry['#changefreq'])) {
        $link .= '    <changefreq>' . _xmlsitemap_frequency($entry['#changefreq']) . '</changefreq>' . "\n";
      }
      if (isset($entry['#priority']) && $entry['#priority'] <= 1 && $entry['#priority'] >= 0) {
        $link .= '    <priority>' . number_format($entry['#priority'], 1) . '</priority>' . "\n";
      }
      $link .= '  </url>';
      $links[] = $link;
    }
  }
  return $links;
}

/**
 * Determine the frequency of updates to a link.
 * @param $interval: The number of seconds since last change
 * @return A string representing the update frequency according to the
 * sitemaps.org protocol
 */
function _xmlsitemap_frequency($interval) {
  $frequencies = array(
    'always' => 3600,
    'hourly' => 86400,
    'daily' => 604800,
    'weekly' => 2419200,
    'monthly' => 29030400,
    'yearly' => 100000000,
    'never' => 0,
  );
  $frequency = 'never';
  if (!is_numeric($interval)) {
    if (isset($frequencies[$interval])) {
      $frequency = $interval;
    }
  }
  else {
    foreach ($frequencies as $frequency => $value) {
      if ($interval < $value) {
        break;
      }
    }
  }
  return $frequency;
}

/**
 * Implementation of hook_xmlsitemap_links().
 */
function xmlsitemap_xmlsitemap_links($type = NULL, $excludes = array()) {
  $links = array();
  if (!isset($type)) {
    global $base_url;
    $links[] = array(
      '#loc' => "{$base_url}/",
      '#changefreq' => 'always',
      '#priority' => variable_get('xmlsitemap_front_page_priority', 1),
    );
    $links = array_merge($links, _xmlsitemap_additional_links());
    $links = array_merge($links, module_invoke_all('gsitemap'));
    $links = array_merge($links, _xmlsitemap_xml_links());
    if (!empty($links)) {
      foreach ($links as $key => $link) {
        $loc[$key] = $link['#loc'];
      }
      array_multisort($loc, $links);
    }
  }
  return $links;
}

/**
 * Get additional links.
 * @return An array of links. Each link is an array containing the XML
 * values for a site map URL.
 */
function _xmlsitemap_additional_links() {
  $links = array();
  $result = db_query("\n    SELECT xa.*, ua.dst AS alias FROM {xmlsitemap_additional} xa\n    LEFT JOIN {url_alias} ua ON xa.pid = ua.pid\n  ");
  while ($link = db_fetch_object($result)) {
    $age = time() - $link->last_changed;
    if (!empty($link->previously_changed)) {
      $interval = $link->last_changed - $link->previously_changed;
    }
    else {
      $interval = 0;
    }
    $links[] = array(
      '#loc' => xmlsitemap_url($link->path, $link->alias, NULL, NULL, TRUE),
      '#lastmod' => $link->last_changed,
      '#changefreq' => max($age, $interval),
      '#priority' => isset($link->priority) ? $link->priority : variable_get('xmlsitemap_additional_links_priority', 0.1),
    );
  }
  return $links;
}

/**
 * Extract links from site maps returned by hook_xmlsitemap_links().
 * @return An array of links.
 */
function _xmlsitemap_xml_links() {
  $links = array();
  $xml = module_invoke_all('xmlsitemap_links', 'xml');
  $xml = array_merge($xml, module_invoke_all('gsitemap', 'xml'));
  foreach ($xml as $entries) {
    $start = strpos($entries, '<url>');
    if ($start !== FALSE) {
      $length = strpos($entries, '</urlset>') - $start;
      $entries = substr($entries, $start, $length);
      $entries = explode('<url>', $entries);
      foreach ($entries as $value) {
        if (($start = strpos($value, '<loc>')) !== FALSE) {
          $length = $start + strlen('<loc>');
          $link['#loc'] = substr($value, $length, strpos($value, '</loc>') - $length);
          if (($start = strpos($value, '<lastmod>')) !== FALSE) {
            $length = $start + strlen('<lastmod>');
            $t = array_shift(explode('+', substr($value, $length, strpos($value, '</lastmod>') - $length)));
            $t = explode('T', $t);
            $t = array_merge(explode('-', $t[0]), explode(':', $t[1]));
            $link['#lastmod'] = gmmktime($t[3], $t[4], $t[5], $t[1], $t[2], $t[0]);
          }
          if (($start = strpos($value, '<changefreq>')) !== FALSE) {
            $length = $start + strlen('<changefreq>');
            $link['#changefreq'] = substr($value, $length, strpos($value, '</changefreq>') - $length);
          }
          if (($start = strpos($value, '<priority>')) !== FALSE) {
            $length = $start + strlen('<priority>');
            $link['#priority'] = substr($value, $length, strpos($value, '</priority>') - $length);
          }
          $links[] = $link;
        }
      }
    }
  }
  return $links;
}

/**
 * Modified version of url(). We don't want to do a separate database query
 * for each url, so we pass the alias as an extra parameter.
 * @param $alias: The URL alias. Default is NULL.
 * @return The fully formatted URL
 */
function xmlsitemap_url($path = NULL, $alias = NULL, $query = NULL, $fragment = NULL, $absolute = FALSE) {
  if (isset($fragment)) {
    $fragment = "#{$fragment}";
  }
  $colonpos = strpos($path, ':');
  if ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path)) {
    if (strpos($path, '#') !== FALSE) {
      list($path, $old_fragment) = explode('#', $path, 2);
      if (isset($old_fragment) && !isset($fragment)) {
        $fragment = "#{$old_fragment}";
      }
    }
    if (isset($query)) {
      $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $query;
    }
    return $path . $fragment;
  }
  global $base_url;
  static $script;
  static $clean_url;
  $script = isset($script) ? $script : strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE ? 'index.php' : '';
  $clean_url = isset($clean_url) ? $clean_url : variable_get('clean_url', FALSE);
  $base = $absolute ? $base_url . '/' : base_path();
  if (!empty($path) && $path != '<front>') {
    $path = _xmlsitemap_get_path_alias($path, $alias);
    $path = drupal_urlencode($path);
    if (!$clean_url) {
      if (isset($query)) {
        return $base . $script . '?q=' . $path . '&' . $query . $fragment;
      }
      else {
        return $base . $script . '?q=' . $path . $fragment;
      }
    }
    else {
      if (isset($query)) {
        return $base . $path . '?' . $query . $fragment;
      }
      else {
        return $base . $path . $fragment;
      }
    }
  }
  else {
    if (isset($query)) {
      return $base . $script . '?' . $query . $fragment;
    }
    else {
      return $base . $fragment;
    }
  }
}

/**
 * Modified version of drupal_get_path_alias() for xmlsitemap_url().
 * @param $path: An internal Drupal path
 * @param $alias: The URL alias. Default is NULL.
 * @return A processed path
 */
function _xmlsitemap_get_path_alias($path, $alias = NULL) {
  $result = $path;
  if (!empty($alias)) {
    $result = $alias;
  }
  if (function_exists('custom_url_rewrite')) {
    $result = custom_url_rewrite('alias', $result, $path);
  }
  if (module_exists('i18n')) {
    i18n_get_lang_prefix($result, TRUE);
  }
  return $result;
}

/**
 * Implementation of hook_cron().
 */
function xmlsitemap_cron() {
  if (variable_get('xmlsitemap_cron_submit', FALSE) && variable_get('xmlsitemap_changed', FALSE)) {
    _xmlsitemap_ping();
  }
}

/**
 * Mark the site map as changed and the cache as needing update.
 * @return None
 */
function xmlsitemap_update_sitemap() {
  variable_set('xmlsitemap_changed', TRUE);
  variable_set('xmlsitemap_update', TRUE);
  if (variable_get('xmlsitemap_submit', FALSE)) {
    _xmlsitemap_submit_on_exit();
  }
}

/**
 * Schedule a call to _xmlsitemap_ping() to be run on exit. Use this
 * function instead of _xmlsitemap_ping() to avoid a delay in outputting
 * the page to the user.
 * @return TRUE if the function has been called previously, FALSE otherwise.
 */
function _xmlsitemap_submit_on_exit() {
  static $called = FALSE;
  $return = $called;
  $called = TRUE;
  return $return;
}

/**
 * Implementation of hook_exit().
 */
function xmlsitemap_exit() {
  if (_xmlsitemap_submit_on_exit()) {
    _xmlsitemap_ping();
  }
}

/**
 * Submit the site map to search engines.
 * @return None
 */
function _xmlsitemap_ping() {
  $status = _xmlsitemap_update_cache();
  if ($status) {
    module_invoke_all('xmlsitemap_engines', 'ping');
    variable_set('xmlsitemap_changed', FALSE);
  }
}

/**
 * @} End of "addtogroup xmlsitemap".
 */

Functions

Namesort descending Description
theme_xmlsitemap_settings_additional Theme additional links form.
xmlsitemap_cron Implementation of hook_cron().
xmlsitemap_exit Implementation of hook_exit().
xmlsitemap_file_download Implementation of hook_file_download().
xmlsitemap_help Implementation of hook_help().
xmlsitemap_menu Implementation of hook_menu().
xmlsitemap_priority_options Get an array of site map priority options.
xmlsitemap_robotstxt Implementation of hook_robotstxt().
xmlsitemap_settings_additional Menu callback; return additional links form.
xmlsitemap_settings_additional_submit Submit additional links form.
xmlsitemap_settings_engines Menu callback; return search engine settings form.
xmlsitemap_settings_engines_submit Submit search engine settings form.
xmlsitemap_settings_sitemap Menu callback; return site map settings form.
xmlsitemap_settings_sitemap_submit Submit site map settings form.
xmlsitemap_settings_sitemap_validate Validate site map settings form.
xmlsitemap_update_sitemap Mark the site map as changed and the cache as needing update.
xmlsitemap_url Modified version of url(). We don't want to do a separate database query for each url, so we pass the alias as an extra parameter.
xmlsitemap_xmlsitemap_links Implementation of hook_xmlsitemap_links().
_xmlsitemap_additional_links Get additional links.
_xmlsitemap_chunk_last_change Compare link modification time to modification time of previous link.
_xmlsitemap_file_create_url Modified version of file_create_url(). Allows us to remove language prefixes.
_xmlsitemap_format Process an array of links.
_xmlsitemap_frequency Determine the frequency of updates to a link.
_xmlsitemap_get_path_alias Modified version of drupal_get_path_alias() for xmlsitemap_url().
_xmlsitemap_links Get all site map links.
_xmlsitemap_link_count Count the total number of links in the site.
_xmlsitemap_output Menu callback; display the site map.
_xmlsitemap_output_chunk Generate a chunk of the site map.
_xmlsitemap_output_index Generate the site map index.
_xmlsitemap_ping Submit the site map to search engines.
_xmlsitemap_submit_on_exit Schedule a call to _xmlsitemap_ping() to be run on exit. Use this function instead of _xmlsitemap_ping() to avoid a delay in outputting the page to the user.
_xmlsitemap_update_cache Update the cached site map files.
_xmlsitemap_xml_links Extract links from site maps returned by hook_xmlsitemap_links().