You are here

xmlsitemap_node.module in XML sitemap 5

Adds nodes to the site map.

File

xmlsitemap_node/xmlsitemap_node.module
View source
<?php

/**
 * @file Adds nodes to the site map.
 */

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

/**
 * Implementation of hook_xmlsitemap_links().
 */
function xmlsitemap_node_xmlsitemap_links($type = NULL, $excludes = array()) {
  $links = array();
  if (!isset($type)) {
    $links = _xmlsitemap_node_links(_xmlsitemap_node_excludes());
    $links = array_merge($links, module_invoke_all('xmlsitemap_links', 'node', _xmlsitemap_node_excludes()));
    $links = array_merge($links, module_invoke_all('gsitemap', 'node', _xmlsitemap_node_excludes()));
    if (!empty($links)) {
      foreach ($links as $key => $link) {
        $nid[$key] = $link['nid'];
        $loc[$key] = $link['#loc'];
      }
      array_multisort($nid, $loc, $links);
    }
  }
  return $links;
}

/**
 * Get array of excluded types.
 * @return An array of node types to exclude.
 */
function _xmlsitemap_node_excludes() {
  static $excludes;
  if (!isset($excludes)) {
    $excludes = array();
    foreach (node_get_types() as $type => $name) {
      if (variable_get("xmlsitemap_node_type_priority_{$type}", 0.5) < 0) {
        $excludes[] = $type;
      }
    }
  }
  return $excludes;
}

/**
 * Get node links.
 * @param $excludes: An array of node types to exclude.
 * @return An array of links. Each link is an array containing the XML
 * values for a site map URL.
 */
function _xmlsitemap_node_links($excludes = array()) {
  $links = array();
  if (module_exists('comment')) {
    $sql = "\n      SELECT n.nid, n.type, n.promote, s.comment_count, n.changed, xn.previously_changed, s.last_comment_timestamp, xn.previous_comment, xn.priority_override, ua.dst AS alias\n      FROM {node} n\n      LEFT JOIN {node_comment_statistics} s ON s.nid = n.nid";
  }
  else {
    $sql = "\n      SELECT n.nid, n.type, n.promote, n.changed, xn.previously_changed, xn.priority_override, ua.dst AS alias\n      FROM {node} n";
  }
  $sql .= "\n    LEFT JOIN {xmlsitemap_node} xn ON xn.nid = n.nid\n    LEFT JOIN {url_alias} ua ON ua.pid = xn.pid\n    WHERE n.status > 0\n    AND (n.type NOT IN ('" . implode("', '", $excludes) . "') AND xn.priority_override IS NULL OR xn.priority_override >= 0)\n    AND n.nid <> %d";
  $result = db_query(db_rewrite_sql($sql), _xmlsitemap_node_frontpage());
  while ($node = db_fetch_object($result)) {
    $links[] = array(
      'nid' => $node->nid,
      '#loc' => xmlsitemap_url('node/' . $node->nid, $node->alias, NULL, NULL, TRUE),
      '#lastmod' => variable_get('xmlsitemap_node_count_comments', TRUE) ? max($node->changed, $node->last_comment_timestamp) : $node->changed,
      '#changefreq' => xmlsitemap_node_frequency($node),
      '#priority' => xmlsitemap_node_priority($node),
    );
  }
  return $links;
}
function xmlsitemap_node_frequency($node) {
  $age = time() - $node->changed;
  if (variable_get('xmlsitemap_node_count_comments', TRUE)) {
    $age = time() - max($node->changed, $node->last_comment_timestamp);
    if (!empty($node->previously_changed) && isset($node->previous_comment)) {
      $interval = min($node->changed, $node->last_comment_timestamp) - max($node->previously_changed, $node->previous_comment);
    }
    elseif (!empty($node->previously_changed)) {
      $interval = min($node->changed, $node->last_comment_timestamp) - $node->previously_changed;
    }
    elseif (isset($node->previous_comment)) {
      $interval = min($node->changed, $node->last_comment_timestamp) - $node->previous_comment;
    }
    else {
      $interval = 0;
    }
  }
  else {
    $interval = empty($node->previously_changed) ? 0 : $node->changed - $node->previously_changed;
  }
  return max($age, $interval);
}

/**
 * Get the nid of the front page node.
 * @return Integer of front page node ID, or 0 if front page is not a node.
 */
function _xmlsitemap_node_frontpage() {
  static $nid;
  if (!isset($nid)) {
    $nid = 0;
    $frontpage = explode('/', drupal_get_normal_path(variable_get('site_frontpage', 'node')));
    if (count($frontpage) == 2 && $frontpage[0] == 'node' && is_numeric($frontpage[1])) {
      $nid = $frontpage[1];
    }
  }
  return $nid;
}

/**
 * Calculate the priority of a node.
 * @param $node: A node object
 * @return A number between 0 and 1, or -1
 */
function xmlsitemap_node_priority($node) {
  static $promote_priority;
  static $comment_priority;
  static $maxcomments;
  $promote_priority = isset($promote_priority) ? $promote_priority : variable_get('xmlsitemap_node_promote_priority', 0.3);
  $comment_priority = isset($comment_priority) ? $comment_priority : variable_get('xmlsitemap_node_comment_priority', 0.5);
  if (!isset($maxcomments)) {
    $maxcomments = 0;
    if (module_exists('comment')) {
      $maxcomments = db_result(db_query("SELECT MAX(comment_count) FROM {node_comment_statistics}"));
    }
  }
  $priority = $node->priority_override;
  if (!isset($node->priority_override)) {
    $priority = 0;
    $priority += variable_get("xmlsitemap_node_type_priority_{$node->type}", 0.5);
    if ($node->promote) {
      $priority += $promote_priority;
    }
    if (!empty($maxcomments)) {
      $priority += $node->comment_count / $maxcomments * $comment_priority;
    }
    $priority = round($priority, 1);
    $priority = min($priority, 0.9);
  }
  return $priority;
}

/**
 * Implementation of hook_perm().
 */
function xmlsitemap_node_perm() {
  return array(
    'override node priority',
  );
}

/**
 * Implementation of hook_form_alter().
 */
function xmlsitemap_node_form_alter($form_id, &$form) {
  switch ($form_id) {
    case $form['type']['#value'] . '_node_form':
      if (isset($form['type'])) {
        $node = $form['#node'];
        if (user_access('override node priority')) {
          $form['xmlsitemap_node'] = array(
            '#type' => 'fieldset',
            '#title' => t('Site map settings'),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
            '#weight' => 30,
          );
          $options = xmlsitemap_priority_options('both');
          $default = variable_get("xmlsitemap_node_type_priority_{$node->type}", '0.5');
          $form['xmlsitemap_node']['priority_override'] = array(
            '#type' => 'select',
            '#title' => t('Site map priority'),
            '#default_value' => $node->priority_override,
            '#options' => $options,
            '#description' => t('The default priority is %priority.', array(
              '%priority' => $options[$default],
            )),
          );
        }
        else {
          $form['priority_override'] = array(
            '#type' => 'value',
            '#value' => $node->priority_override,
          );
        }
        $form['xmlsitemap_node_status'] = array(
          '#type' => 'value',
          '#value' => $node->status,
        );
      }
      break;
    case 'node_type_form':
      if (isset($form['identity']['type'])) {
        $form['xmlsitemap_node'] = array(
          '#type' => 'fieldset',
          '#title' => t('Site map'),
          '#collapsible' => TRUE,
          '#collapsed' => TRUE,
        );
        $form['xmlsitemap_node']['xmlsitemap_node_type_priority'] = array(
          '#type' => 'select',
          '#title' => t('Priority adjustment'),
          '#default_value' => variable_get("xmlsitemap_node_type_priority_{$form['#node_type']->type}", 0.5),
          '#options' => xmlsitemap_priority_options('exclude'),
          '#description' => t('This number will be added to the priority of this content type.'),
        );
        $form['xmlsitemap_old_priority'] = array(
          '#type' => 'value',
          '#value' => variable_get("xmlsitemap_node_type_priority_{$form['#node_type']->type}", 0.5),
        );
        $form['#submit']['_xmlsitemap_node_submit'] = array();
        $form['submit']['#weight'] = 1;
        $form['reset']['#weight'] = 1;
      }
      break;
    case 'xmlsitemap_settings_sitemap':
      $form['xmlsitemap_node'] = array(
        '#type' => 'fieldset',
        '#title' => t('Content priority'),
        '#description' => t('The default priority for specific content types can be set on the !link pages.', array(
          '!link' => l(t('content type settings'), 'admin/content/types'),
        )),
      );
      $form['xmlsitemap_node']['xmlsitemap_node_promote_priority'] = array(
        '#type' => 'select',
        '#title' => t('Promotion adjustment'),
        '#default_value' => variable_get('xmlsitemap_node_promote_priority', 0.3),
        '#options' => xmlsitemap_priority_options(),
        '#description' => t('This number will be added to the priority of each post that is promoted to the front page.'),
      );
      $form['xmlsitemap_node']['xmlsitemap_node_comment_priority'] = array(
        '#type' => 'select',
        '#title' => t('Comment ratio weight'),
        '#default_value' => variable_get('xmlsitemap_node_comment_priority', 0.5),
        '#options' => xmlsitemap_priority_options(),
        '#description' => t('This number will be multiplied by the ratio of the number of comments on a post to the highest number of comments on any post&#8212;that is, this number will be added to the priority of the post with the most comments.'),
      );
      $form['xmlsitemap_node']['xmlsitemap_node_count_comments'] = array(
        '#type' => 'checkbox',
        '#title' => t('Count comments in change date and frequency'),
        '#default_value' => variable_get('xmlsitemap_node_count_comments', TRUE),
        '#description' => t('If enabled, the frequency of comments on a post will affect its change frequency and last modification date.'),
      );
      $form['buttons']['#weight'] = 1;
      break;
  }
}

/**
 * Implmentation of hook_nodeapi().
 */
function xmlsitemap_node_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  switch ($op) {
    case 'prepare':
      $priority = db_result(db_query("SELECT priority_override FROM {xmlsitemap_node} WHERE nid = %d", $node->nid));
      $node->priority_override = isset($priority) && $priority !== FALSE ? $priority : 'NULL';
      break;
    case 'insert':
      $node->priority_override = isset($node->priority_override) ? $node->priority_override : 'NULL';
      $query = "\n        INSERT INTO {xmlsitemap_node} (nid, pid, last_changed, priority_override)\n        SELECT %d, ua.pid, %d, %s\n        FROM {node} n";
      switch ($GLOBALS['db_type']) {
        case 'mysql':
        case 'mysqli':
          $query .= "\n        LEFT JOIN {url_alias} ua ON ua.src = CONCAT('node/', CAST(%d AS CHAR))";
          break;
        case 'pgsql':
          $query .= "\n        LEFT JOIN {url_alias} ua ON ua.src = CONCAT('node/', CAST(%d AS VARCHAR))";
          break;
      }
      $query .= "\n        LIMIT 1";
      db_query($query, $node->nid, $node->changed, $node->priority_override, $node->nid);
      if ($node->status) {
        xmlsitemap_update_sitemap();
      }
      break;
    case 'update':
      if (!isset($node->priority_override)) {
        $priority = db_result(db_query("SELECT priority_override FROM {xmlsitemap_node} WHERE nid = %d", $node->nid));
        $node->priority_override = isset($priority) && $priority !== FALSE ? $priority : 'NULL';
      }
      switch ($GLOBALS['db_type']) {
        case 'mysql':
        case 'mysqli':
          db_query("\n            UPDATE {xmlsitemap_node} xn LEFT JOIN {url_alias} ua ON ua.src = CONCAT('node/', CAST(xn.nid AS CHAR))\n            SET xn.pid = ua.pid, xn.previously_changed = xn.last_changed, xn.last_changed = %d, xn.priority_override = %s\n            WHERE xn.nid = %d\n          ", $node->changed, $node->priority_override, $node->nid);
          break;
        case 'pgsql':
          db_query("\n            UPDATE {xmlsitemap_node}\n            SET pid = {url_alias}.pid, previously_changed = last_changed, last_changed = %d, priority_override = %s\n            FROM {url_alias} WHERE nid = %d AND ({url_alias}.src = CONCAT('node/', CAST(nid AS VARCHAR)) OR {url_alias}.src IS NULL)\n          ", $node->changed, $node->priority_override, $node->nid);
          break;
      }
      if ($node->status || $node->xmlsitemap_node_status) {
        xmlsitemap_update_sitemap();
      }
      break;
    case 'delete':
      db_query("DELETE FROM {xmlsitemap_node} WHERE nid = %d", $node->nid);
      if ($node->status) {
        xmlsitemap_update_sitemap();
      }
      break;
  }
}

/**
 * Implementation of hook_comment().
 */
function xmlsitemap_node_comment($comment, $op) {
  $comment = (object) $comment;
  switch ($op) {
    case 'insert':
    case 'update':
    case 'moderate':
    case 'delete':
      db_query("UPDATE {xmlsitemap_node} SET previous_comment = last_comment, last_comment = %d WHERE nid = %d", $comment->timestamp, $comment->nid);
      if (variable_get('xmlsitemap_node_count_comments', TRUE)) {
        xmlsitemap_update_sitemap();
      }
      break;
  }
}

/**
 * Add submit actions to forms.
 * @return None
 */
function _xmlsitemap_node_submit($form_id, $form_values) {
  switch ($form_id) {
    case 'node_type_form':
      $op = isset($form_values['op']) ? $form_values['op'] : '';
      $type = isset($form_values['old_type']) ? $form_values['old_type'] : trim($form_values['type']);
      $priority = $form_values['xmlsitemap_node_type_priority'];
      $old_priority = $form_values['xmlsitemap_old_priority'];
      if ($op == t('Save content type') && $priority != $old_priority || $op == t('Reset to defaults') && $old_priority != 0.1) {
        xmlsitemap_update_sitemap();
      }
      break;
  }
}

/**
 * Implementation of hook_node_type().
 */
function xmlsitemap_node_node_type($op, $info) {
  if ($op == 'delete' && variable_get("xmlsitemap_node_type_priority_{$info->old_type}", 0.5) != 0.5) {
    xmlsitemap_update_sitemap();
  }
}

/**
 * Implementation of hook_cron().
 */
function xmlsitemap_node_cron() {
  if (db_result(db_query_range("SELECT COUNT(*) FROM {node} n LEFT JOIN {xmlsitemap_node} xn ON xn.nid = n.nid WHERE xn.nid IS NULL", 0, 1))) {
    $query = "\n      INSERT INTO {xmlsitemap_node} (nid, last_changed, last_comment, previous_comment)\n      SELECT n.nid, n.changed, s.last_comment_timestamp, MAX(c.timestamp) FROM {node} n\n      LEFT JOIN {node_comment_statistics} s ON s.nid = n.nid\n      LEFT OUTER JOIN {comments} c ON c.nid = n.nid AND c.timestamp < s.last_comment_timestamp\n      LEFT JOIN {xmlsitemap_node} xn ON xn.nid = n.nid\n      WHERE xn.nid IS NULL\n      GROUP BY n.nid, n.changed, s.last_comment_timestamp\n    ";
    db_query($query);
    switch ($GLOBALS['db_type']) {
      case 'mysql':
      case 'mysqli':
        db_query("\n          UPDATE {xmlsitemap_node} xn INNER JOIN {url_alias} ua\n          ON ua.src = CONCAT('node/', CAST(xn.nid AS CHAR))\n          SET xn.pid = ua.pid\n          WHERE xn.pid IS NULL\n        ");
        break;
      case 'pgsql':
        db_query("\n          UPDATE {xmlsitemap_node}\n          SET pid = {url_alias}.pid\n          FROM {url_alias}\n          WHERE {url_alias}.src = CONCAT('node/', CAST(nid AS VARCHAR)) AND {xmlsitemap_node}.pid IS NULL\n        ");
        break;
    }
    xmlsitemap_update_sitemap();
  }
}

/**
 * Implementation of hook_views_style_plugins().
 */
function xmlsitemap_node_views_style_plugins() {
  return array(
    'xmlsitemap_sitemap' => array(
      'name' => t('XML Sitemap: Sitemap'),
      'theme' => 'xmlsitemap_node_view_sitemap',
    ),
    'xmlsitemap_news' => array(
      'name' => t('XML Sitemap: News'),
      'theme' => 'xmlsitemap_node_view_news',
    ),
  );
}

/**
 * Implementation of hook_views_query_alter().
 */
function xmlsitemap_node_views_query_alter(&$query, &$view, $summary, $level) {
  switch ($view->page_type) {
    case 'xmlsitemap_news':
      $query
        ->add_field('created', 'node');
    case 'xmlsitemap_sitemap':
      $xn_join = array(
        'left' => array(
          'table' => 'node',
          'field' => 'nid',
        ),
        'right' => array(
          'field' => 'nid',
        ),
      );
      $ua_join = array(
        'left' => array(
          'table' => 'xmlsitemap_node',
          'field' => 'pid',
        ),
        'right' => array(
          'field' => 'pid',
        ),
      );
      $query
        ->add_table('xmlsitemap_node', FALSE, 1, $xn_join);
      $query
        ->add_table('url_alias', FALSE, 1, $ua_join);
      if (module_exists('comment')) {
        $query
          ->add_table('node_comment_statistics');
        $query
          ->add_field('comment_count', 'node_comment_statistics');
        $query
          ->add_field('last_comment_timestamp', 'node_comment_statistics');
        $query
          ->add_field('previous_comment', 'xmlsitemap_node');
      }
      $query
        ->add_field('type', 'node');
      $query
        ->add_field('promote', 'node');
      $query
        ->add_field('changed', 'node');
      $query
        ->add_field('previously_changed', 'xmlsitemap_node');
      $query
        ->add_field('priority_override', 'xmlsitemap_node');
      $query
        ->add_field('dst', 'url_alias', 'alias');
      break;
  }
}

/**
 * Display the nodes of a view as an XML site map.
 */
function theme_xmlsitemap_node_view_sitemap($view, $nodes, $type) {
  drupal_set_header('Content-Type: text/xml; charset=utf-8');
  $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";
  foreach ($nodes as $node) {
    $lastmod = variable_get('xmlsitemap_node_count_comments', TRUE) ? max($node->changed, $node->last_comment_timestamp) : $node->changed;
    $output .= '  <url>' . "\n";
    $output .= '    <loc>' . xmlsitemap_url('node/' . $node->nid, $node->alias, NULL, NULL, TRUE) . '</loc>' . "\n";
    $output .= '    <lastmod>' . gmdate('Y-m-d\\TH:i:s+00:00', $lastmod) . '</lastmod>' . "\n";
    $output .= '    <changefreq>' . _xmlsitemap_frequency(xmlsitemap_node_frequency($node)) . '</changefreq>' . "\n";
    $output .= '    <priority>' . number_format(xmlsitemap_node_priority($node), 1) . '</priority>' . "\n";
    $output .= '  </url>' . "\n";
  }
  $output .= '</urlset>';
  print $output;
  module_invoke_all('exit');
  exit;
}

/**
 * Display the nodes of a view as a Google News site map.
 */
function theme_xmlsitemap_node_view_news($view, $nodes, $type) {
  drupal_set_header('Content-Type: text/xml; charset=utf-8');
  $output .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
  $output .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' . "\n";
  $output .= '        xmlns:news="http://www.google.com/schemas/sitemap-news/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";
  foreach ($nodes as $node) {
    $lastmod = variable_get('xmlsitemap_node_count_comments', TRUE) ? max($node->changed, $node->last_comment_timestamp) : $node->changed;
    $output .= '  <url>' . "\n";
    $output .= '    <loc>' . xmlsitemap_url('node/' . $node->nid, $node->alias, NULL, NULL, TRUE) . '</loc>' . "\n";
    $output .= '    <lastmod>' . gmdate('Y-m-d\\TH:i:s+00:00', $lastmod) . '</lastmod>' . "\n";
    $output .= '    <changefreq>' . _xmlsitemap_frequency(xmlsitemap_node_frequency($node)) . '</changefreq>' . "\n";
    $output .= '    <priority>' . number_format(xmlsitemap_node_priority($node), 1) . '</priority>' . "\n";
    $output .= '    <news:news>' . "\n";
    $output .= '      <news:publication_date>' . gmdate('Y-m-d\\TH:i:s+00:00', $node->created) . '</news:publication_date>' . "\n";
    $output .= '    </news:news>' . "\n";
    $output .= '  </url>' . "\n";
  }
  $output .= '</urlset>';
  print $output;
  module_invoke_all('exit');
  exit;
}

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

Functions

Namesort descending Description
theme_xmlsitemap_node_view_news Display the nodes of a view as a Google News site map.
theme_xmlsitemap_node_view_sitemap Display the nodes of a view as an XML site map.
xmlsitemap_node_comment Implementation of hook_comment().
xmlsitemap_node_cron Implementation of hook_cron().
xmlsitemap_node_form_alter Implementation of hook_form_alter().
xmlsitemap_node_frequency
xmlsitemap_node_nodeapi Implmentation of hook_nodeapi().
xmlsitemap_node_node_type Implementation of hook_node_type().
xmlsitemap_node_perm Implementation of hook_perm().
xmlsitemap_node_priority Calculate the priority of a node.
xmlsitemap_node_views_query_alter Implementation of hook_views_query_alter().
xmlsitemap_node_views_style_plugins Implementation of hook_views_style_plugins().
xmlsitemap_node_xmlsitemap_links Implementation of hook_xmlsitemap_links().
_xmlsitemap_node_excludes Get array of excluded types.
_xmlsitemap_node_frontpage Get the nid of the front page node.
_xmlsitemap_node_links Get node links.
_xmlsitemap_node_submit Add submit actions to forms.