You are here

regcode.module in Registration codes 8

Main functionality and hooks of regcode module.

File

regcode.module
View source
<?php

/**
 * @file
 * Main functionality and hooks of regcode module.
 */
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormStateInterface;

// Define validation error codes.
const REGCODE_VALIDITY_NOTEXISTING = 0;
const REGCODE_VALIDITY_NOTAVAILABLE = 1;
const REGCODE_VALIDITY_TAKEN = 2;
const REGCODE_VALIDITY_EXPIRED = 3;
const REGCODE_MODE_REPLACE = 0;
const REGCODE_MODE_SKIP = 1;
const REGCODE_CLEAN_TRUNCATE = 1;
const REGCODE_CLEAN_INACTIVE = 3;
const REGCODE_CLEAN_EXPIRED = 4;

/**
 * Implements hook_help().
 */
function regcode_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'regcode.admin_list':
      $output = '<p>' . t('View and manage created registration codes.') . '</p>';
      break;
    case 'regcode.admin_create':
      $output = '<p>' . t('Create manually or generate new registration codes.') . '</p>';
      break;
    case 'regcode.admin_manage':
      $output = '<p>' . t('Provides bulk management features for created registration codes.') . '</p>';
      break;
    case 'regcode.admin_settings':
      $output = '<p>' . t('Configure the registration code module.') . '</p>';
      break;
  }
  return $output;
}

/**
 * Implements hook_entity_extra_field_info().
 */
function regcode_entity_extra_field_info() {
  $extra = [];
  $config = \Drupal::config('regcode.settings');
  $extra['user']['user']['form']['regcode'] = [
    'label' => $config
      ->get('regcode_field_title'),
    'description' => $config
      ->get('regcode_field_description'),
    'weight' => 10,
    'visible' => TRUE,
  ];
  return $extra;
}

/**
 * Implements hook_form_FORM_ID_alter() for user_register_form.
 */
function regcode_form_user_register_form_alter(&$form, FormStateInterface $form_state) {

  // Only display the regcode field when it's attached to the form display.
  if ($form_state
    ->get('form_display')
    ->getComponent('regcode')) {
    $config = \Drupal::config('regcode.settings');
    $form['regcode'] = [
      '#type' => 'textfield',
      '#title' => Html::escape($config
        ->get('regcode_field_title')),
      '#description' => Html::escape($config
        ->get('regcode_field_description')),
      '#required' => !($config
        ->get('regcode_optional') || \Drupal::currentUser()
        ->hasPermission('administer users')),
      '#element_validate' => [
        'regcode_code_element_validate',
      ],
    ];
    $form['actions']['submit']['#submit'][] = 'regcode_user_register_form_submit_handler';

    // Capture the code from the URL, if present, and inject it into the
    // registration form.
    if (\Drupal::request()->query
      ->has('regcode')) {

      // The Form API can handle potentially unsafe characters as long as they
      // are not printed directly. This code gets trimmed in
      // regcode_code_validate().
      $form['regcode']['#value'] = \Drupal::request()->query
        ->get('regcode');
      $form['regcode']['#description'] = NULL;
      $form['regcode']['#disabled'] = TRUE;
    }
  }
}

/**
 * Validates the content of the code-field on user registration.
 */
function regcode_code_element_validate(array &$element, FormStateInterface $form_state) {
  $regcode = $form_state
    ->getValue('regcode');
  if (!empty($regcode)) {
    $code = regcode_code_validate($regcode);
    if (!is_object($code)) {
      $form_state
        ->setError($element, regcode_errormsg($code));
      \Drupal::logger('regcode')
        ->warning('User entered invalid registration code (@code)', [
        '@code' => $regcode,
      ]);
    }
  }
}

/**
 * Updates data for a regcode in the database.
 */
function regcode_user_register_form_submit_handler(array &$form, FormStateInterface $form_state) {
  $regcode = $form_state
    ->getValue('regcode');
  $uid = $form_state
    ->getValue('uid');
  if (!empty($regcode) && !empty($uid)) {
    $code = regcode_code_consume($regcode, $uid);
    if ($code) {
      $username = $form_state
        ->getValue('name');
      \Drupal::logger('regcode')
        ->info(t('The registration code "@code" was used by user @user with UID @uid.', [
        '@code' => $regcode,
        '@uid' => $uid,
        '@user' => $username,
      ]));
    }
    else {
      \Drupal::logger('regcode')
        ->error(t('Error checking code @code.', [
        '@code' => $code,
      ]));
    }
  }
}

/**
 * Returns text message requested by given identifier/constant.
 *
 * @param int $err
 *   The error message code.
 *
 * @return string
 *   The text of the message.
 */
function regcode_errormsg($err) {
  $messages = [
    REGCODE_VALIDITY_NOTEXISTING => t('Registration code does not exist'),
    REGCODE_VALIDITY_NOTAVAILABLE => t('Registration code is not available'),
    REGCODE_VALIDITY_TAKEN => t('Registration code has already been used'),
    REGCODE_VALIDITY_EXPIRED => t('Registration code has expired'),
  ];
  return isset($messages[$err]) ? $messages[$err] : FALSE;
}

/**
 * Loads a registration code.
 *
 * @param int|null $id
 *   The database primary key (rid).
 * @param array $conditions
 *   An associative array containing the search conditions.
 *
 * @return object|bool
 *   The regcode object or FALSE if the code does not exist.
 *
 * @example
 *   regcode_load(1231); // Loads the regcode with rid=1231
 *   regcode_load(NULL, ['code'=>'foobar']); // Loads the "foobar" regcode
 */
function regcode_load_single($id, $conditions = []) {

  // Build the query.
  $query = \Drupal::database()
    ->select('regcode')
    ->fields('regcode', [
    'rid',
    'uid',
    'created',
    'lastused',
    'begins',
    'expires',
    'code',
    'is_active',
    'maxuses',
    'uses',
  ])
    ->range(0, 1);

  // Allow mixed search parameters.
  if (!empty($id)) {
    $query
      ->condition('rid', $id);
  }
  else {
    foreach ($conditions as $field => $value) {
      $query
        ->condition($field, $value);
    }
  }

  // Run the query and grab a single regcode.
  $regcode = $query
    ->execute()
    ->fetchObject();
  if (!$regcode) {
    return FALSE;
  }

  // Entity loaders expect arrays of objects. entity_load() and
  // this function both invoke the hook below.
  $reg_codes = [
    $regcode->rid => $regcode,
  ];
  \Drupal::moduleHandler()
    ->invokeAll('regcode_load', [
    $reg_codes,
  ]);
  return $reg_codes[$regcode->rid];
}

/**
 * Validates a regcode.
 *
 * @param string $regcode
 *   The regcode alphanumeric code.
 *
 * @return bool|int|object
 *   An error code, or the loaded regcode.
 */
function regcode_code_validate($regcode) {

  // Load the code.
  $code = regcode_load_single(NULL, [
    'code' => trim($regcode),
  ]);

  // Check validity.
  if ($code === FALSE) {
    return REGCODE_VALIDITY_NOTEXISTING;
  }
  if ($code->uses >= $code->maxuses && $code->maxuses !== '0') {
    return REGCODE_VALIDITY_TAKEN;
  }
  if (!$code->is_active) {
    return REGCODE_VALIDITY_NOTAVAILABLE;
  }
  if (!empty($code->begins) && $code->begins > \Drupal::time()
    ->getRequestTime()) {
    return REGCODE_VALIDITY_NOTAVAILABLE;
  }
  if (!empty($code->expires) && $code->expires < \Drupal::time()
    ->getRequestTime()) {
    return REGCODE_VALIDITY_EXPIRED;
  }
  return $code;
}

/**
 * Consumes a regcode and attribute it to a user.
 *
 * @param string $regcode
 *   The registration code.
 * @param int $account_id
 *   Optional user id to assign the given code to.
 *
 * @return mixed
 *   An error code, or TRUE if the code was assigned successfully.
 */
function regcode_code_consume($regcode, $account_id) {
  $code = regcode_code_validate($regcode);

  // Check the code validated, otherwise return the error code.
  if (!is_object($code)) {
    return $code;
  }
  $code->uses++;

  // Mark the code inactive if it's used up.
  $active = 1;
  if ($code->maxuses != 0 && $code->uses >= $code->maxuses) {
    $active = 0;
  }

  // Use the code.
  \Drupal::database()
    ->update('regcode')
    ->fields([
    'uses' => $code->uses,
    'lastused' => \Drupal::time()
      ->getRequestTime(),
    'uid' => $account_id,
    'is_active' => $active,
  ])
    ->condition('rid', $code->rid)
    ->execute();

  // Trigger the regcode_used hook.
  $account = \Drupal::service('entity_type.manager')
    ->getStorage('user')
    ->load($account_id);
  $account->regcode = $code;
  foreach (\Drupal::moduleHandler()
    ->getImplementations('regcode_used') as $module) {
    $hook = $module . '_regcode_used';
    $hook($code, $account);
  }
  return $code;
}

/**
 * Saves code in the database and calls the regcode_presave hook.
 *
 * @param object $code
 *   A code object (required fields are code, begins, expires, is_active, and
 *   maxuses.
 * @param int $action
 *   Action to perform when saving the code.
 *
 * @return bool
 *   The regcode ID if the code was saved. Otherwise FALSE.
 */
function regcode_save($code, $action = REGCODE_MODE_REPLACE) {

  // Sanity check.
  if (empty($code) || empty($code->code)) {
    return FALSE;
  }

  // Trigger the regcode_save hook.
  foreach (\Drupal::moduleHandler()
    ->getImplementations('regcode_presave') as $module) {
    $hook = $module . '_regcode_presave';
    $hook($code);
  }

  // Insert mode.
  if ($action == REGCODE_MODE_REPLACE) {
    \Drupal::database()
      ->delete('regcode')
      ->condition('code', $code->code)
      ->execute();
  }

  // Insert.
  $rid = \Drupal::database()
    ->insert('regcode')
    ->fields([
    'created' => \Drupal::time()
      ->getRequestTime(),
    'begins' => empty($code->begins) ? NULL : (int) $code->begins,
    'expires' => empty($code->expires) ? NULL : (int) $code->expires,
    'code' => Html::escape($code->code),
    'is_active' => isset($code->is_active) ? $code->is_active : 1,
    'maxuses' => isset($code->maxuses) ? (int) $code->maxuses : 1,
  ])
    ->execute();
  return $rid;
}

/**
 * Deletes regcode codes.
 *
 * @param int $op
 *   The operation ID.
 *
 * @return bool|object|int
 *   The number of deleted rows or FALSE if nothing happened or TRUE if tables
 *   were empty.
 */
function regcode_clean($op) {
  $result = FALSE;
  switch ($op) {
    case REGCODE_CLEAN_TRUNCATE:
      \Drupal::database()
        ->truncate('regcode')
        ->execute();
      $result = TRUE;
      break;
    case REGCODE_CLEAN_EXPIRED:
      $result = \Drupal::database()
        ->delete('regcode')
        ->condition('expires', \Drupal::time()
        ->getRequestTime(), '<')
        ->execute();
      break;
    case REGCODE_CLEAN_INACTIVE:
      $result = \Drupal::database()
        ->delete('regcode')
        ->condition('is_active', 0)
        ->execute();
      break;
  }
  return $result;
}

/**
 * Generates a code.
 */
function regcode_generate($length, $output, $case) {
  static $seeded = FALSE;

  // Possible seeds.
  $outputs['alpha'] = 'abcdefghijklmnopqrstuvwqyz';
  $outputs['numeric'] = '0123456789';
  $outputs['alphanum'] = 'abcdefghijklmnopqrstuvwqyz0123456789';
  $outputs['hexadec'] = '0123456789abcdef';

  // Choose seed.
  if (isset($outputs[$output])) {
    $output = $outputs[$output];
  }

  // Seed generator (only do this once per invocation).
  if (!$seeded) {
    list($usec, $sec) = explode(' ', microtime());
    $seed = (double) $sec + (double) $usec * 100000;
    mt_srand($seed);
    $seeded = TRUE;
  }

  // Generate.
  $str = '';
  $output_count = strlen($output);
  for ($i = 0; $length > $i; $i++) {
    $str .= $output[mt_rand(0, $output_count - 1)];
  }
  if ($case) {
    $str = strtoupper($str);
  }
  return $str;
}

/**
 * Regcode delete action.
 */
function regcode_delete_action(&$object, $context = []) {
  \Drupal::database()
    ->delete('regcode')
    ->condition('rid', $object->rid)
    ->execute();
}

/**
 * Regcode activate action.
 */
function regcode_activate_action(&$object, $context = []) {
  \Drupal::database()
    ->update('regcode')
    ->fields([
    'is_active' => 1,
  ])
    ->condition('rid', $object->rid)
    ->execute();
}

/**
 * Regcode deactivate action.
 */
function regcode_deactivate_action(&$object, $context = []) {
  \Drupal::database()
    ->update('regcode')
    ->fields([
    'is_active' => 0,
  ])
    ->condition('rid', $object->rid)
    ->execute();
}

/**
 * Gets a list of terms from the registration code vocabulary.
 */
function regcode_get_vocab_terms() {
  $tree = \Drupal::service('entity_type.manager')
    ->getStorage("taxonomy_term")
    ->loadTree(\Drupal::config('regcode.settings')
    ->get('regcode_vocabulary'));
  $terms = [];
  foreach ($tree as $term) {
    $terms[$term->tid] = $term->name;
  }
  return $terms;
}

Functions

Namesort descending Description
regcode_activate_action Regcode activate action.
regcode_clean Deletes regcode codes.
regcode_code_consume Consumes a regcode and attribute it to a user.
regcode_code_element_validate Validates the content of the code-field on user registration.
regcode_code_validate Validates a regcode.
regcode_deactivate_action Regcode deactivate action.
regcode_delete_action Regcode delete action.
regcode_entity_extra_field_info Implements hook_entity_extra_field_info().
regcode_errormsg Returns text message requested by given identifier/constant.
regcode_form_user_register_form_alter Implements hook_form_FORM_ID_alter() for user_register_form.
regcode_generate Generates a code.
regcode_get_vocab_terms Gets a list of terms from the registration code vocabulary.
regcode_help Implements hook_help().
regcode_load_single Loads a registration code.
regcode_save Saves code in the database and calls the regcode_presave hook.
regcode_user_register_form_submit_handler Updates data for a regcode in the database.

Constants