You are here

archive.module in Archive 5

Same filename and directory in other branches
  1. 6 archive.module
  2. 7.2 archive.module
  3. 7 archive.module

File

archive.module
View source
<?php

/**
 * Implementation of hook_help().
 */
function archive_help($section) {
  switch ($section) {
    case 'admin/help#archive':
      $output = '<p>' . t('The archive module provides a suggested menu item, which shows a page allowing content to be filtered by date and node type. The date selectors allow visitors to view content published in any given year, month or day. The node type selectors allow visitors to view content of all node types or only specific ones.') . '</p>';
      $output .= t('<p>You can</p>
<ul>
<li>view your <a href="!archive">archive page</a>.</li>
<li><a href="!admin-menu">enable or disable the archive menu item</a>.</li>
</ul>
', array(
        '!archive' => url('archive'),
        '!admin-menu' => url('admin/menu'),
      ));
      $output .= '<p>' . t('For more information please read the configuration and customization handbook <a href="http://drupal.org/handbook/modules/archive/">Archive page</a>.') . '</p>';
      return $output;
  }
}

/**
 * Implementation of hook_menu().
 */
function archive_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'archive',
      'access' => user_access('access content'),
      'callback' => 'archive_page',
      'description' => t('Browse the archives'),
      'type' => MENU_SUGGESTED_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/archive',
      'title' => t('Archive Settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'archive_admin_settings',
      ),
      'description' => t('Configure node types available for archive browsing.'),
      'type' => MENU_NORMAL_ITEM,
    );
  }
  else {
    drupal_add_css(drupal_get_path('module', 'archive') . '/archive.css');
  }
  return $items;
}
function archive_page($type = 'all', $year = NULL, $month = NULL, $day = NULL) {
  include_once 'archive.inc';
  return _archive_page($type, $year, $month, $day);
}
function archive_admin_settings() {
  $types = node_get_types();
  $final_types = array();
  foreach ($types as $key => $value) {
    $final_types[$key] = $value->name;
  }
  $form['archive_type_filters'] = array(
    '#type' => 'checkboxes',
    '#title' => t('What nodes are available as filters'),
    '#default_value' => variable_get('archive_type_filters', array_keys($final_types)),
    '#options' => $final_types,
    '#description' => t('Whichever node type you choose here will be available as a filter to the user.'),
  );
  return system_settings_form($form);
}

/**
 * Check if given year is valid for a Drupal archive.
 * Max/min values defined by limitations in POSIX
 * timestamp.
 *
 * @param $year
 *    The year to check
 * @return
 *    TRUE or FALSE
 */
function _archive_validate_year($year) {
  return 1970 <= $year && $year <= 2038;
}

/**
 * Check if given month is valid.
 *
 * @param $month
 *    The month to check
 * @return
 *    TRUE or FALSE
 */
function _archive_validate_month($month) {
  return 1 <= $month && $month <= 12;
}

/**
 * Check if given year, month and date combination
 * is valid for a Drupal archive.
 *
 * @param $year
 *    The year to check
 * @param $month
 *    The month to check
 * @param $day
 *    The day to check
 * @return
 *    TRUE or FALSE
 */
function _archive_validate_day($year, $month, $day) {
  if (_archive_validate_month($month) && _archive_validate_year($year)) {

    // Number of days for that month
    $last = gmdate('t', gmmktime(0, 0, 0, $month, 1, $year));
    return 1 <= $day && $day <= $last;
  }
  else {
    return FALSE;
  }
}

/**
 * Check if given node type is valid for a Drupal archive
 *
 * @param $type
 *   A string representing the node-type currently being displayed
 * @return
 *    TRUE or FALSE
 */
function _archive_validate_type($type) {
  $types = variable_get('archive_type_filters', array());
  foreach ($types as $key => $value) {
    if (!$value) {
      unset($types[$key]);
    }
    else {
      $types[$key] = 0;
    }
  }
  return in_array($type, array_keys($types));
}

/**
 * Generate an archive URL based on the $y, $m and $d
 * provided, falling back on the $date properties if
 * an invalid date is specified.
 *
 * Validation checking included for $date properties,
 * because those could be zero, if not defined from the
 * URL.
 *
 * @param $type
 *   A string representing the node-type currently being displayed
 * @param $y
 *    The year to use if valid
 * @param $m
 *    The month to use if valid
 * @param $d
 *    The day to use if valid
 * @return
 *    A string with the generated archive URL
 */
function _archive_url($type, $y = 0, $m = 0, $d = 0) {
  $url = 'archive';
  if (_archive_validate_type($type)) {
    $url .= '/' . $type;
  }
  else {
    $url .= '/all';
  }
  if (_archive_validate_year($y)) {
    $url .= '/' . $y;
    if (_archive_validate_month($m)) {
      $url .= '/' . $m;
      if (_archive_validate_day($y, $m, $d)) {
        $url .= '/' . $d;
      }
    }
  }
  return $url;
}

/**
 * Determine timezone to use for the dates (from format_date)
 *
 * @return
 *   Timezone offset to use in time operations
 */
function _archive_get_timezone() {
  global $user;
  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
    return $user->timezone;
  }
  else {
    return variable_get('date_default_timezone', 0);
  }
}

/**
 * Parses the current URL and populates an archive
 * date object with the selected date information.
 *
 * @param $year
 *   Number of year
 * @param $month
 *   Number of month
 * @param $day
 *   Number of day
 * @return
 *   A date object with GMT date values and a timezone value
 */
function _archive_date($type, $year = NULL, $month = NULL, $day = NULL) {
  $date = (object) array(
    'tz' => _archive_get_timezone(),
  );
  $date->year = 0;
  $date->month = 0;
  $date->day = 0;
  if (_archive_validate_year($year)) {
    $date->year = $year;
    if (_archive_validate_month($month)) {
      $date->month = $month;
      if (_archive_validate_day($year, $month, $day)) {
        $date->day = $day;
      }
    }
  }
  $post_counts = _archive_post_count($type, $date);
  $date->years = $post_counts['years'];
  ksort($date->years);
  $date->months = $post_counts['months'];
  ksort($date->months);
  $date->days = $post_counts['days'];
  ksort($date->days);
  $date->next_month_days = $post_counts['next_month_days'];
  $date->prev_month_days = $post_counts['prev_month_days'];
  return $date;
}

/**
 * Returns the count of nodes per day/month/year
 *
 * @param $type
 *   A string representing the node-type currently being displayed
 * @param $date
 *   A date object obtained from _archive_date()
 * @return
 *   An array, indexed by date and with post counts as values.
 */
function _archive_post_count($type, $date) {
  $final_types = _archive_types_sql_string($type);
  $node_query = db_query(db_rewrite_sql('SELECT n.created FROM {node} n WHERE n.status = 1 ' . $final_types));
  $with_posts = array(
    'years' => array(),
    'months' => array(),
    'days' => array(),
    'next_month_days' => array(),
    'prev_month_days' => array(),
  );
  $next_month = $date->month == 12 ? 1 : $date->month + 1;
  $next_year = $date->month == 12 ? $date->year + 1 : $date->year;
  $prev_month = $date->month == 1 ? 12 : $date->month - 1;
  $prev_year = $date->month == 1 ? $date->year - 1 : $date->year;
  while ($o = db_fetch_object($node_query)) {
    list($year, $month, $day) = explode(' ', format_date($o->created, 'custom', 'Y n j'));

    // Check for current month.
    $with_posts['years'][$year] = array_key_exists($year, $with_posts['years']) ? $with_posts['years'][$year] + 1 : 1;
    if ($date->year && $year == $date->year) {
      $with_posts['months'][$month] = array_key_exists($month, $with_posts['months']) ? $with_posts['months'][$month] + 1 : 1;
      if ($date->month && $month == $date->month) {
        $with_posts['days'][$day] = array_key_exists($day, $with_posts['days']) ? $with_posts['days'][$day] + 1 : 1;
      }
    }

    // Check for next month.
    if ($year == $next_year && $month == $next_month) {
      if (isset($with_posts['next_month_days'][$day])) {
        $with_posts['next_month_days'][$day]++;
      }
      else {
        $with_posts['next_month_days'][$day] = 1;
      }
    }

    // Check for previous month.
    if ($year == $prev_year && $month == $prev_month) {
      if (isset($with_posts['prev_month_days'][$day])) {
        $with_posts['prev_month_days'][$day]++;
      }
      else {
        $with_posts['prev_month_days'][$day] = 1;
      }
    }
  }
  return $with_posts;
}

/**
 * Builds a SQL statement to check that the appropriate
 * node types are being returned 
 *
 * @param $type
 *   A string representing the node-type currently being displayed
 * @return
 *    A SQL string
 */
function _archive_types_sql_string($type) {

  // Validate type and specify node types to include
  $final_types = '';
  if (_archive_validate_type($type) && $type != 'all') {
    $final_types = $type;
  }
  else {
    $types = variable_get('archive_type_filters', array());

    // If no checkboxes selected
    if (!array_key_exists('0', $types)) {
      foreach ($types as $key => $value) {
        if (!$value) {
          unset($types[$key]);
        }
      }
      $final_types = join(array_keys($types), '", "');
    }
  }
  if (strlen($final_types) > 0) {
    $final_types = 'AND n.type IN ("' . $final_types . '") ';
  }
  return $final_types;
}

/**
 * Returns a single month as a calendar grid
 * TODO: take the archive logic out to allow better theme-overloading of this
 *       Pass days with post numbers to this function and have this function
 *       only create the calendar
 * 
 */
function theme_archive_block_calendar($timestamp) {
  $the_date = explode(' ', format_date($timestamp, 'custom', 'F Y n t'));
  $title = $the_date[0] . ' ' . $the_date[1];
  $year = $the_date[1];
  $month = $the_date[2];
  $num_days = (int) $the_date[3];

  // Get the number of days for the previous month for adding those dates
  // to the block calendar. Store the month/year for the previous month
  // so that we can find and link to archive pages in those extra days.
  // We don't need to get days for the next month as we'll never need to fit
  // the entire month into the block.
  $prev_month = $month == 1 ? 12 : $month - 1;
  $prev_year = $month == 1 ? $year - 1 : $year;
  $num_days_prev = cal_days_in_month(CAL_GREGORIAN, $month - 1, $year);
  $next_month = $month == 12 ? 1 : $month + 1;
  $next_year = $month == 12 ? $year + 1 : $year;
  $date = _archive_date('all', $year, $month);
  $month_title = '';
  if ($date->months[$month]) {
    $month_title = l($title, _archive_url('all', $year, $month), array(
      'title' => format_plural($date->months[format_date($timestamp, 'custom', 'n')], '1 post', '@count posts'),
    ));
  }
  else {
    $month_title = $title;
  }

  // Build the week starting with
  $first_day_of_week = variable_get('date_first_day', 0);
  $week = array(
    t('Sun'),
    t('Mon'),
    t('Tue'),
    t('Wed'),
    t('Thu'),
    t('Fri'),
    t('Sat'),
  );
  $day_headers = array();
  for ($i = $first_day_of_week; $i < $first_day_of_week + 7; $i++) {
    $day_headers[] = $week[$i % 7];
  }

  // this is inefficient but necessary to determine the start of the month:
  // We use gmdate the first time so that we don't apply the user's timezone twice
  list($start_year, $start_month) = explode(' ', format_date($timestamp, 'custom', 'Y m'));
  $start = gmmktime(0, 0, 0, (int) $start_month, 1, (int) $start_year);
  $weekday = gmdate('w', $start) - $first_day_of_week;
  $days_row = array();

  // From http://www.theadminzone.com/forums/showthread.php?t=17490
  for ($i = 1 - $weekday; $i <= ceil(($weekday + $num_days) / 7) * 7; $i++) {
    if ($i > 0) {
      if (array_key_exists($i, $date->days)) {
        $days_row[] = l($i, _archive_url('all', $year, $month, $i), array(
          'title' => format_plural($date->days[$i], '1 post', '@count posts'),
        ));
      }
      else {
        if ($i <= $num_days) {
          $days_row[] = $i;
        }
        else {
          $curr_cal_date = $i - $num_days;
          if (isset($date->next_month_days[$curr_cal_date])) {
            $data = l($curr_cal_date, _archive_url('all', $next_year, $next_month, $curr_cal_date), array(
              'title' => format_plural($date->next_month_days[$curr_cal_date], '1 post', '@count posts'),
            ));
          }
          else {
            $data = $curr_cal_date;
          }
          $days_row[] = array(
            'data' => $data,
            'class' => 'out-of-month',
          );
        }
      }

      // Add the week table row we just created if we've finished it
      if (($i + $weekday) % 7 == 0) {
        $rows[] = $days_row;
        $days_row = array();
      }
    }
    else {
      $curr_cal_date = $num_days_prev + $i;
      if (isset($date->prev_month_days[$curr_cal_date])) {
        $data = l($curr_cal_date, _archive_url('all', $prev_year, $prev_month, $curr_cal_date), array(
          'title' => format_plural($date->prev_month_days[$curr_cal_date], '1 post', '@count posts'),
        ));
      }
      else {
        $data = $curr_cal_date;
      }
      $days_row[] = array(
        'data' => $data,
        'class' => 'out-of-month',
      );
    }
  }
  return theme('table', $day_headers, $rows, array(), $month_title);
}

/**
 * Implementation of hook_block().
 */
function archive_block($op = 'list', $delta = 0, $edit = array()) {
  if ($op == 'list') {
    $blocks = array(
      array(
        'info' => t('Archive calendar'),
      ),
      array(
        'info' => t('Archive months'),
      ),
    );
    return $blocks;
  }
  else {
    if ($op == 'view') {
      switch ($delta) {
        case 0:
          drupal_add_css(drupal_get_path('module', 'archive') . '/archive.css');
          $block = array(
            'subject' => t('Archives'),
            'content' => theme('archive_block_calendar', time()),
          );
          break;
        case 1:
          $block = array(
            'subject' => t('Archives'),
            'content' => theme('archive_month_list', archive_month_list()),
          );
          break;
      }
      return $block;
    }
    else {
      if ($op == 'configure') {
        if ($delta == 1) {
          $form['archive_block_months'] = array(
            '#type' => 'select',
            '#title' => t('Maximum number of months to display'),
            '#default_value' => variable_get('archive_block_months', 6),
            '#options' => array(
              1 => 1,
              2 => 2,
              3 => 3,
              4 => 4,
              5 => 5,
              6 => 6,
              7 => 7,
              8 => 8,
              9 => 9,
              10 => 10,
            ),
          );
          return $form;
        }
      }
      else {
        if ($op == 'save') {
          if ($delta == 1) {
            variable_set('archive_block_months', $edit['archive_block_months']);
          }
        }
      }
    }
  }
}

/**
 * Return a list of links suitable to be passed to theme_links() that represent
 * months containing posts. Limit the number of past months to 6 and then have a
 * link to the general archives page.
 */
function archive_month_list() {
  $node_query = db_query(db_rewrite_sql('SELECT n.created FROM {node} n WHERE n.status = 1 AND DATE(FROM_UNIXTIME(n.created)) BETWEEN DATE_SUB(CURDATE(), INTERVAL 6 MONTH) AND CURDATE() ORDER BY n.created DESC'));
  $months = array();
  $total_months = 0;
  $max_months = variable_get('archive_block_months', 6);
  while ($node = db_fetch_object($node_query)) {
    list($month_name, $year, $month) = explode(' ', format_date($node->created, 'custom', 'F Y n'));
    if (isset($months[$year][$month])) {
      $months[$year][$month]['count']++;
    }
    else {
      if ($total_months++ < $max_months) {

        // Limit total months to 10
        $months[$year][$month] = array(
          'count' => 1,
          'month_name' => $month_name,
        );
      }
    }
  }
  return $months;
}
function theme_archive_month_list($list_items) {

  // Sort by year then by month
  krsort($list_items);
  foreach ($list_items as $year => $v) {
    krsort($list_items[$year]);
  }
  $links = array();
  foreach ($list_items as $year => $months) {
    foreach ($months as $month => $data) {
      $links[] = l(t($data['month_name'] . ' ' . $year), 'archive/all/' . $year . '/' . $month, array(
        'title' => format_plural($data['count'], '1 post', '@count posts'),
      ));
    }
  }
  if (count($links) == variable_get('archive_block_months', 6)) {
    $links[] = '<div class="more-link">' . l(t('all'), 'archive', array(
      'title' => t('Browse all of the archives'),
    )) . '</div>';
  }
  return theme('item_list', $links);
}

Functions

Namesort descending Description
archive_admin_settings
archive_block Implementation of hook_block().
archive_help Implementation of hook_help().
archive_menu Implementation of hook_menu().
archive_month_list Return a list of links suitable to be passed to theme_links() that represent months containing posts. Limit the number of past months to 6 and then have a link to the general archives page.
archive_page
theme_archive_block_calendar Returns a single month as a calendar grid TODO: take the archive logic out to allow better theme-overloading of this Pass days with post numbers to this function and have this function only create the calendar
theme_archive_month_list
_archive_date Parses the current URL and populates an archive date object with the selected date information.
_archive_get_timezone Determine timezone to use for the dates (from format_date)
_archive_post_count Returns the count of nodes per day/month/year
_archive_types_sql_string Builds a SQL statement to check that the appropriate node types are being returned
_archive_url Generate an archive URL based on the $y, $m and $d provided, falling back on the $date properties if an invalid date is specified.
_archive_validate_day Check if given year, month and date combination is valid for a Drupal archive.
_archive_validate_month Check if given month is valid.
_archive_validate_type Check if given node type is valid for a Drupal archive
_archive_validate_year Check if given year is valid for a Drupal archive. Max/min values defined by limitations in POSIX timestamp.