<?php

/**
 * @file
 * Attach profile2 form to registration form according to path.
 */

// @todo Create tests
// @todo Make profile2_regpath settings exportable via CTools.

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

  $reg_paths = profile2_regpath_get_reg_paths();
  if ($reg_paths) {
    // Set menu items for each registration path.
    foreach ($reg_paths as $key => $value) {
      $path = $value->path;
      if ($profile_types = profile2_regpath_get_profiles($path)) {
        // Add profile-specific administrative 'add user' page.
        $items['admin/people/p2rp-create/' . $path] = array(
          'title' => 'Add user (' . $profile_types[0]->label . ' profile)',
          'page callback' => '_profile2_regpath_user_register',
          'page arguments' => array(
            'profiles' => $profile_types,
          ),
          'access arguments' => array('administer users'),
          'type' => MENU_LOCAL_ACTION,
          'file' => 'registration_form.inc',
        );

        // Create registration pages for each profile type.
        // We will use hook_menu_alter() to deal with the 'user/' path later.
        if ($path != 'user') {  
          $registration_path = $path . '/register';

          $items[$registration_path] = array(
            'title' => 'Create new account',
            'page callback' => '_profile2_regpath_user_register',
            'page arguments' => array(
              'profiles' => $profile_types,
            ),
            'access callback' => 'user_is_anonymous',
            'file' => 'registration_form.inc',
            'type' => MENU_LOCAL_TASK,
          );
          $items[$path] = array(
            'title' => 'Log in',
            'page callback' => '_profile2_regpath_user_login',
            'page arguments' => array(
              'profiles' => $profile_types,
            ),
            'access callback' => 'user_is_anonymous',
            'file' => 'registration_form.inc',
            'menu_name' => 'user-menu',
            'type' => MENU_CALLBACK,
          );
          $items[$path . '/login'] = array(
            'title' => 'Log in',
            'page callback' => '_profile2_regpath_user_login',
            'page arguments' => array(
              'profiles' => $profile_types,
            ),
            'access callback' => 'user_is_anonymous',
            'file' => 'registration_form.inc',
            'type' => MENU_DEFAULT_LOCAL_TASK,
          );

          $items[$path . '/password'] = array(
            'title' => 'Request new password',
            'type' => MENU_LOCAL_TASK,
            'page callback' => '_profile2_regpath_user_password',
            'page arguments' => array(
              'profiles' => $profile_types,
            ),
            'access callback' => 'user_is_anonymous',
            'file' => 'registration_form.inc',
          );
        }
      }
    }
  }
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function profile2_regpath_menu_alter(&$items) {
  // Check to see if the default 'user' path is being used with Profile2.
  if ($user_paths = profile2_regpath_get_profiles('user')) {
    // Build form at user/register using _profile2_regpath_user_register().
    $items['user/register']['page callback'] = '_profile2_regpath_user_register';
    $items['user/register']['page arguments'] = array('profiles'=>$user_paths);
    $items['user/register']['file'] = 'registration_form.inc';
    $items['user/register']['file path'] = drupal_get_path('module', 'profile2_regpath');

    return $items;
  }
}

/**
 * Implements hook_permission().
 */
function profile2_regpath_permission() {
  $permissions = array(
    'administer profile2_regpath' => array(
      'title' => t('Administer Profile2 registration paths'),
      'description' => t('Enable and configure unique registration paths per Profile2 profile type.'),
    ),
  );

  return $permissions;
}

/**
 * Implements hook_form_FORM_ID_alter() for the registration form.
 */
function profile2_regpath_form_profile2_type_form_alter(&$form, &$form_state, $form_id) {
  if (user_access('administer profile2_regpath')) {
    // Grab existing values.
    global $base_url;
    $profile_type = $form['type']['#default_value'];
    $profile_id   = profile2_regpath_get_profile_id($profile_type);
    $settings     = db_query("SELECT * FROM {profile2_regpath} WHERE profile_id = :profile_id", array(':profile_id' => $profile_id))->fetch();
    // Unserialize array of miscellaneous display options.
    if (is_object($settings)) {
      $misc = unserialize($settings->misc);
    }

    // Change description of Profile2's registration checkbox to create clear distinction.
    $form['data']['registration']['#title'] = t('Show on all user account registration forms.');
    $form['data']['registration']['#description'] = t('If selected, fields for this profile type will be added to the core user registration page and the administrative add user page.');

    // Create new form fields.
    $form['regpath'] = array(
      '#type' => 'fieldset',
      '#title' => t('Unique registration path'),
    );
    $form['regpath']['status'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable unique registration path'),
      '#default_value' => is_object($settings) && $settings->status ? $settings->status : 0,
      '#description' => t("If checked, a unique registration page utilizing this profile type's fields will be available at the specified URL."),
    );
    $form['regpath']['settings'] = array(
      '#type' => 'fieldset',
      '#title' => t('Settings'),
      '#states' => array(
        'invisible' => array(
          'input[name="status"]' => array('checked' => FALSE),
        ),
      ),
    );
    // @todo add unlimited cardinality to the path field OR change to textarea and loop through each line.
    $form['regpath']['settings']['path'] = array(
      '#type' => 'textfield',
      '#title' => t('URL path'),
      '#field_prefix' => $base_url . '/',
      '#field_suffix' => '/register',
      '#default_value' => is_object($settings) && $settings->path ? $settings->path : '',
      '#description' => t('Please enter the base URL for this registration path.
        <ul>
          <li>Menu router items for [path], [path]/register, [path]/login, and [path]/password will be generated.</li>
          <li>You may use the "user" base path to attach this profile to the default user registration form.</li>
          <li>Do not include any slashes.</li>
        </ul>'
      ),
      '#size' => 20,
    );

    // Add textfields for custom titles on the login, register, and forgot password pages.
    $form['regpath']['settings']['custom_titles'] = array(
      '#type' => 'checkbox',
      '#title' => t('Set custom page titles for the login, register, and forgot password pages.'),
      '#default_value' => is_object($settings) && isset($misc['custom_titles']) ? $misc['custom_titles'] : 0,
      '#description' => t('Enabling this option will permit you to override the default title of "User Account" on the login, register, and forgot password pages.'),
    );
    $form['regpath']['settings']['custom_titles_settings'] = array(
      '#type' => t('fieldset'),
      '#title' => t('Custom page titles'),
      '#states' => array(
        'invisible' => array(
          'input[name="custom_titles"]' => array('checked' => FALSE),
        ),
      ),
    );
    $form['regpath']['settings']['custom_titles_settings']['login_title'] = array(
      '#type' => 'textfield',
      '#title' => t('Login page title'),
      '#default_value' => is_object($settings) && isset($misc['login_title']) ? $misc['login_title'] : 'User Account',
      '#description' => t('Enter the title you want to display in the login page. The default title is <i>User Account</i>.'),
    );
    $form['regpath']['settings']['custom_titles_settings']['register_title'] = array(
      '#type' => 'textfield',
      '#title' => t('Register page title'),
      '#default_value' => is_object($settings) && isset($misc['register_title']) ? $misc['register_title'] : 'User Account',
      '#description' => t('Enter the title you want to display in the registration page. The default title is <i>User Account</i>.'),
    );
    $form['regpath']['settings']['custom_titles_settings']['password_title'] = array(
      '#type' => 'textfield',
      '#title' => t('Password page title'),
      '#default_value' => is_object($settings) && isset($misc['password_title']) ? $misc['password_title'] : 'User Account',
      '#description' => t('Enter the title you want to display in the request new password page. The default title is <i>User Account</i>.'),
    );

    // Add text fields for custom registration confirmation message.
    $form['regpath']['settings']['confirmation_display'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display a confirmation message after registration.'),
      '#default_value' => is_object($settings) && isset($misc['confirmation_display']) ? $misc['confirmation_display'] : 0,
      '#description' => t('Enabling this option display a custom confirmation message to the user after a successful registration.'),
    );
    $form['regpath']['settings']['confirmation_message'] = array(
      '#type' => 'textarea',
      '#title' => t('Confirmation Message'),
      '#default_value' => is_object($settings) && isset($misc['confirmation_message']) ? $misc['confirmation_message'] : '',
      '#description' => t('Enter the confirmation message that you would like to display.'),
      '#states' => array(
        'invisible' => array(
          'input[name="confirmation_display"]' => array('checked' => FALSE),
        ),
      ),
    );

    $form['regpath']['settings']['fieldset_wrap'] = array(
      '#type' => 'checkbox',
      '#title' => 'Wrap profile fields in a fieldset',
      '#description' => t('If checked, all profile fields for this profile type will be wrapped in a fieldset titled @label on the user registration form.', array('@label' => '')),
      '#default_value' => is_object($settings) && isset($misc['fieldset_wrap']) ? $misc['fieldset_wrap'] : 0,
    );

    // Add form fields for assigning roles during registration.
    $roles = user_roles(TRUE);
    unset($roles[DRUPAL_AUTHENTICATED_RID]);

    $form['regpath']['settings']['roles'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Assign roles during registration'),
      '#description' => t('Please select any roles that you would like to automatically assign to users registering via this registration path.'),
      '#options' => $roles,
      '#default_value' => is_object($settings) && $settings->roles ? unserialize($settings->roles) : array(),
    );

    $form['regpath']['settings']['weight'] = array(
      '#type' => 'select',
      '#title' => t('Weight'),
      '#description' => t("If multiple sets of profile fields are attached to a single registration path, they will be sorted in ascending order by weight. This will affect both the order of profile fields and the title settings."),
      '#options' => drupal_map_assoc(range(-10, 10, 1)),
      '#default_value' => is_object($settings) && $settings->weight ? $settings->weight : 0,
    );

    // Add new validate and submit handlers.
    $form['#validate'][] = 'profile2_regpath_validate_settings';
    $form['#submit'][] = 'profile2_regpath_save_settings';
  }
}

/**
 * Validate profile settings form values.
 */
function profile2_regpath_validate_settings($form, &$form_state) {
  if ($form_state['values']['status'] == 1) {
    // Validate URL tail via regex. This also tests that path is not null.
    if (profile2_regpath_url_validator(trim($form_state['values']['path'])) == FALSE) {
      form_set_error('path', 'Error, you did not enter a valid URL.');
    }
    // Ensure that URL does not contain a '/'.
    // @todo move this to profile2_regpath_url_validator.
    if (strpos($form_state['values']['path'], '/')) {
      form_set_error('path', 'Error, you cannot use a "/" in your unique path. You may specify only a single segment of the URL.');
    }
    // Check to see if another module is using the selected path.
    // We must make exceptions for '/user' and other p2rp registered paths.
    if ($form_state['values']['path'] != 'user' && $existing_item = menu_get_item($form_state['values']['path'])) {
      if ($existing_item['page_callback'] != '_profile2_regpath_user_login') {
        form_set_error('path', 'Error, that base path is already being used by another module.');
      }
    }
    // Check to see if selected path is being used by an alias.
    if ($existing_alias = drupal_lookup_path('source', $form_state['values']['path'])) {
      form_set_error('path', t('Error, that base path is already being used as an alias. Please select a different base path or remove the alias.'));
    }
  }
}

/**
 * Verifies the syntax of the given URL.
 *
 * @param string $url
 *   A string containing a URL.
 *
 * @return
 *   TRUE if the URL is in a valid format, and FALSE if it isn't.
 */
function profile2_regpath_url_validator($url) {
  $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array(
        // æ
        "&#x00E6;",
        // Æ
        "&#x00C6;",
        // ø
        "&#x00F8;",
        // Ø
        "&#x00D8;",
        // å
        "&#x00E5;",
        // Å
        "&#x00C5;",
        // ä
        "&#x00E4;",
        // Ä
        "&#x00C4;",
        // ö
        "&#x00F6;",
        // Ö
        "&#x00D6;",
        // ü
        "&#x00FC;",
        // Ü
        "&#x00DC;",
        // Ñ
        "&#x00D1;",
        // ñ
        "&#x00F1;",
      )), ENT_QUOTES, 'UTF-8');

  $LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", array(
        // ß
        "&#x00DF;",
      )), ENT_QUOTES, 'UTF-8');

  // Pattern specific to internal links.
  $internal_pattern = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\]]+)";

  $directories = "(?:\/[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'!():;*@\[\]]*)*";
  // Yes, four backslashes == a single backslash.
  $query = "(?:\/?\?([?a-z0-9" . $LINK_ICHARS . "+_|\-\.\/\\\\%=&,$'():;*@\[\]{} ]*))";
  $anchor = "(?:#[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'():;*@\[\]\/\?]*)";

  // The rest of the path for a standard URL.
  $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';

  if (preg_match($internal_pattern . $end, $url)) {
    return TRUE;
  }
}

/**
 * Helper function to save profile settings.
 */
function profile2_regpath_save_settings($form, &$form_state) {
  $profile_type = $form_state['values']['type'];
  $profile_id = profile2_regpath_get_profile_id($profile_type);

  // Define fields for db_merge.
  $fields = array(
    'path' => $form_state['values']['path'],
    'status' => $form_state['values']['status'],
    'weight' => $form_state['values']['weight'],
  );

  // Add roles if enabled.
  $fields['roles'] = serialize($form_state['values']['roles']);
  // Create array of miscellaneous display options.
  $fields['misc'] = serialize(array(
      'fieldset_wrap' => $form_state['values']['fieldset_wrap'],
      'custom_titles' => $form_state['values']['custom_titles'],
      'login_title' => $form_state['values']['login_title'],
      'register_title' => $form_state['values']['register_title'],
      'password_title' => $form_state['values']['password_title'],
      'confirmation_display' => $form_state['values']['confirmation_display'],
      'confirmation_message' => $form_state['values']['confirmation_message'],
    ));

  // Add settings to database.
  db_merge('profile2_regpath')->key(array('profile_id' => $profile_id))->fields($fields)->execute();

  // Build array of form elements to check for new values.
  $changed_check = array(
    array(
      'default' => $form['regpath']['settings']['path']['#default_value'],
      'new' => $form_state['values']['path'],
    ),
    array(
      'default' => $form['regpath']['settings']['custom_titles_settings']['login_title']['#default_value'],
      'new' => $form_state['values']['login_title'],
    ),
    array(
      'default' => $form['regpath']['settings']['custom_titles_settings']['register_title']['#default_value'],
      'new' => $form_state['values']['register_title'],
    ),
    array(
      'default' => $form['regpath']['settings']['custom_titles_settings']['password_title']['#default_value'],
      'new' => $form_state['values']['password_title'],
    ),
  );
  // Rebuild the menu if any of the following values have been changed.
  $REBUILD = FALSE;
  foreach ($changed_check as $field) {
    if ($field['default'] != $field['new'] && $REBUILD == FALSE) {
      if (menu_rebuild()) {
        drupal_set_message(t('The menu system has been rebuilt.'));
        $REBUILD = TRUE;
      }
    }
  }
}

/**
 * Implements hook_profile2_type_delete().
 */
function profile2_regpath_profile2_type_delete($type) {
  // Delete table entries for deleted profile2 profile type.
  db_delete('profile2_regpath')->condition('profile_id', $type->id)->execute();
}

/**
 * Implements hook_form_alter().
 */
function profile2_regpath_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_register_form') {
    // Get profile2 profile types from current path.
    $url = drupal_parse_url($form['#action']);
    $path = explode('/', str_replace('/register', '', $url['path']));
    $path_key = end($path);
    $profile_types = profile2_regpath_get_profiles($path_key);
    profile2_regpath_attach_profile_fields($form, $form_state, $form_id, $profile_types);
  }
}

/*
 * Attach profile fields to form.
 */
function profile2_regpath_attach_profile_fields(&$form, &$form_state, $form_id, $profile_types = NULL) {

  // Check to see if the form is being rebuilt after an AJAX request.
  // If so, we will have lost the $profile_types parameter, and need to reacquire it.
  $menu_item = menu_get_item();
  if (!$profile_types && ($menu_item['path'] == 'system/ajax' || $menu_item['path'] == 'file/ajax')) {
    $url = drupal_parse_url($form['#action']);
    $path = explode('/', str_replace('/register', '', $url['path']));
    $path_key = end($path);
    $profile_types = profile2_regpath_get_profiles($path);
  }

  if ($profile_types != NULL) {

    // Prepare variables for roles.
    $user_roles = user_roles(TRUE);
    $roles = array();

    // Attach profile(s) to user/register form.
    foreach ($profile_types as $key => $value) {
      // Get profile object.
      $type_name = (string) $profile_types[$key]->type;
      $profile_type = profile2_get_types($type_name);

      // If this profile has not already been attached.
      if (empty($form_state['profiles'][$type_name])) {
        $profile = profile_create(array('type' => $type_name));
        $form_state['profiles'][$type_name] = $profile;

        $misc = unserialize($profile_types[$key]->misc);
        if ($misc['fieldset_wrap']) {
          // Wrap each profile form in a fieldset.
          $form['profile_' . $type_name] = array(
            '#type' => 'fieldset',
            '#title' => check_plain($profile_type->label),
          );
        }
        // Set Form API #weight attribute for profile.
        $form['profile_' . $type_name]['#weight'] = $profile_type->weight;

        // Attach custom confirmation message to form for later display.
        if (isset($misc['confirmation_display'])) {
          $_SESSION['profile2_regpath']['confirmation_message'] = $misc['confirmation_message'];
        }
      }

      // Add appropriate user roles.
      $profile_roles = unserialize($value->roles);
      foreach ($profile_roles as $rid => $value) {
        // Add role to roles array if it isn't already there.
        if ($value != 0 && !array_key_exists($rid, $form['account']['roles'])) {
          $form['account']['roles'][$rid] = array(
            '#type' => 'checkbox',
            '#title' => check_plain($user_roles[$rid]),
            '#default_value' => TRUE,
            '#disabled' => (user_access('administer users') ? FALSE : TRUE),
          );
        }
      }

    }
    // Attach the profile fields via profile2.
    // @todo Add test for AJAX fields after profile2_attach_form() has been called.
    profile2_attach_form($form, $form_state);
  }
}

/**
 * Implements hook_user_insert().
 */
function profile2_regpath_user_insert(&$edit, $account, $category) {
  // Show custom confirmation message.
  if (isset($_SESSION['profile2_regpath']['confirmation_message'])) {
    drupal_set_message($_SESSION['profile2_regpath']['confirmation_message']);
    unset($_SESSION['profile2_regpath']['confirmation_message']);
  }
}

/**
 * Provides profile_id by profile machine_name.
 *
 * @param string $profile_type
 *   Machine-name of profile2 profile type.
 *
 * @return string
 *   The profile id for indicated profile type.
 */
function profile2_regpath_get_profile_id($profile_type) {
  $profile_id = db_query("SELECT id FROM {profile_type} WHERE type = :profile_type", array(':profile_type' => $profile_type))->fetchField();
  return $profile_id;
}

/**
 * Returns object containing all p2rp data.
 *
 * @param string $path
 *   (optional) path value for WHERE condition. Defaults to NULL.
 *
 * @param string $groupby
 *   (optional) field to groupby. Defaults to NULL.
 *
 * @return object
 *   An object containing all matching profile2 registration path enabled
 *   profile types.
 */
function profile2_regpath_get_profiles($path = NULL, $groupby = NULL, $pid = NULL) {
  // Get data object of all registration paths.
  $query = db_select('profile2_regpath', 'pr');
  $query->join('profile_type', 'pt', 'pr.profile_id = pt.id');
  $query->fields('pr', array('path', 'roles', 'misc', 'status'));
  $query->fields('pt', array('id', 'label', 'type'));
  if ($path) {
    $query->condition('path', $path);
  }
  if ($groupby) {
    $query->groupBy($groupby);
  }
  if ($pid) {
    $query->condition('profile_id', $pid);
  }
  $query->condition('pr.status', 1);
  $query->orderBy('pr.weight', 'ASC');
  $result = $query->execute();
  $profile_types = $result->fetchAll();

  return $profile_types;
}

/**
 * Returns object containing all p2rp registration paths.
 *
 * @return array
 *   An array containing all active registration paths & path types.
 */
function profile2_regpath_get_reg_paths() {
  $reg_paths = NULL;
  // Get data object of all registration paths.
  $query = db_select('profile2_regpath', 'pr');
  $query->fields('pr', array('path', 'misc'));
  $query->groupBy('path');
  $query->condition('pr.status', 1);
  $result = $query->execute();
  $reg_paths = $result->fetchAll();

  return $reg_paths;
}

