Ok so I've managed this in a little bit of a clumsy way. I didn't end up using page_callback at all.
Calling the user_register_submit and validate from inside the custom form functions works, they just don't have the values, so I added the form elements manually to my form.
Because it was a custom form I was able to copy and paste the respective form elements from user_register_form. That form calls user_account_form and I ended up having to paste both of them into my custom form.
That's the general answer. Here's the specifics I ended up using.
function mycustommodule_form($form, $form_state) { ... MY CUSTOM NODE FIELDS GO HERE.... global $user;
The first note here is I've wrapped all the user-register stuff in an if function so it will only run if the user is anonymous. If the user is logged in they'll never see this.
if (user_is_anonymous($user) || $user->uid == 1) { /*Theres a lot of stuff on the user form which only appears if you're an admin. That's not really important *because as an admin you won't be using it here, but i've left it in because I'm not sure entirely how *to take it our safely.*/ $admin = user_access('administer users'); // Pass access information to the submit handler. Running an access check // inside the submit function interferes with form processing and breaks // hook_form_alter(). $form['administer_users'] = array( '#type' => 'value', '#value' => $admin, ); // If we aren't admin but already logged on, go to the user page instead. $form['#user'] = drupal_anonymous_user(); $form['#user_category'] = 'register'; $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; // Start with the default user account fields.
Here in user_register_form it calls user_account_form.
user_account_form($form, $form_state);
This partly works. Unfortunately it doesn't ever display the role selector which appears in the normal register process. I also can't seem to set the role if I mimic the array structure. So instead I just pasted the relevant code from that function.
$account = $form['#user']; $register = ($form['#user']->uid > 0 ? FALSE : TRUE); $form['#validate'][] = 'user_account_form_validate'; // Account information. $form['account'] = array( '#type' => 'container', '#weight' => -10, ); // Only show name field on registration form or user can change own username. $form['account']['name'] = array( '#type' => 'textfield', '#title' => t('Username'), '#maxlength' => USERNAME_MAX_LENGTH, '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'), '#required' => TRUE, '#attributes' => array('class' => array('username')), '#default_value' => (!$register ? $account->name : ''), '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin), '#weight' => -10, ); $form['account']['mail'] = array( '#type' => 'textfield', '#title' => t('E-mail address'), '#maxlength' => EMAIL_MAX_LENGTH, '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'), '#required' => TRUE, '#default_value' => (!$register ? $account->mail : ''), ); $form['account']['pass'] = array( '#type' => 'password_confirm', '#size' => 25, '#description' => t('To change the current user password, enter the new password in both fields.'), ); $form['account']['pass'] = array( '#type' => 'password_confirm', '#size' => 25, '#description' => t('Provide a password for the new account in both fields.'), '#required' => TRUE, ); if ($admin) { $status = isset($account->status) ? $account->status : 1; } else { $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status; } $form['account']['status'] = array( '#type' => 'radios', '#title' => t('Status'), '#default_value' => $status, '#options' => array(t('Blocked'), t('Active')), '#access' => $admin, );
This is the part which works with roles. First the user is set to authenticated.
$roles = array_map('check_plain', user_roles(TRUE)); // The disabled checkbox sub-element for the 'authenticated user' role // must be generated separately and added to the checkboxes element, // because of a limitation in Form API not supporting a single disabled // checkbox within a set of checkboxes. // @todo This should be solved more elegantly. See issue #119038. $checkbox_authenticated = array( '#type' => 'checkbox', '#title' => $roles[DRUPAL_AUTHENTICATED_RID], '#default_value' => TRUE, '#disabled' => TRUE, );
Then the role selector is displayed. Normally all the roles are shown at this stage. i.e. admin, other and my two custom roles. I only want my two custom ones so I unset the values I don't want people to select. i.e. admin and other. (authenticated is always unset).
unset($roles[DRUPAL_AUTHENTICATED_RID]); unset($roles['3']); unset($roles['6']); dpm($roles); $form['account']['roles'] = array( '#type' => 'checkboxes', '#title' => t('Roles'), '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()), '#options' => $roles, );
Finally we have the end of the user_register_account function.
// Attach field widgets, and hide the ones where the 'user_register_form' // setting is not on. $langcode = entity_language('user', $form['#user']); field_attach_form('user', $form['#user'], $form, $form_state, $langcode); foreach (field_info_instances('user', 'user') as $field_name => $instance) { if (empty($instance['settings']['user_register_form'])) { $form[$field_name]['#access'] = FALSE; } } }
I'm not sure this is a very slick way of doing it, so if anyone has any better ways of doing this, or improving what I've done I'd be really pleased to hear them.