Extending core Profile module

This is a request for comments / interest in extending the core Profile module itself in Drupal 6. It is a bit off topic for the group, but this group is closest of any to the topic.

Background
Leading to a core module hack, so here is the full background.

After developing in Drupal 5 and 6 over the last two years, I just hit complex profile requirements in 3 different projects at the same time. Each have different access requirements and field requirements.

Attempt One: Profile module
From past experiences, aka none, I turned to the profile module. Instantly I realized that this could not cut it. No way to extend it, and no way to store multiple data.

Attempt Two: Try to extend the core Profile module
Using a series of menu_alters, I successfully extended the Profile module, but it felt like a house of cards. New page callbacks extended the fields allowed, but data from these new fields had to be saved and removed prior to the Profile save action, then all loads had to be done manually. All up, about 1/4 of the Profile node functionality was redirected into my own functions to overwrite about 20 lines of code. Strange bugs randomly appeared, so I stopped before wasting to much time.

Attempt Three: Nodes
Initially put off by the size of the issue queue, I gave content profile a go. Overall, the results were pretty good, but the node behavior felt strange when handling user profile data. The profile content types were in the general create content links, general navigation within the node system, ability to have multiple profile nodes yet the interface appeared to only handle one, etc. So back into a series of menu_alters and programmatic controlled content types. Things looked good.

A menu alter intercepted the "node/add/TYPE" into "user/%user/edit/TYPE" with an access check to hide after a node was created belonging to the user. A custom new router entry to "user/%user_load_if_has_node_of_type/edit/TYPE" wrapped the node edit page nicely. Things looked good until realizing that custom menu aliases would be required for every node to automate the interaction between other module and the new paths. This by itself was not a killer blow, but when the access controller on one site failed using the router design, this approach meet the same fate as the first few attempts.

Attempt Four: Hack the Profile module
After spending more than a week on the first three phases, 2 days of programming had allowed me to extend the module to do pretty much everything that I required. This included creating the following extension:

  • Keyed selection list - "safe key | Human readable option" single + multiple selections | select list, radios, checkboxes
  • Node references - filter by content type | single + multiple | select list, radios, checkboxes
  • User references - filter by role | single + multiple | select list, radios, checkboxes
  • Taxonomy - defined vocab per field | single / multiple as defined by vocab
  • Name field - title, first name, surname
  • Address field - physical + postal (11 fields all up)
  • Children field - first name, last name, dob | multiple storage via external table (requires views integration but that's another story)

With the exception of the children fields, all data was saved in the profile_values table, serialized as required.

Goals
To normalize the functionality to be as compatible and flexible as possible with the core profile module as possible. It is not aimed at the user node-like functionality, but to enhance the core user-based profile data.

Finally, the reason for the post!
To find any other interested developers to help direct and extend the hack. The best solution would be to develop a new module that extends Profile, but Profile is very difficult to do this.

Currently, the extensions are done via a very simple field api. This is the core date functionality wrapped into the new api.

<?php

/**
* Fields are presented by three callback functions.
* These need to be in the loaded path.
*
* Required - Provides the input / view of the field
*
* profile_field_FIELD_KEY_form
* profile_field_FIELD_KEY_view
*
* You can specify a different function name by using the
* 'form callback' and 'view callback' keys.
*
* Optional - defines the options field on the administer profile field page.
*
* profile_field_FIELD_KEY_options
*
* No default is generated, so by default this element is not shown.
*
* The way that options field is stored is defaulted to be serialized.
* This can be turned off using the flag.
*
* 'options serialize' => FALSE
*
* Only the core selection list uses this.
*
* Handling the submitted data
*
* All of the fields here store the data in the {profile_values} table.
* As such, all complex or multi-value data needs to be serialized first.
* This is done by defining the 'save callback' and 'load callbacks'.
*
* This could be used to link data to another database table.
*
* 'save callback' => 'profile_serialize',
* 'load callback' => 'profile_unserialize',
*
* The title field is the name of the field to present to the user.
*
* The explanation field is the text to append to the field specific explanation
* to explain how to use the form element.
*/
function profile_profile_fields() {
 
$fields = array(
   
// cut
   
'url' => array(
     
'title' => t('URL'),
    ),
   
'date' => array(
     
'title' => t('date'),
     
'save callback' => 'profile_serialize',
     
'load callback' => 'profile_unserialize',
    ),
   
// cut
 
);

  return
$fields;
}

function
profile_field_date_form($field, $value, $field_info) {
 
$form_field = array('#type' => 'date',
   
'#title' => check_plain($field->title),
   
'#default_value' => $value,
   
'#description' => _profile_form_explanation($field),
   
'#required' => $field->required,
  );
  return
$form_field;
}

function
profile_field_date_view($field, $value, $field_info, $browse) {
 
$format = substr(variable_get('date_format_short', 'm/d/Y - H:i'), 0, 5);
 
// Note: Avoid PHP's date() because it does not handle dates before
  // 1970 on Windows. This would make the date field useless for e.g.
  // birthdays.
 
$replace = array(
   
'd' => sprintf('%02d', $value['day']),
   
'j' => $value['day'],
   
'm' => sprintf('%02d', $value['month']),
   
'M' => map_month($value['month']),
   
'Y' => $value['year'],
   
'H:i' => NULL,
   
'g:ia' => NULL,
  );
  return
strtr($format, $replace);
}

// The save / load handlers - requires the field / account info in more complex types
function profile_serialize($field, $user, $value, $edit) {
  return
serialize($value);
}

function
profile_unserialize($field, $user, $value) {
  return
unserialize($value);
}

?>

Any feedback welcome, I'll supply a zip of the hacked module after things are tidied up a bit + testing is completed, in a week or so.

Login to post comments