You are here

blocked_ips_expire.module in Blocked IPs Expire 7

Hooks, callbacks and helper functions for the blocked_ips_expire module.

File

blocked_ips_expire.module
View source
<?php

/**
 * @file
 * Hooks, callbacks and helper functions for the blocked_ips_expire module.
 */

/* Hooks. */

/**
 * Implements hook_menu().
 */
function blocked_ips_expire_menu() {
  $items = array();

  // The blocked IPs expire preference page.
  $items['admin/config/people/blocked_ips_expire'] = array(
    'title' => 'Blocked IPs expiry',
    'description' => 'Manage how long IP addresses get blocked for by default.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      '_blocked_ips_expire_preferences',
    ),
    'access arguments' => array(
      'block IP addresses',
    ),
    'file' => 'blocked_ips_expire.admin.inc',
  );

  // A default local task, which happens to be the blocked IPs expire preference
  // page.
  $items['admin/config/people/blocked_ips_expire/config'] = array(
    'title' => 'Config',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -2,
  );

  // Assign expiry times to IP addresses in bulk.
  $items['admin/config/people/blocked_ips_expire/bulk_assign'] = array(
    'title' => 'Bulk-assign expiry times',
    'description' => 'Add expiry times to blocked IP addresses without them.',
    'type' => MENU_LOCAL_TASK,
    'weight' => -1,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      '_blocked_ips_expire_bulk_assign',
    ),
    'access arguments' => array(
      'block IP addresses',
    ),
    'file' => 'blocked_ips_expire.bulk_assign.inc',
  );
  return $items;
}

/**
 * Implements hook_cron_queue_info().
 */
function blocked_ips_expire_cron_queue_info() {
  $queues = array();

  // A queue to delete blocked IP addresses whose expiry dates have passed upon
  // cron run.
  $queues['blocked_ips_expire_cron_delete'] = array(
    'worker callback' => '_blocked_ips_expire_cron_delete',
    'time' => 60,
  );
  return $queues;
}

/**
 * Implements hook_cron().
 */
function blocked_ips_expire_cron() {
  $expired_ips = _blocked_ips_expire_get_expired();

  // If there are IPs to delete, queue them for deletion.
  if (count($expired_ips) > 0) {
    $queue = DrupalQueue::get('blocked_ips_expire_cron_delete');
    foreach ($expired_ips as $expired_ip_entry) {
      $queue
        ->createItem($expired_ip_entry);
    }
  }
}

/**
 * Implements hook_blocked_ips_expire_deleting().
 */
function blocked_ips_expire_blocked_ips_expire_deleting($ip_info) {

  // Log that a blocked IP address has been removed.
  watchdog('blocked_ips_expire', 'The IP address @ip expired on @expiry_date and has been unblocked.', array(
    '@ip' => $ip_info->ip,
    '@expiry_date' => format_date($ip_info->expiry_date),
  ));
}

/* Alter functions. */

/**
 * Implements hook_page_alter().
 */
function blocked_ips_expire_page_alter(&$page) {

  // On the list of blocked IP addresses, add an expiry date column.
  if (isset($page['content']['system_main']['system_ip_blocking_form'])) {
    $page['content']['system_main']['system_ip_blocking_table']['#header'][] = t('Expiry dates');
    foreach ($page['content']['system_main']['system_ip_blocking_table']['#rows'] as &$row) {
      $ip = _blocked_ips_expire_get_one_by_ip($row[0]);
      if ($ip) {
        $row[] = format_date($ip->expiry_date);
      }
      else {
        $row[] = t('Not set');
      }
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function blocked_ips_expire_form_alter(&$form, &$form_state, $form_id) {

  // Add a control to let a user enter an expiry date when they're blocking an
  // IP address.
  if ($form_id === 'system_ip_blocking_form') {
    $form['#submit'][] = '_blocked_ips_expire_system_ip_blocking_form';
    $default_duration = variable_get('blocked_ips_expire_default_time', '+2 years');
    $default_expiry_date = new DateTime($default_duration);
    $form['expiry_date'] = array(
      '#type' => 'date',
      '#title' => t('Expiry date'),
      '#default_value' => array(
        'year' => $default_expiry_date
          ->format('Y'),
        'month' => $default_expiry_date
          ->format('n'),
        'day' => $default_expiry_date
          ->format('j'),
      ),
      '#description' => t('Defaults to the current default, %interval.', array(
        '%interval' => $default_duration,
      )),
      '#required' => TRUE,
    );
  }
}

/**
 * Implements hook_help().
 */
function blocked_ips_expire_help($path, $arg) {
  $result = db_query("SELECT COUNT(ip) FROM {blocked_ips}");
  $number_of_rows = $result
    ->fetchField();
  switch ($path) {
    case 'admin/config/people/ip-blocking':
      return '<h3>' . t("The total number of blocked IPs is: @number_of_rows", array(
        '@number_of_rows' => $number_of_rows,
      )) . '</h3>';
  }
}

/* Callbacks. */

/**
 * Delete an IP address entry.
 */
function _blocked_ips_expire_cron_delete($expired_ip_entry) {
  if (!empty($expired_ip_entry)) {

    // Let other modules act on this event.
    module_invoke_all('blocked_ips_expire_deleting', $expired_ip_entry);

    // Now delete the IP address.
    _blocked_ips_expire_delete_ip($expired_ip_entry->iid);
  }
}

/* Batch API callbacks. */

/**
 * Implements callback_batch_operation().
 */
function _blocked_ips_expire_bulk_assign_batch_operation($new_date = '', $iid = '', &$context = array()) {

  // Set context for the finished message.
  if (!isset($context['results']['date'])) {
    $context['results']['date'] = $new_date;
  }

  // Set the expiry date for this ip.
  db_merge('blocked_ips_expire')
    ->key(array(
    'iid' => $iid,
  ))
    ->fields(array(
    'expiry_date' => $new_date,
  ))
    ->execute();
}

/**
 * Implements callback_batch_finished().
 */
function _blocked_ips_expire_bulk_assign_batch_finished($success, $results, $operations) {

  // Yay! Write the results in a message and in the log.
  if ($success) {
    $message = format_plural(count($results), 'Assigned the date @date to 1 IP address.', 'Assigned the date @date to @count IP addresses.', array(
      '@date' => format_date($results['date']),
    ));
    drupal_set_message($message);
    watchdog('blocked_ips_expire', $message, array(), WATCHDOG_INFO);
  }
  else {
    $error_operation = reset($operations);
    $message = t('An error occurred while processing %error_operation with arguments: @arguments', array(
      '%error_operation' => $error_operation[0],
      '@arguments' => print_r($error_operation[1], TRUE),
    ));
    drupal_set_message($message, 'error');
  }

  // Either way, redirect to the bulk assignment page, which should be empty.
  drupal_goto('admin/config/people/blocked_ips_expire/bulk_assign');
}

/* Form callbacks. */

/**
 * Form submission handler for system_ip_blocking_form().
 *
 * @see blocked_ips_expire_form_alter
 */
function _blocked_ips_expire_system_ip_blocking_form($form, &$form_state) {
  $expiry_date = new DateTime();
  $expiry_date
    ->setDate($form_state['values']['expiry_date']['year'], $form_state['values']['expiry_date']['month'], $form_state['values']['expiry_date']['day']);
  _blocked_ips_expire_add_ip($form_state['values']['ip'], $expiry_date
    ->format('U'));
}

/* Form element validation handlers. */

/**
 * Form element validation handler for strtotime()-compatible strings.
 *
 * @see https://php.net/manual/function.strtotime.php
 */
function _blocked_ips_expire_element_validate_strtotime($element, &$form_state) {
  if (strtotime($element['#value']) === FALSE) {
    form_error($element, t("%name must be in a format that <a href='@strtotime'>PHP's strtotime function</a> can interpret.", array(
      '%name' => $element['#title'],
      '@strtotime' => 'https://php.net/manual/function.strtotime.php',
    )));
  }
}

/**
 * Form element validation handler for strings that parse as dates.
 */
function _blocked_ips_expire_element_validate_strtotime_or_empty($element, &$form_state) {
  if (!empty($element['#value']) && strtotime($element['#value']) === FALSE) {
    form_error($element, t('%name must be a date.', array(
      '%name' => $element['#title'],
    )));
  }
}

/* Helper functions. */

/**
 * Returns IP addresses whose expiry dates have passed.
 *
 * @param bool $is_count_query
 *   Whether this is a count query, or a regular one.
 *
 * @return array|int
 *   If (bool) $is_count_query === TRUE, then returns the number of blocked IP
 *   addresses without expiry dates (an int). Otherwise, returns an array of
 *   IIDs mapped to objects with the following properties:
 *   - 'iid': The IID of the IP address.
 *   - 'ip': The IP address.
 *   - 'expiry_date': The date the IP address expired.
 */
function _blocked_ips_expire_get_expired($is_count_query = FALSE) {
  $expired = array();

  // Query for IP addresses whose expiry dates have passed.
  $query = db_select('blocked_ips', 'bi');
  $query
    ->join('blocked_ips_expire', 'bie', 'bi.iid = bie.iid');
  $query
    ->addField('bi', 'iid');
  $query
    ->addField('bi', 'ip');
  $query
    ->addField('bie', 'expiry_date');
  $query
    ->condition('expiry_date', strtotime('now'), '<');

  // If this is a count query, return the count.
  if ((bool) $is_count_query) {
    return (int) $query
      ->countQuery()
      ->execute()
      ->fetchField();
  }

  // If we get here, it was a regular query: return the results.
  $result = $query
    ->execute();
  $expired = $result
    ->fetchAllAssoc('iid');
  return $expired;
}

/**
 * Returns IP addresses that have not been assigned expiry dates.
 *
 * @param bool $is_count_query
 *   Whether this is a count query, or a regular one.
 *
 * @return array|int
 *   If (bool) $is_count_query === TRUE, then returns the number of blocked IP
 *   addresses without expiry dates (an int). Otherwise, returns an array of
 *   IIDs mapped to objects with the following properties:
 *   - 'iid': The IID of the IP address.
 *   - 'ip': The IP address.
 *   - 'expiry_date': The date the IP address expired.
 */
function _blocked_ips_expire_get_unassigned($is_count_query = FALSE) {
  $unassigned = array();

  // Query for IP addresses without expiry dates.
  $query = db_select('blocked_ips', 'bi');
  $query
    ->leftJoin('blocked_ips_expire', 'bie', 'bi.iid = bie.iid');
  $query
    ->addField('bi', 'iid');
  $query
    ->addField('bi', 'ip');
  $query
    ->addField('bie', 'expiry_date');
  $query
    ->isNull('bie.expiry_date');

  // If this is a count query, return the count.
  if ((bool) $is_count_query) {
    return (int) $query
      ->countQuery()
      ->execute()
      ->fetchField();
  }

  // If we get here, it was a regular query: return the results.
  $result = $query
    ->execute();
  $unassigned = $result
    ->fetchAllAssoc('iid');
  return $unassigned;
}

/**
 * Blocks an IP address and gives it an expiry date.
 *
 * @param string $ip
 *   The IP address to block.
 * @param string $expiry_date
 *   The date that the block should expire (i.e.: the date that the IP address
 *   should be unblocked).
 *
 * @return int
 *   The IID of the IP address that was added.
 */
function _blocked_ips_expire_add_ip($ip, $expiry_date) {
  if (empty($ip)) {
    $ip = ip_address();
  }

  // Remove spaces around $ip before adding it to database, so it operates the
  // same as system_ip_blocking_form_submit().
  $ip = trim((string) $ip);

  // Check for duplicate IP addresses. There is no constraint on the table, so
  // checking for duplicates must be done manually.
  $duplicate_ip_query = db_select('blocked_ips', 'bi');
  $duplicate_ip_query
    ->addField('bi', 'iid');
  $duplicate_ip_query
    ->condition('bi.ip', $ip);
  $iid = $duplicate_ip_query
    ->execute()
    ->fetchField();

  // If there was no duplicate IP address, add it to the database.
  if (empty($iid)) {
    $iid = db_insert('blocked_ips')
      ->fields(array(
      'ip' => (string) $ip,
    ))
      ->execute();
  }

  // Add expiry date to the expiry table.
  db_merge('blocked_ips_expire')
    ->key(array(
    'iid' => $iid,
  ))
    ->fields(array(
    'expiry_date' => (int) $expiry_date,
  ))
    ->execute();
  return $iid;
}

/**
 * Gets information about an IP address, given an IID.
 *
 * @param int $iid
 *   The IID of the IP address.
 *
 * @return object|bool
 *   Returns FALSE if an IP address with the given IID was not found, or an
 *   object with the following properties:
 *   - 'iid': The IID of the IP address.
 *   - 'ip': The IP address.
 *   - 'expiry_date': The date the IP address expired.
 */
function _blocked_ips_expire_get_one($iid) {
  $query = db_select('blocked_ips', 'bi');
  $query
    ->join('blocked_ips_expire', 'bie', 'bi.iid = bie.iid');
  $query
    ->addField('bi', 'iid');
  $query
    ->addField('bi', 'ip');
  $query
    ->addField('bie', 'expiry_date');
  $query
    ->condition('bi.iid', (int) $iid);
  $result = $query
    ->execute();
  return $result
    ->fetchObject();
}

/**
 * Gets information about an IP address, given an IP address.
 *
 * @param string $ip
 *   The IP address to look up.
 *
 * @return object|bool
 *   Returns FALSE if the given IP address was not found, or an object with the
 *   following properties:
 *   - 'iid': The IID of the IP address.
 *   - 'ip': The IP address.
 *   - 'expiry_date': The date the IP address expired.
 */
function _blocked_ips_expire_get_one_by_ip($ip) {
  $query = db_select('blocked_ips', 'bi');
  $query
    ->join('blocked_ips_expire', 'bie', 'bi.iid = bie.iid');
  $query
    ->addField('bi', 'iid');
  $query
    ->addField('bi', 'ip');
  $query
    ->addField('bie', 'expiry_date');
  $query
    ->condition('bi.ip', $ip);
  $result = $query
    ->execute();
  return $result
    ->fetchObject();
}

/**
 * Deletes an IP address from the database.
 *
 * @param int $iid
 *   The IID of the IP address.
 */
function _blocked_ips_expire_delete_ip($iid) {

  // Delete the entries from both tables.
  db_delete('blocked_ips_expire')
    ->condition('iid', (int) $iid)
    ->execute();
  db_delete('blocked_ips')
    ->condition('iid', (int) $iid)
    ->execute();
}

Functions

Namesort descending Description
blocked_ips_expire_blocked_ips_expire_deleting Implements hook_blocked_ips_expire_deleting().
blocked_ips_expire_cron Implements hook_cron().
blocked_ips_expire_cron_queue_info Implements hook_cron_queue_info().
blocked_ips_expire_form_alter Implements hook_form_alter().
blocked_ips_expire_help Implements hook_help().
blocked_ips_expire_menu Implements hook_menu().
blocked_ips_expire_page_alter Implements hook_page_alter().
_blocked_ips_expire_add_ip Blocks an IP address and gives it an expiry date.
_blocked_ips_expire_bulk_assign_batch_finished Implements callback_batch_finished().
_blocked_ips_expire_bulk_assign_batch_operation Implements callback_batch_operation().
_blocked_ips_expire_cron_delete Delete an IP address entry.
_blocked_ips_expire_delete_ip Deletes an IP address from the database.
_blocked_ips_expire_element_validate_strtotime Form element validation handler for strtotime()-compatible strings.
_blocked_ips_expire_element_validate_strtotime_or_empty Form element validation handler for strings that parse as dates.
_blocked_ips_expire_get_expired Returns IP addresses whose expiry dates have passed.
_blocked_ips_expire_get_one Gets information about an IP address, given an IID.
_blocked_ips_expire_get_one_by_ip Gets information about an IP address, given an IP address.
_blocked_ips_expire_get_unassigned Returns IP addresses that have not been assigned expiry dates.
_blocked_ips_expire_system_ip_blocking_form Form submission handler for system_ip_blocking_form().