Index: vcard/vcard.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/vcard/vcard.module,v
retrieving revision 1.6
diff -u -p -r1.6 vcard.module
--- vcard/vcard.module  3 Nov 2006 23:19:43 -0000 1.6
+++ vcard/vcard.module  25 Nov 2007 11:45:44 -0000
@@ -1,15 +1,44 @@
 <?php
 // $Id: vcard.module,v 1.6 2006/11/03 23:19:43 walkah Exp $
 
+/**
+ * Implementation of hook_help()
+ */
 function vcard_help($section) {
   switch ($section) {
     case 'admin/settings/modules#description':
       return t('Allows vcard export of user data');
     case 'admin/help#vcard':
-      return t('vCard automates the exchange of personal information typically found on a traditional business card. vCard is used in applications such as Internet mail, voice mail, Web browsers, telephony applications, call centers, video conferencing, PIMs (Personal Information Managers), PDAs (Personal Data Assistants), pagers, fax, office equipment, and smart cards. vCard information goes way beyond simple text, and includes elements like pictures, company logos, live Web addresses, and so on.');
+      return t('
+        <p>
+        vCard automates the exchange of personal information typically found on a traditional business card. vCard is used in applications such as Internet mail, voice mail, Web browsers, telephony applications, call centers, video conferencing, PIMs (Personal Information Managers), PDAs (Personal Data Assistants), pagers, fax, office equipment, and smart cards. vCard information goes way beyond simple text, and includes elements like pictures, company logos, live Web addresses, and so on.
+        </p><p>
+        Web browsers and <a href="https://addons.mozilla.org/en-US/firefox/addon/2240">aggregation tools</a> can also extract and use vCard-like information when it is displayed on the page using <a href="http://microformats.org/wiki/hcard">hCard</a> - an embedded <a href="http://microformats.org">microformat</a>. 
+        To enable this in your theme, you can invoke theme_vcard($user) directly. 
+        eg, add the following to your theme template.php and your user page details will be formatted as a vcard.
+        </p>
+        <pre>
+/**
+ * Override of theme_user_profile()
+ */
+function yourtheme_user_profile($account, $fields) {
+  if (module_exists("vcard")) {
+    return theme("vcard", $account, $fields);
+  }
+  else {
+    return theme_user_profile($account, $fields);
+  }
+}
+        </pre>
+      ');
+    case 'admin/settings/vcard':
+      return t('vCard fields need to be matched to existing profile fields if the labels or wording is different.');
   }
 }
 
+/**
+ * Implementation of hook_menu()
+ */
 function vcard_menu($may_cache) {
   $items = array();
 
@@ -17,7 +46,9 @@ function vcard_menu($may_cache) {
     $items[] = array(
        'path' => 'admin/settings/vcard',
        'title' => t('vCard'),
-       'callback' => 'vcard_admin_settings',
+       'callback' => 'drupal_get_form',
+       'callback arguments' => 'vcard_admin_settings',
+       'description' => 'Configure the profile field mappings.',
        'access' => user_access('administer site configuration'),
        'type' => MENU_NORMAL_ITEM);
   }
@@ -35,54 +66,160 @@ function vcard_menu($may_cache) {
   return $items;
 }
 
+/**
+ * vCard module settings form.
+ */
 function vcard_admin_settings() {
   if (!_vcard_init()) {
     drupal_set_message(t('The PEAR package Contact_Vcard_Build (required by vcard.module) has not been installed properly, please read INSTALL.txt.'), 'error');
   }
 
-  if (module_exist('profile')) {
-    $form['field_mappings'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Field Mappings'),
-      '#description' => t('This section is only enabled if the "profiles" module is enabled. When enabled it will provide a dropdown selection box for each defined profile item of type textfield, textarea, or url.'),
+  $form['field_mappings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Field Mappings'),
+    '#description' => t('This section is only enabled if the "profiles" module is enabled. When enabled it will provide a dropdown selection box for each defined profile item of type textfield, textarea, or url.'),
+  );
+  
+  $form['auto_create'] = array(
+    '#type' => 'submit',
+    '#value' => t('Auto-create profile fields'),
+    '#description' => t("Attempt to create the normal required profile fields, if you haven't already done so'"),
+  );
+  $form['#submit']['vcard_auto_create_profile_submit'] = array(); 
+  
+  $options = array('' => 'Select a property');  
+  $options = $options + _vcard_properties();
+  foreach (_vcard_profile_fields() as $fid => $title) {
+    $form['field_mappings']['vcard_profile_'. $fid] = array(
+      '#type' => 'select',
+      '#title' => t('Property for ') . $title,
+      '#default_value' => variable_get('vcard_profile_'. $fid, ""),
+      '#options' => $options,
     );
-    $options = array('' => 'Select a property');  
-    $options = $options + _vcard_properties();
-    foreach (_vcard_profile_fields() as $fid => $title) {
-      $form['field_mappings']['vcard_profile_'. $fid] = array(
-        '#type' => 'select',
-        '#title' => t('Property for ') . $title,
-        '#default_value' => variable_get('vcard_profile_'. $fid,""),
-        '#options' => $options,
-      );
-    }
   }
 
-  return system_settings_form('vcard_admin_settings', $form);
+  return system_settings_form($form);
+}
+
+/**
+ * Add an action to auto-create a detailed contact profile with consistant
+ * names.
+ *
+ * Capture the vcard settings submission, and act on the button if pressed.
+ *  
+ * Although I could have just over-written everything, instead I take care to
+ * absorb half-profiles that have already been made, possibly with different
+ * names, and not replace anything that already works OK. This process just adds
+ * the supplimental contact vCard fields. Thus it should be safe to run as an
+ * upgrade over anything you already have.
+ */
+function vcard_auto_create_profile_submit($form_id, $form_values) {
+  if ($form_values['op'] == t('Auto-create profile fields')) {
+    drupal_set_message(t("Creating required profile fields"));
+
+    $required_fields = _vcard_properties();
+    #drupal_set_message(print_r($required_fields, 1));
+
+    // Load in all currently existing fields, to see what exists and what they are matched to so far.
+    $available_fields = array();
+    $available_fields_result = db_query('SELECT * FROM {profile_fields} ORDER BY weight ');
+    while ($field = db_fetch_object($available_fields_result)) {
+      // Index these on a version of the internal id, not the text label
+      $key = preg_replace('|^profile_|', '', $field->name);
+      if ($linked = variable_get('vcard_profile_'. $field->fid, "")) {
+        // This field is already linked to a vcard tag. Add it to the index in the right place
+        $key = $linked;
+      }
+      // There may be confusion if there are more than one field named the same. Don't do that.
+      if (! $available_fields[$key]) {
+        $available_fields[$key] = $field;
+      }
+    }
+    #drupal_set_message(print_r($available_fields, 1));
+
+    $vcard_fields = _vcard_profile_fields();
+    #drupal_set_message(print_r($vcard_fields, 1));
+
+    foreach ($required_fields as $required_field_id => $required_field_label) {
+      // Check if a field with its label is already existing
+      if (in_array($required_field_id, array_keys($available_fields))) {
+        $field_def = $available_fields[$required_field_id];
+        drupal_set_message(t("%field_label already exists as a profile field. %alt_name", array('%field_label' => $required_field_label, '%alt_name' => (($field_def->title != $required_field_label) ? " (called $field_def->title)" : ""))));
+      }
+      else {
+        // need to make it
+        drupal_set_message("Creating profile field " . $required_field_label);
+        // @see profile_field_form_submit()
+        $field_def = array(
+          'title'       => $required_field_label, 
+          'name'        => 'profile_'. $required_field_id, 
+          'explanation' => '', 
+          'category'    => 'Contact Information', 
+          'type'        => 'textfield', 
+          'weight'      => 0, 
+          'required'    => 0, 
+          'register'    => 1, 
+          'visibility'  => 2,
+        );
+        if ($required_field_id == 'url') {$field_def['type']='url';}
+        if ($required_field_id == 'birthday') {$field_def['type']='date';}
+        db_query("
+          INSERT INTO {profile_fields} 
+          (title, name, explanation, category, type, weight, required, register, visibility, autocomplete, options, page) 
+          VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, '%s', '%s')", 
+          $field_def['title'], $field_def['name'], $field_def['explanation'], $field_def['category'], $field_def['type'], $field_def['weight'], $field_def['required'], $field_def['register'], $field_def['visibility'], $field_def['autocomplete'], $field_def['options'], $field_def['page']        
+          );
+        // retrieve its new ID
+        $result = db_query('SELECT * FROM {profile_fields} WHERE name = "%s"', $field_def['name']);
+        $field_def = db_fetch_object($result);
+      }
+      // Now check the right profile field is matched up with the right vcard field
+      if ($field_def && (variable_get('vcard_profile_'. $field_def->fid, '') != $required_field_id)) {
+        drupal_set_message("Assigning profile field $field_def->fid ($field_def->title) to vcard key $required_field_id ");
+        variable_set('vcard_profile_'. $field_def->fid, $required_field_id);
+      }
+
+    }
+  }
 }
 
+/**
+ * Adds a vcard icon and link to the user page 
+ * 
+ * Implementation of hook_user()
+ */
 function vcard_user($type, &$edit, &$user) {
   switch ($type) {
     case 'view':
-    $items[] = array(
-      'value' => theme('image', drupal_get_path('module','vcard') . '/vcard.png', t('download vcard')) .' '. l(t('download vcard'), 'user/'. $user->uid .'/vcard', NULL, NULL, NULL, FALSE, TRUE),
-    );
-    return array(t('vCard') => $items);
+    // Only show the link icon if library is valid
+    if (_vcard_init()) {
+      $items[] = array(
+        'value' => theme('image', drupal_get_path('module', 'vcard') .'/vcard.png', t('download vcard')) .' '. l(t('download vcard'), 'user/'. $user->uid .'/vcard', NULL, NULL, NULL, FALSE, TRUE),
+      );
+      return array(t('vCard') => $items);
+    }
   }
 }
 
+
+/**
+ * Returns a vcard directly for download
+ */
 function vcard_fetch() {
   $account = user_load(array('uid' => arg(1)));
   $vcard = _vcard_init();
+  if (!$vcard) {
+    return "vCard dependancy error. Library unavailable.";
+  }
 
   $first = vcard_get_field('givenname', $account);
   $last = vcard_get_field('familyname', $account);
   if (!empty($first) && !empty($last)) {
-      $vcard->setName($last, $first,'','','');
+      $vcard->setName($last, $first, '', '', '');
       $vcard->setFormattedName($first .' '. $last);
   }
   else {
-      $vcard->setName($account->name, '','', '', '');
+      $vcard->setName($account->name, '', '', '', '');
       $vcard->setFormattedname($account->name);
   }
   $vcard->addEmail($account->mail);
@@ -131,17 +268,46 @@ function vcard_fetch() {
   }
 
   header('Content-type: text/x-vcard');
-  header('Content-Disposition: attachment; filename="'.$account->name.'.vcf"');    
+  header('Content-Disposition: attachment; filename="'. $account->name .'.vcf"');    
   print $vcard->fetch();
   exit;
 }
 
 /**
- * outputs a nice vcard for the user
+ * Displays user contact information decorated with hCard microformats.
+ * 
+ * Use this in your theme to replace theme_user_profile() or
+ * theme_profile_listing()
  *
- * uses hCard as the default xHTML
+ * Uses hCard as the default xHTML
+ * 
+ * @param $account user object
+ * @param $fields a structured (nested) array of profile field definitions and
+ * values. Access visibility (public/private etc) has already filtered them to
+ * the ones that should be displayed. Settings are on the profile fields
+ * 'visibility' setting
  */
-function theme_vcard($account) {
+function theme_vcard($account, $fields = NULL) {
+
+  #drupal_set_message('<pre>'. print_r($account, 1) .'</pre>');
+  #drupal_set_message('<pre>'. print_r($fields, 1) .'</pre>');
+
+  // Flatten the fields so I can access them directly by id
+  $all_fields = array();
+  foreach ($fields as $category => $items) {
+    $all_fields = array_merge($all_fields, $items);
+  }
+  // For privacy, only profile fields that have an entry in 'fields' should be displayed
+  // Respect this by only accessing values listed in $fields
+
+  foreach ($account as $var => $val) {
+    if (! $all_fields[$var]) { 
+      if (($var != 'picture') && ($var != 'name')) {
+        unset($account->$var);
+      }
+    }
+  }
+
   // photo
   if (variable_get('user_pictures', 0)) {
     if ($account->picture && file_exists($account->picture)) {
@@ -151,46 +317,49 @@ function theme_vcard($account) {
       $picture = variable_get('user_picture_default', '');
     }
     if ($picture) {
-      $photo = '<img class="photo" src="'.$picture.'" alt="photo" />';
+      $photo = '<img class="photo" src="'. $picture .'" alt="photo" />';
     }
   }
 
+  $url = vcard_get_field('url', $account);
+
   // name
   $firstname = vcard_get_field('givenname', $account);
   $lastname = vcard_get_field('familyname', $account);
-  if ($firstname && $lastname) {
-    $name = '<div class="n">';
-    $name.= '<span class="given-name">'. $firstname ."</span>\n";
-    $name.= '<span class="family-name">'. $lastname . "</span>\n";
-    $name.= '<span class="nickname">'. $account->name . "</span>\n";
-    $name.= "</div>\n";
+  if ($firstname || $lastname) {
+    $name  = '<div class="n">';
+    if ($url) {$name .= '<a class="url" href="'. $url .'">'; }
+    $name .= '<span class="given-name">'. $firstname ."</span>\n";
+    $name .= '<span class="family-name">'. $lastname ."</span>\n";
+    if ($url) {$name .= "</a>"; }
+    $name .= '(<span class="nickname">'. $account->name ."</span>)\n";
+    $name .= "</div>\n";
   }
   else {
-    $name = '<div class="fn">'. $account->name . "</div>\n";
+    $name = '<div class="fn">'. $account->name ."</div>\n";
   }
-
-  $street = vcard_get_field('street', $account);
-  $city = vcard_get_field('city', $account);
+  $street   = vcard_get_field('street', $account);
+  $city     = vcard_get_field('city', $account);
   $province = vcard_get_field('province', $account);
-  $postal = vcard_get_field('postal', $account);
-  $country = vcard_get_field('country', $account);
+  $postal   = vcard_get_field('postal', $account);
+  $country  = vcard_get_field('country', $account);
 
   if ($street || $city || $province || $postal || $country ) {
-    $address = '<div class="adr">';
-    $address.= ($street) ? '<div class="street-address">'.$street."</div>\n" : '';
-    $address.= ($city) ? '<span class="locality">'. $city ."</div>\n" : '';
-    $address.= ($province) ? '<span class="region">'. $province ."</span>\n" : '';
-    $address.= ($postal) ? '<span class="postal-code">'. $postal ."</span>\n" : '';
-    $address.= ($country) ? '<span class="country-name">'. $country ."</span>\n" : '';
-    $address.=  "</div>\n";
+    $address  = '<div class="adr">';
+    $address .= ($street) ? '<div class="street-address">'. $street ."</div>\n" : '';
+    $address .= ($city) ? '<span class="locality">'. $city ."</div>\n" : '';
+    $address .= ($province) ? '<span class="region">'. $province ."</span>\n" : '';
+    $address .= ($postal) ? '<span class="postal-code">'. $postal ."</span>\n" : '';
+    $address .= ($country) ? '<span class="country-name">'. $country ."</span>\n" : '';
+    $address .=  "</div>\n";
   }
 
   if ($telephone = vcard_get_field('telephone', $account)) {
-    $tel = '<div class="tel">'.$telephone."</div>\n";
+    $tel = '<div class="tel">'. $telephone ."</div>\n";
   }
 
-  if ($org = vcard_get_field('org', $account)) {
-    $org = '<div class="org">'.$org."</div>\n";
+  if ($org = vcard_get_field('organization', $account)) {
+    $org = '<div class="org">'. $org ."</div>\n";
   }
   
   $output .= '<div class="vcard">';
@@ -199,6 +368,7 @@ function theme_vcard($account) {
   $output .= $org;
   $output .= $address;
   $output .= $tel;
+  $output .= $fields['vCard'][0]['value'];
   $output .= '<br class="clear" />';
   $output .= "</div>\n";
   return $output;
@@ -206,16 +376,21 @@ function theme_vcard($account) {
 
 function vcard_get_field($field, $account) {
   $vcard_map = _vcard_get_map($account);
-
   return $vcard_map[$field];
 }
 
-
+/**
+ * Ensures the vcard PEAR library is available and returns the vCard builder
+ * object.
+ * 
+ * @return vCard object, null if library is not available.
+ */
 function _vcard_init() {
-  require_once 'Contact_Vcard_Build.php';
-
-  $vcard = new Contact_Vcard_Build();
-  return $vcard;
+  @include_once 'Contact_Vcard_Build.php';
+  if (function_exists('Contact_Vcard_Build')) {
+    $vcard = new Contact_Vcard_Build();
+    return $vcard;
+  }
 }
 
 function _vcard_profile_fields() {
@@ -234,7 +409,13 @@ function _vcard_properties() {
                'birthday' => t('Birthday'),
                'organization' => t('Organization'),
                'telephone' => t('Telephone'),
-               'url' => t('URL'));
+               'url' => t('URL'),
+               'street' => t('Street'),
+               'city' => t('City'),
+               'postal' => t('Postal'),
+               'province' => t('Province'),
+               'country' => t('Country'),
+               );
 }
 
 function _vcard_get_map($account) {
