You are here

agreement.module in Agreement 7.2

Agreement module code - agreement.module.

Module allows the administrator to force a user role to accept an agreement before accessing any site content.

File

agreement.module
View source
<?php

/**
 * @file
 * Agreement module code - agreement.module.
 *
 * Module allows the administrator to force a user role to accept an agreement
 * before accessing any site content.
 */

/**
 * Implements hook_permission().
 */
function agreement_permission() {
  return array(
    'administer agreements' => array(
      'title' => t('Administer agreements'),
    ),
    'bypass agreement' => array(
      'title' => t('Bypass agreement'),
    ),
    'revoke own agreement' => array(
      'title' => t('Revoke own agreement'),
    ),
  );
}

/**
 * Implements hook_init().
 */
function agreement_init() {

  // If the user hasn't already agreed, redirect them to the agreement page.
  global $user;

  // Users with the bypass agreement permission are always excluded from any
  // agreement.
  if (!user_access('bypass agreement')) {
    $path = strtolower(drupal_get_path_alias(current_path()));
    $info = agreement_type_get_best_match($user, $path);
    if ($info) {

      // Save intended destination.
      if (!isset($_SESSION['agreement_destination'])) {
        if (preg_match('/^user\\/reset/i', current_path())) {
          $_SESSION['agreement_destination'] = 'change password';
        }
        else {
          $_SESSION['agreement_destination'] = current_path();
        }
      }
      drupal_goto(check_plain($info['path']));
      exit;
    }
  }
}

/**
 * Implements hook_menu().
 */
function agreement_menu() {
  $items = array();
  $items['admin/config/people/agreement'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'description' => 'Configure settings for the Agreement module.',
    'file' => 'agreement.admin.inc',
    'page callback' => 'agreement_type_overview',
    'page arguments' => array(),
    'title' => 'Agreement settings',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/people/agreement/manage'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'title' => 'Manage',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/people/agreement/manage/%agreement_type'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'file' => 'agreement.admin.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'agreement_settings_form',
      5,
    ),
    'title' => 'Manage Agreement: %agreement_type',
    'title arguments' => array(
      5,
    ),
    'title callback' => 'agreement_type_edit_title',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/people/agreement/manage/%agreement_type/edit'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'title' => 'Modify',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/people/agreement/add'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'file' => 'agreement.admin.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'agreement_settings_form',
      NULL,
    ),
    'title' => 'Add agreement',
    'type' => MENU_LOCAL_ACTION,
  );
  $items['admin/config/people/agreement/manage/%agreement_type/reset'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'file' => 'agreement.admin.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'agreement_type_reset_form',
      5,
    ),
    'title' => 'Reset',
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  $items['admin/config/people/agreement/manage/%agreement_type/delete'] = array(
    'access arguments' => array(
      'administer agreements',
    ),
    'file' => 'agreement.admin.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'agreement_type_delete_form',
      5,
    ),
    'title' => 'Remove',
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
  );
  $types = agreement_type_load(NULL, FALSE);
  foreach ($types as $name => $type) {
    $page = check_plain($type['path']);
    if (!isset($items[$page])) {
      $items[$page] = array(
        'access arguments' => array(
          'access content',
        ),
        'description' => 'The ' . $type['type'] . ' agreement page.',
        'file' => 'agreement.pages.inc',
        'page callback' => 'agreement_page',
        'page arguments' => array(
          $name,
        ),
        'title' => $type['settings']['title'],
        'type' => MENU_CALLBACK,
      );
    }
  }
  return $items;
}

/**
 * Implements hook_user_update().
 */
function agreement_user_update(&$edit, &$account, $category = NULL) {
  global $user;
  $types = agreement_type_load();
  foreach ($types as $name => $info) {

    // Do not require user to re-accept agreement if they've just changed their
    // password.
    if ($info['settings']['frequency'] === 0 && !empty($edit['pass']) && $account->uid === $user->uid) {
      agreement_agree($account, $name, 2);
    }
  }
}

/**
 * Implements hook_theme().
 */
function agreement_theme($existing, $type, $theme, $path) {
  return array(
    'agreement_page' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Implements hook_mail().
 */
function agreement_mail($key, &$message, $params) {
  switch ($key) {
    case 'notice':
      $variables = array(
        '!site-name' => variable_get('site_name', 'Drupal'),
        '!username' => format_username($params['account']),
      );
      $message['subject'] = t('!site-name: !username accepted agreement', $variables, array(
        'langcode' => $message['language']->language,
      ));
      $message['body'][] = t('The user has accepted the agreement.');
      break;
    case 'revoked':
      $variables = array(
        '!site-name' => variable_get('site_name', 'Drupal'),
        '!username' => format_username($params['account']),
      );
      $message['subject'] = t('!site-name: !username revoked acceptance of agreement', $variables, array(
        'langcode' => $message['language']->language,
      ));
      $message['body'][] = t('The user has revoked their acceptance of the agreement.');
      break;
  }
}

/**
 * Implements hook_views_api().
 */
function agreement_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'agreement'),
  );
}

/**
 * Format the agreement page.
 *
 * @ingroup themeable
 */
function theme_agreement_page(&$variables) {
  return $variables['form'];
}

/**
 * Load agreement types.
 *
 * @param string $name
 *   (Optional) An agreement type machine name to load.
 * @param bool $use_cache
 *   (Optional) Whether or not to load from cache or not.
 *
 * @return array
 *   An associative array of agreement types keyed by the agreement type ID, or
 *   a single agreement type.
 */
function agreement_type_load($name = NULL, $use_cache = TRUE) {
  $agreement_types =& drupal_static(__FUNCTION__);
  if (!$use_cache || !isset($agreement_types) || !is_array($agreement_types)) {
    $types = array();
    $query = db_select('agreement_type');
    $query
      ->fields('agreement_type')
      ->orderBy('name');
    $result = $query
      ->execute();
    if (!empty($result)) {
      $types = $result
        ->fetchAllAssoc('name', PDO::FETCH_ASSOC);

      // Unserialize the type settings.
      foreach ($types as $type_name => $type) {
        $types[$type_name]['settings'] = unserialize($types[$type_name]['settings']);
      }
    }

    // Set the type(s) into static cache.
    $agreement_types = $types;
  }
  else {
    $types = $agreement_types;
  }
  return !empty($types) && isset($name) && isset($types[$name]) ? $types[$name] : $types;
}

/**
 * Save an agreement type.
 *
 * @param array $info
 *   The agreement type info.
 * @param bool $rebuild_menu
 *   (Optional) Rebuilds the menu cache.
 *
 * @return array
 *   The agreement type that was saved after setting static cache.
 */
function agreement_type_save($info, $rebuild_menu = FALSE) {
  $new = (object) $info;
  $new->settings = serialize($new->settings);
  if (isset($new->id)) {
    drupal_write_record('agreement_type', $new, array(
      'id',
    ));
  }
  else {
    drupal_write_record('agreement_type', $new);
  }

  // When a user changes the URL of the page the menu will need to be rebuilt.
  // Submitting the form lands the user right back here. This will also reset
  // the static cache as built in hook_menu().
  if ($rebuild_menu) {
    menu_rebuild();
  }

  // Prevent multiple static cache reset when menu is rebuilt, but do reset the
  // cache when menu is not rebuilt.
  return agreement_type_load($new->name, !$rebuild_menu);
}

/**
 * Retrieve the label for the agreement type to display as the page title.
 *
 * @param array $info
 *   The agreement type info array.
 *
 * @return string
 *   The page title.
 */
function agreement_type_edit_title($info) {
  $type = '';
  if (isset($info['type'])) {
    $type = $info['type'];
  }
  return t('Manage Agreement: @type', array(
    '@type' => $type,
  ));
}

/**
 * Whether or not an agreement type exists by name.
 *
 * @param string $name
 *   The agreement type machine name.
 *
 * @return bool
 *   TRUE if the agreement type exists.
 */
function agreement_type_name_exists($name) {
  $result = db_select('agreement_type')
    ->condition('name', $name)
    ->fields('agreement_type', array(
    'name',
  ))
    ->execute()
    ->fetchField();
  return $result && $result === $name;
}

/**
 * Has the user account agreed to the agreement yet?
 *
 * @param object $account
 *   The user account.
 * @param array $info
 *   The agreement type..
 *
 * @return bool
 *   TRUE if the user has agreed.
 */
function agreement_has_agreed($account, $info) {
  $type = $info['name'];

  // Check the user account data for the agreement.
  if (isset($account->data['agreement']) && isset($account->data['agreement'][$type])) {
    return $info['settings']['frequency'] ? session_id() === $account->data['agreement'] : TRUE;
  }
  $query = db_select('agreement');
  $query
    ->fields('agreement', array(
    'agreed',
  ))
    ->condition('type', $type)
    ->condition('uid', $account->uid)
    ->range(0, 1);
  if ($info['settings']['frequency'] == 0) {

    // Must agree on every login.
    $query
      ->condition('sid', session_id());
  }
  else {

    // Must agree when frequency is set greater than zero (number of days).
    if ($info['settings']['frequency'] > 0) {
      $frequency_timestamp = time() - $info['settings']['frequency'] * 24 * 60 * 60;
    }
    else {
      $frequency_timestamp = 0;
    }
    $reset_date = isset($info['settings']['reset_date']) ? $info['settings']['reset_date'] : 0;
    $timestamp = max($reset_date, $frequency_timestamp);
    if ($timestamp > 0) {
      $query
        ->condition('agreed_date', $timestamp, '>=');
    }
  }
  return $query
    ->execute()
    ->fetchField();
}

/**
 * Agrees the user account to the agreement.
 *
 * @param object $account
 *   The user account object.
 * @param string $type
 *   The agreement type name.
 * @param int $agreed
 *   (Optional) Set the agreed value.
 */
function agreement_agree($account, $type, $agreed = 1) {
  db_delete('agreement')
    ->condition('uid', $account->uid)
    ->condition('type', $type)
    ->execute();
  $edit = (object) array(
    'uid' => $account->uid,
    'type' => $type,
    'agreed' => $agreed,
    'sid' => session_id(),
    'agreed_date' => REQUEST_TIME,
  );
  drupal_write_record('agreement', $edit);
}

/**
 * Get the best match agreement type for a user and path.
 *
 * @param object $account
 *   The user account to check.
 * @param string $path
 *   The path alias to check based on current path.
 *
 * @return bool|array
 *   The agreement type or FALSE if none found that matched the criteria.
 */
function agreement_type_get_best_match($account, $path) {
  $types = agreement_type_load();
  $default_exceptions = array(
    'user/logout',
    'admin/config/people/agreement',
    'admin/config/people/agreement/*',
    'admin/config/people/agreement/manage/*',
  );

  // Get a list of pages to never display agreement on.
  $exceptions = array_reduce($types, function (&$result, $item) {
    $result[] = $item['path'];
    return $result;
  }, $default_exceptions);
  $exception_string = implode("\n", $exceptions);
  if (drupal_match_path($path, $exception_string)) {
    return FALSE;
  }

  // Reduce the agreement types based on the agreement restricted roles.
  $account_roles = array_keys($account->roles);
  $role_types = array_reduce($types, function (&$result, $item) use ($account) {
    if (_agreement_user_has_role($account, $item['settings']['role'])) {
      $result[$item['name']] = $item;
    }
    return $result;
  }, array());

  // Try to find an agreement type that matches the path.
  $info = array_reduce($role_types, function (&$result, $item) use ($path, $account) {
    if ($result) {
      return $result;
    }
    $pattern = html_entity_decode(strtolower($item['settings']['visibility_pages']));
    $has_match = drupal_match_path($path, $pattern);
    $has_agreed = agreement_has_agreed($account, $item);
    if (0 === (int) $item['settings']['visibility_settings'] && FALSE === $has_match && !$has_agreed) {

      // An agreement type exists that matches any page.
      $result = $item;
    }
    elseif (1 === (int) $item['settings']['visibility_settings'] && $has_match && !$has_agreed) {

      // An agreement type exists that matches the current path.
      $result = $item;
    }
    return $result;
  }, FALSE);
  return $info;
}

/**
 * Internal function to get the user's "agreement status".
 *
 * @param object $uid
 *   (Optional) UID for which the status should be checked. Defaults to current
 *   user.
 * @param int $frequency
 *   (Optional) Frequency. This is here for legacy purposes.
 * @param int $reset
 *   (Optional) Whether a "reset" has occurred on the agreement.
 *
 * @return bool
 *   if agreement found in db.
 *
 * @deprecated See agreement_has_agreed().
 *
 * @internal
 */
function _agreement_status($uid = NULL, $frequency = 0, $reset = 0) {

  // If the UID is not specified, use the current user.
  if (empty($uid)) {
    global $user;
    $uid = $user->uid;
  }

  // Make sure we weren't passed some garbage as $uid.
  $uid = (int) $uid;
  $query = db_select('agreement', 'a');
  $query
    ->fields('a', array(
    'agreed',
  ))
    ->condition('a.uid', $uid, '=')
    ->condition('a.name', 'default', '=')
    ->range(0, 1);

  // Must agree on every login.
  if ($frequency == 0) {
    $query
      ->condition('a.sid', session_id(), '=');
  }
  else {

    // Must agree when frequency is set greater than zero (number of days).
    if ($frequency > 0) {
      $frequency_timestamp = time() - $frequency * 24 * 60 * 60;
    }
    else {
      $frequency_timestamp = 0;
    }
    $timestamp = max($reset, $frequency_timestamp);
    if ($timestamp > 0) {
      $query
        ->condition('agreed_date', $timestamp, '>=');
    }
  }
  return $query
    ->execute()
    ->fetchField();
}

/**
 * Agreement settings form submit callback.
 *
 * Rebuilds the menu system when an user changes the URL or page title of the
 * page.
 *
 * @param array $form
 *   The form array.
 * @param array $form_state
 *   The form state array.
 *
 * @deprecated See agreement.pages.inc
 */
function _agreement_admin_form_submit($form, &$form_state) {
  if ($form_state['values']['agreement_page_url'] !== $form['agreement_page_url']['#default_value'] || $form_state['values']['agreement_page_title'] !== $form['agreement_page_title']['#default_value']) {
    menu_rebuild();
  }

  // If they selected to force re-acceptance of the agreement for every user.
  if (!empty($form_state['values']['agreement_clear'])) {
    variable_set('agreement_reset', time());
  }
}

/**
 * Get agreement translated string constants.
 *
 * @param string $name
 *   The constant name to retrieve as a string.
 *
 * @return string
 *   Get the translated string for the agreement "constant".
 */
function agreement_get_translated_message($name) {
  $messages = _agreement_get_translated_messages();
  if ($name && isset($messages[$name])) {
    return $messages[$name];
  }
  return '';
}

/**
 * Get all translated string constants.
 *
 * @return array
 *   An array keyed by the "constant" name and value as the translated string.
 */
function _agreement_get_translated_messages() {
  $agreement_constants =& drupal_static(__FUNCTION__);
  if (!isset($agreement_constants)) {
    $agreement_constants = array(
      'AGREEMENT_PAGE_URL' => 'agreement',
      'AGREEMENT_PAGE_TITLE' => t('Our Agreement'),
      'AGREEMENT_MESSAGE_SUCCESS' => t('Thank you for accepting our agreement.'),
      'AGREEMENT_MESSAGE_FAILURE' => t('You must accept our agreement to continue.'),
      'AGREEMENT_MESSAGE_REVOKED' => t('You successfully revoked your acceptance of our agreement.'),
      'AGREEMENT_CHECKBOX_TEXT' => t('I agree.'),
      'AGREEMENT_SUBMIT_TEXT' => t('Submit'),
      'AGREEMENT_FORMAT' => filter_default_format(),
    );
  }
  return $agreement_constants;
}

/**
 * Checks if user has agreement roles.
 *
 * @param object $account
 *   The user account.
 * @param array $role_ids
 *   The role ids to check.
 *
 * @return bool
 *   TRUE if the user's roles intersect with the provided role ids.
 */
function _agreement_user_has_role($account, $role_ids) {
  $account_roles = array_keys($account->roles);
  return !empty(array_intersect($role_ids, $account_roles));
}

Functions

Namesort descending Description
agreement_agree Agrees the user account to the agreement.
agreement_get_translated_message Get agreement translated string constants.
agreement_has_agreed Has the user account agreed to the agreement yet?
agreement_init Implements hook_init().
agreement_mail Implements hook_mail().
agreement_menu Implements hook_menu().
agreement_permission Implements hook_permission().
agreement_theme Implements hook_theme().
agreement_type_edit_title Retrieve the label for the agreement type to display as the page title.
agreement_type_get_best_match Get the best match agreement type for a user and path.
agreement_type_load Load agreement types.
agreement_type_name_exists Whether or not an agreement type exists by name.
agreement_type_save Save an agreement type.
agreement_user_update Implements hook_user_update().
agreement_views_api Implements hook_views_api().
theme_agreement_page Format the agreement page.
_agreement_admin_form_submit Deprecated Agreement settings form submit callback.
_agreement_get_translated_messages Get all translated string constants.
_agreement_status Deprecated Internal function to get the user's "agreement status".
_agreement_user_has_role Checks if user has agreement roles.