<?php
// $Id$

/**
 * @file
 * Enables arbitrary profile fields to be mapped to a fixed data model.
 */

/**
 * Implementation of hook_help().
 */
function profile_map_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Maps profile fields to a fixed data model.');

    case 'admin/help#profile_map':
      return '
      <p>
      Profile Map enables arbitrary profile fields to be mapped to a fixed data model in the $user object so that third-party modules can easily access the profile data. The module provides callbacks when the mapped profile fields are changed, for example, to require a form to be resubmitted on an address change. Profile  Map also optionally themes the mapped profile fields to display as a single field on the user page, such as Full Name instead of two separate fields for First and Last names.
      </p>' . 
      theme('item_list', array(
        t('Full Name: first, last'),
        t('Address: street, city, state, postal, country'),
        t('Phone: country code, area code, number'),
        t('Messaging: service, userid')
      ), t('Profile Map categories:')) . '
      <h3>Developers</h3>
      <p>Profile Map adds the following data model to the $user object:</p>
      <pre>
      stdClass Object (
        [profile_map] => Array (
          [name] => Array (
            [first] => Theodore
            [last] => Williams
            [view] => Theodore Williams
          )
          [address] => Array (
            [street] => 4 Yawkey Way
            [city] => Boston
            [state] => MA
            [postal] => 02215
            [country] => United States
            [view] => 4 Yawkey Way, Boston, MA 02215
          )
          [phone] => Array (
            [countrycode] => 
            [areacode] => 999
            [number] => 999-9999
            [view] => (999) 999-9999
          )
          [messaging] => Array (
            [service] => AIM
            [username] => 400clubmember
            [view] => AIM: 400clubmember
          )
        )
      )
      </pre>';

    case 'admin/settings/profile_map':
      return t('Profile Map enables arbitrary profile fields to be mapped to a fixed data model in the $user object.', 
        array('!node_types' => l('node types', 'admin/content/types')));
  }
}

/**
 * Implementation of hook_menu().
 */
function profile_map_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/profile_map',
      'title' => 'Profile Map',
      'description' => t('Maps profile fields to a fixed data model.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'profile_map_settings',
      'type' => MENU_NORMAL_ITEM,
	    'access' => user_access('administer site configuration'), //do we *really* need yet another permission??
    );
  }
  return $items;
}

/**
 * Menu callback
 *
 * @return
 *   array of form content.
 */
function profile_map_settings() {
  $form = array();

  // Get profile fields and group by categories
  $categories[0] = '<none>';
  $result = db_query('SELECT p.fid, p.title, p.category FROM {profile_fields} p ORDER BY p.category, p.weight');
  while ($profile = db_fetch_object($result)) {
    if ($profile->category != $category) {
      if (isset($category)) {
        $categories[$profile->category] = $fields;
        unset($fields);
      }
      $category = $profile->category;
    }
    $fields[$profile->fid] = $profile->title;
  }

  // Get previous settings. We could use the standard system_settings_form but
  // it would be more cumbersome to get the profile map from vars.
  $result = db_query('SELECT fid, CONCAT_WS("_", category, field) AS field FROM {profile_map}');
  while ($profile = db_fetch_object($result)) {
    $map[$profile->field] = $profile->fid;
  }

  $form['name'] = array('#type' => 'fieldset', '#title' => t('Name profile map'));
  $form['name']['profile_map_name_first'] = array(
    '#type' => 'select',
    '#title' => t('First name'),
    '#options' => $categories,
    '#default_value' => $map['name_first'],
    '#description' => t('Select the profile field that maps to the first name.'),
    '#multiple' => FALSE,
  );
  $form['name']['profile_map_name_last'] = array(
    '#type' => 'select',
    '#title' => t('Last name'),
    '#options' => $categories,
    '#default_value' => $map['name_last'],
    '#description' => t('Select the profile field that maps to the last name.'),
    '#multiple' => FALSE,
  );
  $form['name']['profile_map_name_view'] = array(
    '#type' => 'checkbox',
    '#title' => t('Replace profile name fields?'),
    '#default_value' => variable_get('profile_map_name_view', NULL),
    '#description' => t('Replace individual profile name fields on user page with themed name map.'),
  );
  $form['name']['profile_map_name-label_view'] = array(
    '#type' => 'textfield',
    '#title' => t('Label for themed name map'),
    '#default_value' => variable_get('profile_map_name-label_view', t('Full Name')),
  );

  $form['address'] = array('#type' => 'fieldset', '#title' => t('Address profile map'));
  $form['address']['profile_map_address_street'] = array(
    '#type' => 'select',
    '#title' => t('Street'),
    '#options' => $categories,
    '#default_value' => $map['address_street'],
    '#description' => t('Select the profile field that maps to the address street.'),
    '#multiple' => FALSE,
  );
  $form['address']['profile_map_address_city'] = array(
    '#type' => 'select',
    '#title' => t('City'),
    '#options' => $categories,
    '#default_value' => $map['address_city'],
    '#description' => t('Select the profile field that maps to the address city.'),
    '#multiple' => FALSE,
  );
  $form['address']['profile_map_address_state'] = array(
    '#type' => 'select',
    '#title' => t('State'),
    '#options' => $categories,
    '#default_value' => $map['address_state'],
    '#description' => t('Select the profile field that maps to the address state.'),
    '#multiple' => FALSE,
  );
  $form['address']['profile_map_address_postal'] = array(
    '#type' => 'select',
    '#title' => t('Postal Code'),
    '#options' => $categories,
    '#default_value' => $map['address_postal'],
    '#description' => t('Select the profile field that maps to the address postal code.'),
    '#multiple' => FALSE,
  );
  $form['address']['profile_map_address_country'] = array(
    '#type' => 'select',
    '#title' => t('Country'),
    '#options' => $categories,
    '#default_value' => $map['address_country'],
    '#description' => t('Select the profile field that maps to the address postal country.'),
    '#multiple' => FALSE,
  );
  $form['address']['profile_map_address_view'] = array(
    '#type' => 'checkbox',
    '#title' => t('Replace profile address fields?'),
    '#default_value' => variable_get('profile_map_address_view', NULL),
    '#description' => t('Replace individual profile address fields on user page with themed address map.'),
  );
  $form['address']['profile_map_address-label_view'] = array(
    '#type' => 'textfield',
    '#title' => t('Label for themed name map'),
    '#default_value' => variable_get('profile_map_address-label_view', t('Address')),
  );

  $form['phone'] = array('#type' => 'fieldset', '#title' => t('Telephone profile map'));
  $form['phone']['profile_map_phone_countrycode'] = array(
    '#type' => 'select',
    '#title' => t('Country code'),
    '#options' => $categories,
    '#default_value' => $map['phone_countrycode'],
    '#description' => t('Select the profile field that maps to the phone country code.'),
    '#multiple' => FALSE,
  );
  $form['phone']['profile_map_phone_areacode'] = array(
    '#type' => 'select',
    '#title' => t('Area code'),
    '#options' => $categories,
    '#default_value' => $map['phone_areacode'],
    '#description' => t('Select the profile field that maps to the phone area code.'),
    '#multiple' => FALSE,
  );
  $form['phone']['profile_map_phone_number'] = array(
    '#type' => 'select',
    '#title' => t('Phone number'),
    '#options' => $categories,
    '#default_value' => $map['phone_number'],
    '#description' => t('Select the profile field that maps to the phone number.'),
    '#multiple' => FALSE,
  );
  $form['phone']['profile_map_phone_view'] = array(
    '#type' => 'checkbox',
    '#title' => t('Replace profile phone fields?'),
    '#default_value' => variable_get('profile_map_phone_view', NULL),
    '#description' => t('Replace individual profile phone fields on user page with themed phone map.'),
  );
  $form['phone']['profile_map_phone-label_view'] = array(
    '#type' => 'textfield',
    '#title' => t('Label for themed phone map'),
    '#default_value' => variable_get('profile_map_phone-label_view', t('Telephone Number')),
  );

  $form['messaging'] = array('#type' => 'fieldset', '#title' => t('Instant Messaging profile map'));
  $form['messaging']['profile_map_messaging_service'] = array(
    '#type' => 'select',
    '#title' => t('Messaging service'),
    '#options' => $categories,
    '#default_value' => $map['messaging_service'],
    '#description' => t('Select the profile field that maps to the messaging service.'),
    '#multiple' => FALSE,
  );
  $form['messaging']['profile_map_messaging_username'] = array(
    '#type' => 'select',
    '#title' => t('Messaging username'),
    '#options' => $categories,
    '#default_value' => $map['messaging_username'],
    '#description' => t('Select the profile field that maps to the messaging username.'),
    '#multiple' => FALSE,
  );
  $form['messaging']['profile_map_messaging_view'] = array(
    '#type' => 'checkbox',
    '#title' => t('Replace profile messaging fields?'),
    '#default_value' => variable_get('profile_map_messaging_view', NULL),
    '#description' => t('Replace individual profile messaging fields on user page with themed messaging map.'),
  );
  $form['messaging']['profile_map_messaging-label_view'] = array(
    '#type' => 'textfield',
    '#title' => t('Label for themed messaging map'),
    '#default_value' => variable_get('profile_map_messaging-label_view', t('Instant Messaging')),
  );

  $form['submit'] = array('#type' => 'submit', '#value' => 'Save Configuration');
  return $form;
}

/**
 * Validate profile map.
 *
 * @param $form_id
 *   The unique string identifying the form.
 * @param $form_values
 *   An array of values mirroring the values returned by the form
 *   when it is submitted by a user.
 */
function profile_map_settings_validate($form_id, $form_values) {
  $profile_map = $duplicate_map = array();
  foreach ($form_values as $field => $value) {
    if (preg_match('/^profile_map_(?<category>\w+)_(?<field>\w+)$/i', $field, $db)) {
      if ($db['field'] != 'view' && $value) {
        if (isset($duplicate_map[$value])) {
          form_set_error($field, t('Profile field cannot be mapped to more than 1 map field.'));
          form_set_error($duplicate_map[$value], t('Profile field cannot be mapped to more than 1 map field.'));
        }
        $duplicate_map[$value] = $field;
      }
    }
  }
}

/**
 * Save profile map.
 *
 * @param $form_id
 *   The unique string identifying the form.
 * @param $form_values
 *   An array of values mirroring the values returned by the form
 *   when it is submitted by a user.
 */
function profile_map_settings_submit($form_id, $form_values) {
  $profile_map = array();
  db_query('DELETE FROM {profile_map} WHERE 1');

  foreach ($form_values as $field => $value) {
    if (preg_match('/^profile_map_(?<category>\w+)_(?<field>\w+)$/i', $field, $db)) {
      if ($value && $db['field'] != 'view') {
        db_query('INSERT INTO {profile_map} (fid, category, field) VALUES (%d, "%s", "%s")', $value, $db['category'], $db['field']);
      }
      $profile_map[$db['category']] = $db['category'];
    }
  }

  foreach (array_keys($profile_map) as $category) {
    variable_set('profile_map_' . $category . '_view', $form_values['profile_map_' . $category . '_view']);
    variable_set('profile_map_' . $category . '-label_view', $form_values['profile_map_' . $category . '-label_view']);
  }
  drupal_set_message(t('The configuration options have been saved.'));
}

/**
 * Implementation of hook_user().
 */
function profile_map_user($type, &$edit, &$account, $category = NULL) {
  switch ($type) {
    /**
     * Monitor profile fields for change and provide callback hook to other modules
     * for $category ('name', 'address', 'messaging').
     */
    case 'submit':
      $profile_map = array();
      $edit_fields = array_keys((array)$edit);

      $result = db_query('SELECT m.category, p.name FROM {profile_map} m LEFT JOIN {profile_fields} p USING (fid)');
      while ($field = db_fetch_object($result)) {
        if (in_array($field->name, $edit_fields)) {
          if ($edit[$field->name] != $account->{$field->name}) {
            $profile_map[$field->category] = $field->category;
          }
        }
      }

      foreach (array_keys($profile_map) as $category) {
        module_invoke_all('profile_map', $category, $edit, $account);
      }
      break;

    /**
     * Load profile fields into standard profile map category structure
     */
    case 'load':
      if (isset($account->profile_map)) break;

      $profile_map = array();
      $result = db_query('SELECT m.category, m.field, p.name FROM {profile_map} m LEFT JOIN {profile_fields} p USING (fid) ORDER BY m.category');
      while ($field = db_fetch_object($result)) {
        $profile_map[$field->category][$field->field] = $account->{$field->name};
      }

      foreach (array_keys($profile_map) as $category) {
        $profile_map[$category]['view'] = theme('profile_map_' . $category, $profile_map[$category]);
      }

      $account->profile_map = $profile_map;
      break;
  }
}


/**
 * Implementation of hook_profile_alter() in user_view.
 *
 * Let modules change the returned fields - useful for personal privacy
 * controls. Since modules communicate changes by reference, we cannot use
 * module_invoke_all().
 */
function profile_map_profile_alter(&$account, &$fields) {
  $profile_map_category = $profile_map_fields = $profile_map_fields_keys = $profile_map_view = array();

  /**
   * Get profile map category and profile field name and create two arrays:
   *   profile map category => profile category theme setting
   *   profile field => profile map category
   */
  $result = db_query('SELECT m.category, p.name FROM {profile_map} m LEFT JOIN {profile_fields} p USING (fid)');
  while ($field = db_fetch_object($result)) {
    if (!isset($profile_map_category[$field->category])) {
      $profile_map_category[$field->category] = variable_get('profile_map_' . $field->category . '_view', NULL);
    }
    $profile_map_fields[$field->name] = $field->category;
  }
  $profile_map_fields_keys = array_keys($profile_map_fields);

  /**
   * If there is at least one themed map field, iterate through profile fields.
   * If there is a match to a mapped field, unset the user field and set the 
   * mapped category to the user category.
   */
  if (array_sum($profile_map_category)) { //variable_get = 1
    foreach ($fields as $user_category => $user_fields) {
      foreach ($user_fields as $field => $values) {
        if (in_array($field, $profile_map_fields_keys)) {
          if ($profile_map_category[$profile_map_fields[$field]]) {
            $profile_map_category[$profile_map_fields[$field]] = $user_category;
            unset($fields[$user_category][$field]);
          }
        }
      }
    }

    /**
     * Build profile map view array and merge with $fields[$user_category]
     */
    foreach ($profile_map_category as $category => $user_category) {
      if (strlen($account->profile_map[$category]['view'])) {
        $profile_map_view['profile_map-' . $category] = array(
          'title' => variable_get('profile_map_' . $category . '-label_view', NULL),
          'value' => $account->profile_map[$category]['view'],
          'class' => 'profile_map-' . $category
        );
      }
    }
    $fields[$user_category] = array_merge($profile_map_view, (array)$fields[$user_category]);
  }
}

/**
 * Sample profile_map callback handler that writes change to log.
 */
function profile_map_profile_map($category, &$edit, &$account) {
  watchdog('Profile Map', t('User %user changed %category.', array(
    '%user' => $account->name,
    '%category' => $category
  )));
}

/**
 * Theme function for name.
 */
function theme_profile_map_name($fields) {
  return $fields['first'] . ' ' . $fields['last'];
}

/**
 * Theme function for address.
 */
function theme_profile_map_address($fields) {
  $street = $fields['street'] ? $fields['street'] . '<br />' : '';
  $city = $fields['city'] ? $fields['city'] . ', ' : '';
  $country = in_array($fields['country'], array('', 'US', 'United States')) ? '' : '<br />' . $fields['country'];

  return $street . $city . $fields['state'] . ' ' . $fields['postal'] . $country;
}

/**
 * Theme function for phone.
 */
function theme_profile_map_phone($fields) {
  $country = $fields['countrycode'] ? '+' . $fields['countrycode'] : '';
  $area = $fields['areacode'] ? '(' . $fields['areacode'] . ') ' : '';

  return $country . $area . $fields['number'];
}

/**
 * Theme function for messaging.
 */
function theme_profile_map_messaging($fields) {
  return $fields['service'] ? 
    $fields['service'] . ': ' . $fields['username'] : 
    '';
}
