You are here

profile2.module in Profile 2 7

Same filename and directory in other branches
  1. 7.2 profile2.module

Support for configurable user profiles.

File

profile2.module
View source
<?php

/**
 * @file
 * Support for configurable user profiles.
 */

/**
 * Implements hook_entity_info().
 */
function profile2_entity_info() {
  $return = array(
    'profile2' => array(
      'label' => t('Profile'),
      'plural label' => t('Profiles'),
      'description' => t('Profile2 user profiles.'),
      'entity class' => 'Profile',
      'controller class' => 'EntityAPIController',
      'base table' => 'profile',
      'fieldable' => TRUE,
      'view modes' => array(
        'account' => array(
          'label' => t('User account'),
          'custom settings' => FALSE,
        ),
      ),
      'entity keys' => array(
        'id' => 'pid',
        'bundle' => 'type',
        'label' => 'label',
      ),
      'bundles' => array(),
      'bundle keys' => array(
        'bundle' => 'type',
      ),
      'label callback' => 'entity_class_label',
      'uri callback' => 'entity_class_uri',
      'access callback' => 'profile2_access',
      'module' => 'profile2',
      'metadata controller class' => 'Profile2MetadataController',
    ),
  );

  // Add bundle info but bypass entity_load() as we cannot use it here.
  $types = db_select('profile_type', 'p')
    ->fields('p')
    ->execute()
    ->fetchAllAssoc('type');
  foreach ($types as $type => $info) {
    $return['profile2']['bundles'][$type] = array(
      'label' => $info->label,
      'admin' => array(
        'path' => 'admin/structure/profiles/manage/%profile2_type',
        'real path' => 'admin/structure/profiles/manage/' . $type,
        'bundle argument' => 4,
        'access arguments' => array(
          'administer profiles',
        ),
      ),
    );
  }

  // Support entity cache module.
  if (module_exists('entitycache')) {
    $return['profile2']['field cache'] = FALSE;
    $return['profile2']['entity cache'] = TRUE;
  }
  $return['profile2_type'] = array(
    'label' => t('Profile type'),
    'plural label' => t('Profile types'),
    'description' => t('Profiles types of Profile2 user profiles.'),
    'entity class' => 'ProfileType',
    'controller class' => 'EntityAPIControllerExportable',
    'base table' => 'profile_type',
    'fieldable' => FALSE,
    'bundle of' => 'profile2',
    'exportable' => TRUE,
    'entity keys' => array(
      'id' => 'id',
      'name' => 'type',
      'label' => 'label',
    ),
    'access callback' => 'profile2_type_access',
    'module' => 'profile2',
    // Enable the entity API's admin UI.
    'admin ui' => array(
      'path' => 'admin/structure/profiles',
      'file' => 'profile2.admin.inc',
      'controller class' => 'Profile2TypeUIController',
    ),
  );
  return $return;
}

/**
 * Menu argument loader; Load a profile type by string.
 *
 * @param $type
 *   The machine-readable name of a profile type to load.
 * @return
 *   A profile type array or FALSE if $type does not exist.
 */
function profile2_type_load($type) {
  return profile2_get_types($type);
}

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

  // Define page which provides form to generate profiles using
  // Devel generate module.
  if (module_exists('devel_generate')) {
    $items['admin/config/development/generate/profile2'] = array(
      'title' => 'Generate profiles',
      'description' => 'Generate a given number of profiles for users. Optionally override current user profiles.',
      'access arguments' => array(
        'administer profiles',
      ),
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'profile2_generate_form',
      ),
      'file' => 'profile2.devel.inc',
    );
  }
  $items['user/%profile2_by_uid/%/delete'] = array(
    'title' => 'Delete',
    'description' => 'Delete Profile of User.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'profile2_delete_confirm_form',
      1,
    ),
    'load arguments' => array(
      2,
    ),
    'access callback' => 'profile2_access',
    'access arguments' => array(
      'delete',
      1,
    ),
    'file' => 'profile2.delete.inc',
  );
  return $items;
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * Adds a local task to the profiles admin page to jump to permissions for profiles.
 */
function profile2_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  if ($root_path == 'admin/structure/profiles') {
    $item = menu_get_item('admin/people/permissions');
    if ($item['access']) {
      $item['title'] = t('Manage permissions');
      if (module_exists('filter_perms')) {
        $item['href'] .= '/module-profile2';
      }
      else {
        $item['localized_options']['fragment'] = 'module-profile2';
      }
      $item['weight'] = 3;
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
  }
}

/**
 * Implements hook_permission().
 */
function profile2_permission() {
  $permissions = array(
    'administer profile types' => array(
      'title' => t('Administer profile types'),
      'description' => t('Create and delete fields on user profiles, and set their permissions.'),
    ),
    'administer profiles' => array(
      'title' => t('Administer profiles'),
      'description' => t('Edit and view all user profiles.'),
    ),
  );

  // Generate per profile type permissions.
  foreach (profile2_get_types() as $type) {
    $type_name = check_plain($type->type);
    $permissions += array(
      "edit own {$type_name} profile" => array(
        'title' => t('%type_name: Edit own profile', array(
          '%type_name' => $type
            ->getTranslation('label'),
        )),
      ),
      "edit any {$type_name} profile" => array(
        'title' => t('%type_name: Edit any profile', array(
          '%type_name' => $type
            ->getTranslation('label'),
        )),
      ),
      "view own {$type_name} profile" => array(
        'title' => t('%type_name: View own profile', array(
          '%type_name' => $type
            ->getTranslation('label'),
        )),
      ),
      "view any {$type_name} profile" => array(
        'title' => t('%type_name: View any profile', array(
          '%type_name' => $type
            ->getTranslation('label'),
        )),
      ),
      "delete own {$type_name} profile" => array(
        'title' => t('%type_name: Delete own profile', array(
          '%type_name' => $type
            ->getTranslation('label'),
        )),
      ),
    );
  }
  return $permissions;
}

/**
 * Gets an array of all profile types, keyed by the type name.
 *
 * @param $type_name
 *   If set, the type with the given name is returned.
 * @return ProfileType[]
 *   Depending whether $type isset, an array of profile types or a single one.
 */
function profile2_get_types($type_name = NULL) {
  $types = entity_load_multiple_by_name('profile2_type', isset($type_name) ? array(
    $type_name,
  ) : FALSE);
  return isset($type_name) ? reset($types) : $types;
}

/**
 * Fetch a profile object.
 *
 * @param $pid
 *   Integer specifying the profile id.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   A fully-loaded $profile object or FALSE if it cannot be loaded.
 *
 * @see profile2_load_multiple()
 */
function profile2_load($pid, $reset = FALSE) {
  $profiles = profile2_load_multiple(array(
    $pid,
  ), array(), $reset);
  return reset($profiles);
}

/**
 * Load multiple profiles based on certain conditions.
 *
 * @param $pids
 *   An array of profile IDs.
 * @param $conditions
 *   An array of conditions to match against the {profile} table.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   An array of profile objects, indexed by pid.
 *
 * @see entity_load()
 * @see profile2_load()
 * @see profile2_load_by_user()
 */
function profile2_load_multiple($pids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('profile2', $pids, $conditions, $reset);
}

/**
 * Fetch profiles by account.
 *
 * @param $account
 *   The user account to load profiles for, or its uid.
 * @param $type_name
 *   To load a single profile, pass the type name of the profile to load.
 * @return
 *   Either a single profile or an array of profiles keyed by profile type.
 *
 * @see profile2_load_multiple()
 * @see profile2_profile2_delete()
 * @see Profile::save()
 */
function profile2_load_by_user($account, $type_name = NULL) {

  // Use a separate query to determine all profile ids per user and cache them.
  // That way we can look up profiles by id and benefit from the static cache
  // of the entity loader.
  $cache =& drupal_static(__FUNCTION__, array());
  $uid = is_object($account) ? $account->uid : $account;
  if (!isset($cache[$uid])) {
    $cache[$uid] = db_select('profile', 'p')
      ->fields('p', array(
      'type',
      'pid',
    ))
      ->condition('uid', $uid)
      ->execute()
      ->fetchAllKeyed();
  }
  if (isset($type_name)) {
    return isset($cache[$uid][$type_name]) ? profile2_load($cache[$uid][$type_name]) : FALSE;
  }

  // Return an array containing profiles keyed by profile type.
  return $cache[$uid] ? array_combine(array_keys($cache[$uid]), profile2_load_multiple($cache[$uid])) : $cache[$uid];
}

/**
 * Implements hook_profile2_delete().
 */
function profile2_profile2_delete($profile) {

  // Clear the static cache from profile2_load_by_user().
  $cache =& drupal_static('profile2_load_by_user', array());
  unset($cache[$profile->uid][$profile->type]);
}

/**
 * Deletes a profile.
 */
function profile2_delete(Profile $profile) {
  $profile
    ->delete();
}

/**
 * Delete multiple profiles.
 *
 * @param $pids
 *   An array of profile IDs.
 */
function profile2_delete_multiple(array $pids) {
  entity_get_controller('profile2')
    ->delete($pids);
}

/**
 * Implements hook_user_delete().
 */
function profile2_user_delete($account) {
  foreach (profile2_load_by_user($account) as $profile) {
    profile2_delete($profile);
  }
}

/**
 * Create a new profile object.
 */
function profile2_create(array $values) {
  return new Profile($values);
}

/**
 * Deprecated. Use profile2_create().
 */
function profile_create(array $values) {
  return new Profile($values);
}

/**
 * Saves a profile to the database.
 *
 * @param $profile
 *   The profile object.
 */
function profile2_save(Profile $profile) {
  return $profile
    ->save();
}

/**
 * Saves a profile type to the db.
 */
function profile2_type_save(ProfileType $type) {
  $type
    ->save();
}

/**
 * Deletes a profile type from.
 */
function profile2_type_delete(ProfileType $type) {
  $type
    ->delete();
}

/**
 * Implements hook_profile2_type_insert().
 */
function profile2_profile2_type_insert(ProfileType $type) {

  // Do not directly issue menu rebuilds here to avoid potentially multiple
  // rebuilds. Instead, let menu_get_item() issue the rebuild on the next page.
  variable_set('menu_rebuild_needed', TRUE);
}

/**
 * Implements hook_profile2_type_update().
 */
function profile2_profile2_type_update(ProfileType $type) {

  // @see profile2_profile2_type_insert()
  variable_set('menu_rebuild_needed', TRUE);
}

/**
 * Implements hook_profile2_type_delete()
 */
function profile2_profile2_type_delete(ProfileType $type) {

  // Delete all profiles of this type but only if this is not a revert.
  if (!$type
    ->hasStatus(ENTITY_IN_CODE)) {
    $pids = array_keys(profile2_load_multiple(FALSE, array(
      'type' => $type->type,
    )));
    if ($pids) {
      profile2_delete_multiple($pids);
    }

    // @see profile2_profile2_type_insert()
    variable_set('menu_rebuild_needed', TRUE);
  }
}

/**
 * Implements hook_user_view().
 */
function profile2_user_view($account, $view_mode, $langcode) {
  foreach (profile2_get_types() as $type => $profile_type) {
    if ($profile_type->userView && ($profile = profile2_load_by_user($account, $type))) {
      if (profile2_access('view', $profile)) {
        $account->content['profile_' . $type] = array(
          '#type' => 'user_profile_category',
          '#title' => $profile_type
            ->getTranslation('label'),
          '#prefix' => '<a id="profile-' . $profile->type . '"></a>',
        );
        $account->content['profile_' . $type]['view'] = $profile
          ->view($view_mode);
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for the user edit form.
 *
 * @see profile2_form_validate_handler
 * @see profile2_form_submit_handler
 */
function profile2_form_user_profile_form_alter(&$form, &$form_state) {
  global $user;
  if (($type = profile2_get_types($form['#user_category'])) && $type->userCategory) {
    if (empty($form_state['profiles'])) {
      $profile = profile2_load_by_user($form['#user'], $form['#user_category']);
      if (empty($profile)) {
        $profile = profile2_create(array(
          'type' => $form['#user_category'],
          'uid' => $form['#user']->uid,
        ));
        $profile->is_new = TRUE;
      }
      $form_state['profiles'][$profile->type] = $profile;
      if (user_access('administer profiles') || $user->uid === $profile->uid && user_access("delete own {$profile->type} profile")) {
        $str_button_value = t('Delete profile');
      }
    }
    if (empty($profile->is_new) && !empty($str_button_value)) {
      $form['actions']['delete'] = array(
        '#type' => 'submit',
        '#value' => $str_button_value,
        '#weight' => 45,
        '#limit_validation_errors' => array(),
        '#submit' => array(
          'profile2_form_submit_own_delete',
        ),
      );
    }
    profile2_attach_form($form, $form_state);
  }
}

/**
 * Profile form submit handler for the delete button.
 */
function profile2_form_submit_own_delete($form, &$form_state) {
  $profile = $form_state['profiles'][$form['#user_category']];
  if (isset($profile) && is_object($profile)) {
    $form_state['redirect'] = 'user/' . $profile->uid . '/' . $form['#user_category'] . '/delete';
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for the registration form.
 */
function profile2_form_user_register_form_alter(&$form, &$form_state) {
  $profile_types = profile2_get_types();
  if (!empty($profile_types)) {
    foreach ($profile_types as $type_name => $profile_type) {
      if (!empty($profile_type->data['registration'])) {
        if (empty($form_state['profiles'][$type_name])) {
          $form_state['profiles'][$type_name] = profile2_create(array(
            'type' => $type_name,
          ));
        }
      }
    }
  }

  // If we have profiles to attach to the registration form - then do it.
  if (!empty($form_state['profiles'])) {
    profile2_attach_form($form, $form_state);

    // Wrap each profile form in a fieldset.
    foreach ($form_state['profiles'] as $type_name => $profile) {
      $form['profile_' . $type_name] += array(
        '#type' => 'fieldset',
        '#title' => check_plain($profile_types[$type_name]
          ->getTranslation('label')),
      );
    }
  }
}

/**
 * Attaches the profile forms of the profiles set in
 * $form_state['profiles'].
 *
 * Modules may alter the profile2 entity form regardless to which form it is
 * attached by making use of hook_form_profile2_form_alter().
 *
 * @param $form
 *   The form to which to attach the profile2 form. For each profile the form
 *   is added to @code $form['profile_' . $profile->type] @endcode. This helper
 *   also adds in a validation and a submit handler caring for the attached
 *   profile forms.
 *
 * @see hook_form_profile2_form_alter()
 * @see profile2_form_validate_handler()
 * @see profile2_form_submit_handler()
 */
function profile2_attach_form(&$form, &$form_state) {
  foreach ($form_state['profiles'] as $type => $profile) {
    $form['profile_' . $profile->type]['#tree'] = TRUE;
    $form['profile_' . $profile->type]['#parents'] = array(
      'profile_' . $profile->type,
    );
    field_attach_form('profile2', $profile, $form['profile_' . $profile->type], $form_state);
    if (user_access('administer profile types')) {
      if (count(field_info_instances('profile2', $profile->type)) == 0) {
        $form['profile_' . $profile->type]['message'] = array(
          '#markup' => t('No fields have been associated with this profile type. Go to the <a href="!url">Profile types</a> page to add some fields.', array(
            '!url' => url('admin/structure/profiles'),
          )),
        );
      }
    }

    // Make sure we don't have duplicate pre render callbacks.
    $form['profile_' . $profile->type]['#pre_render'] = array_unique($form['profile_' . $profile->type]['#pre_render']);

    // Provide a central place for modules to alter the profile forms, but
    // skip that in case the caller cares about invoking the hooks.
    // @see profile2_form().
    if (!isset($form_state['profile2_skip_hook'])) {
      $hooks[] = 'form_profile2_edit_' . $type . '_form';
      $hooks[] = 'form_profile2_form';
      drupal_alter($hooks, $form, $form_state);
    }
  }
  $form['#validate'][] = 'profile2_form_validate_handler';

  // Default name of user registry form callback.
  $register_submit_callback = 'user_register_submit';

  // LoginToBoggan module replaces default user_register_submit() callback
  // with his own. So if this module enabled we need to track his callback
  // instead one that comes from the User module.
  if (module_exists('logintoboggan')) {
    $register_submit_callback = 'logintoboggan_user_register_submit';
  }

  // Search for key of user register submit callback.
  if (!empty($form['#submit']) && is_array($form['#submit'])) {
    $submit_key = array_search($register_submit_callback, $form['#submit']);
  }

  // Add these hooks only when needed, and ensure they are not added twice.
  if (isset($submit_key) && $submit_key !== FALSE && !in_array('profile2_form_before_user_register_submit_handler', $form['#submit'])) {

    // Insert submit callback right before the user register submit callback.
    // Needs for disabling email notification during user registration.
    array_splice($form['#submit'], $submit_key, 0, array(
      'profile2_form_before_user_register_submit_handler',
    ));

    // Add a submit callback right after the user register submit callback.
    // This is needed for creation of a new user profile.
    array_splice($form['#submit'], $submit_key + 2, 0, array(
      'profile2_form_submit_handler',
    ));

    // Insert submit handler right after the creation of new user profile.
    // This is needed for sending email which was blocked during registration.
    array_splice($form['#submit'], $submit_key + 3, 0, array(
      'profile2_form_after_user_register_submit_handler',
    ));
  }
  else {

    // Fallback if some contrib module removes user register submit callback
    // from form submit functions.
    $form['#submit'][] = 'profile2_form_submit_handler';
  }
}

/**
 * Validation handler for the profile form.
 *
 * @see profile2_attach_form()
 */
function profile2_form_validate_handler(&$form, &$form_state) {
  foreach ($form_state['profiles'] as $type => $profile) {
    if (isset($form_state['values']['profile_' . $profile->type])) {

      // @see entity_form_field_validate()
      $pseudo_entity = (object) $form_state['values']['profile_' . $profile->type];
      $pseudo_entity->type = $type;
      field_attach_form_validate('profile2', $pseudo_entity, $form['profile_' . $profile->type], $form_state);
    }
  }
}

/**
 * User registration form submit handler
 * that executes right before user_register_submit().
 *
 * In generally, this callback disables the notification emails
 * during the execution of user_register_submit() callback.
 * The reason for this - we want to support profile2 tokens
 * in emails during registration, and there is no another
 * proper way to do this. See https://drupal.org/node/1097684.
 *
 * @see profile2_form_after_user_register_submit_handler()
 * @see user_register_submit()
 * @see profile2_attach_form()
 */
function profile2_form_before_user_register_submit_handler(&$form, &$form_state) {
  global $conf;

  // List of available operations during the registration.
  $register_ops = array(
    'register_admin_created',
    'register_no_approval_required',
    'register_pending_approval',
  );

  // We also have to track if we change a variables, because
  // later we have to restore them.
  $changed_ops =& drupal_static('profile2_register_changed_operations', array());
  foreach ($register_ops as $op) {

    // Save variable value.
    if (isset($conf['user_mail_' . $op . '_notify'])) {
      $changed_ops['user_mail_' . $op . '_notify'] = $conf['user_mail_' . $op . '_notify'];
    }

    // Temporary disable the notification about registration.
    $conf['user_mail_' . $op . '_notify'] = FALSE;
  }
}

/**
 * User registration form submit handler
 * that executes right after user_register_submit().
 *
 * This callback sends delayed email notification to a user
 * about his registration. See https://drupal.org/node/1097684.
 *
 * @see profile2_form_prepare_user_register_submit_handler()
 * @see user_register_submit()
 * @see profile2_attach_form()
 */
function profile2_form_after_user_register_submit_handler(&$form, &$form_state) {
  global $conf;

  // List of registration operations that where
  // notification values were changed.
  $changed_ops =& drupal_static('profile2_register_changed_operations', array());

  // List of available operations during the registration.
  $register_ops = array(
    'register_admin_created',
    'register_no_approval_required',
    'register_pending_approval',
  );
  foreach ($register_ops as $op) {

    // If we changed the notification value in
    // profile2_form_before_user_register_submit_handler() then change it back.
    if (isset($changed_ops['user_mail_' . $op . '_notify'])) {
      $conf['user_mail_' . $op . '_notify'] = $changed_ops['user_mail_' . $op . '_notify'];
    }
    else {
      unset($conf['user_mail_' . $op . '_notify']);
    }
  }

  // Get the values that we need to define which notification
  // should be sent to the user. Generally this is a trimmed version
  // of user_register_submit() callback.
  $admin = !empty($form_state['values']['administer_users']);
  $account = $form_state['user'];
  $notify = !empty($form_state['values']['notify']);
  if ($admin && !$notify) {

    // If admin has created a new account and decided to don't notify a user -
    // then just do nothing.
  }
  elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
    _user_mail_notify('register_no_approval_required', $account);
  }
  elseif ($account->status || $notify) {
    $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
    _user_mail_notify($op, $account);
  }
  elseif (!$admin) {
    _user_mail_notify('register_pending_approval', $account);
  }
}

/**
 * Submit handler that builds and saves all profiles in the form.
 *
 * @see profile2_attach_form()
 */
function profile2_form_submit_handler(&$form, &$form_state) {
  profile2_form_submit_build_profile($form, $form_state);

  // This is needed as some submit callbacks like user_register_submit() rely on
  // clean form values.
  profile2_form_submit_cleanup($form, $form_state);
  foreach ($form_state['profiles'] as $type => $profile) {

    // During registration set the uid field of the newly created user.
    if (empty($profile->uid) && isset($form_state['user']->uid)) {
      $profile->uid = $form_state['user']->uid;
    }
    profile2_save($profile);
  }
}

/**
 * Submit builder. Extracts the form values and updates the profile entities.
 *
 * @see profile2_attach_form()
 */
function profile2_form_submit_build_profile(&$form, &$form_state) {
  foreach ($form_state['profiles'] as $type => $profile) {

    // @see entity_form_submit_build_entity()
    if (isset($form['profile_' . $type]['#entity_builders'])) {
      foreach ($form['profile_' . $type]['#entity_builders'] as $function) {
        $function('profile2', $profile, $form['profile_' . $type], $form_state);
      }
    }
    field_attach_submit('profile2', $profile, $form['profile_' . $type], $form_state);
  }
}

/**
 * Cleans up the form values as the user modules relies on clean values.
 *
 * @see profile2_attach_form()
 */
function profile2_form_submit_cleanup(&$form, &$form_state) {
  foreach ($form_state['profiles'] as $type => $profile) {
    unset($form_state['values']['profile_' . $type]);
  }
}

/**
 * Implements hook_user_categories().
 */
function profile2_user_categories() {
  $data = array();
  foreach (profile2_get_types() as $type => $info) {
    if ($info->userCategory) {
      $data[] = array(
        'name' => $type,
        'title' => $info
          ->getTranslation('label'),
        // Add an offset so a weight of 0 appears right of the account category.
        'weight' => $info->weight + 3,
        'access callback' => 'profile2_category_access',
        'access arguments' => array(
          1,
          $type,
        ),
      );
    }
  }
  return $data;
}

/**
 * Menu item access callback - check if a user has access to a profile category.
 */
function profile2_category_access($account, $type_name) {
  $profile = profile2_load_by_user($account, $type_name);

  // As there might be no profile yet, create a new object for being able to run
  // a proper access check.
  if (empty($profile)) {
    $profile = profile2_create(array(
      'type' => $type_name,
      'uid' => $account->uid,
    ));
  }
  return $account->uid > 0 && $profile
    ->type()->userCategory && profile2_access('edit', $profile);
}

/**
 * Determines if profile user has current profile available by role.
 *
 * @param $profile
 *   Profile to check.
 * @return boolean
 *   TRUE if profile is available to the profile user.
 */
function profile2_role_access($profile) {
  if (isset($profile->type)) {
    $profile_type = profile2_type_load($profile->type);
    if (!empty($profile_type) && !empty($profile_type->data['roles']) && isset($profile->uid)) {
      $profile_user = user_load($profile->uid);
      $profile_roles = array_keys($profile_type->data['roles']);
      $user_roles = array_keys($profile_user->roles);
      $matches = array_intersect($profile_roles, $user_roles);
      if (empty($matches)) {
        return FALSE;
      }
    }
  }
  return TRUE;
}

/**
 * Determines whether the given user has access to a profile.
 *
 * @param $op
 *   The operation being performed. One of 'view', 'update', 'create', 'delete'
 *   or just 'edit' (being the same as 'create' or 'update').
 * @param $profile
 *   (optional) A profile to check access for. If nothing is given, access for
 *   all profiles is determined.
 * @param $account
 *   The user to check for. Leave it to NULL to check for the global user.
 * @return boolean
 *   Whether access is allowed or not.
 *
 * @see hook_profile2_access()
 * @see profile2_profile2_access()
 */
function profile2_access($op, $profile = NULL, $account = NULL) {

  // Check if profile user has current profile available by role.
  if (isset($profile) && !profile2_role_access($profile)) {
    return FALSE;
  }

  // With access to all profiles there is no need to check further.
  if (user_access('administer profiles', $account)) {
    return TRUE;
  }
  if ($op == 'create' || $op == 'update') {
    $op = 'edit';
  }

  // Allow modules to grant / deny access.
  $access = module_invoke_all('profile2_access', $op, $profile, $account);

  // Only grant access if at least one module granted access and no one denied
  // access.
  if (in_array(FALSE, $access, TRUE)) {
    return FALSE;
  }
  elseif (in_array(TRUE, $access, TRUE)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_profile2_access().
 */
function profile2_profile2_access($op, $profile = NULL, $account = NULL) {
  if (isset($profile) && ($type_name = $profile->type)) {
    if (user_access("{$op} any {$type_name} profile", $account)) {
      return TRUE;
    }
    $account = isset($account) ? $account : $GLOBALS['user'];
    if (isset($profile->uid) && $profile->uid == $account->uid && user_access("{$op} own {$type_name} profile", $account)) {
      return TRUE;
    }
  }

  // Do not explicitly deny access so others may still grant access.
}

/**
 * Access callback for the entity API.
 */
function profile2_type_access($op, $type = NULL, $account = NULL) {
  return user_access('administer profile types', $account);
}

/**
 * Implements hook_theme().
 */
function profile2_theme() {
  return array(
    'profile2' => array(
      'render element' => 'elements',
      'template' => 'profile2',
    ),
  );
}

/**
 * The class used for profile entities.
 */
class Profile extends Entity {

  /**
   * The profile id.
   *
   * @var integer
   */
  public $pid;

  /**
   * The name of the profile type.
   *
   * @var string
   */
  public $type;

  /**
   * The profile label.
   *
   * @var string
   */
  public $label;

  /**
   * The user id of the profile owner.
   *
   * @var integer
   */
  public $uid;

  /**
   * The Unix timestamp when the profile was created.
   *
   * @var integer
   */
  public $created;

  /**
   * The Unix timestamp when the profile was most recently saved.
   *
   * @var integer
   */
  public $changed;
  public function __construct($values = array()) {
    if (isset($values['user'])) {
      $this
        ->setUser($values['user']);
      unset($values['user']);
    }
    if (isset($values['type']) && is_object($values['type'])) {
      $values['type'] = $values['type']->type;
    }
    if (!isset($values['label']) && isset($values['type']) && ($type = profile2_get_types($values['type']))) {

      // Initialize the label with the type label, so newly created profiles
      // have that as interim label.
      $values['label'] = $type->label;
    }
    parent::__construct($values, 'profile2');
  }

  /**
   * Returns the user owning this profile.
   */
  public function user() {
    return user_load($this->uid);
  }

  /**
   * Sets a new user owning this profile.
   *
   * @param $account
   *   The user account object or the user account id (uid).
   */
  public function setUser($account) {
    $this->uid = is_object($account) ? $account->uid : $account;
  }

  /**
   * Gets the associated profile type object.
   *
   * @return ProfileType
   */
  public function type() {
    return profile2_get_types($this->type);
  }

  /**
   * Returns the full url() for the profile.
   */
  public function url() {
    $uri = $this
      ->uri();
    return url($uri['path'], $uri);
  }

  /**
   * Returns the drupal path to this profile.
   */
  public function path() {
    $uri = $this
      ->uri();
    return $uri['path'];
  }
  public function defaultUri() {
    return array(
      'path' => 'user/' . $this->uid,
      'options' => array(
        'fragment' => 'profile-' . $this->type,
      ),
    );
  }
  public function defaultLabel() {
    $type = profile2_get_types($this->type);
    if (!empty($type->data['edit_label'])) {
      return $this->label;
    }
    else {

      // Return a label that combines the type name and user name, translatable.
      return t('@type profile for @user', array(
        '@type' => $type
          ->getTranslation('label'),
        '@user' => format_username($this
          ->user()),
      ));
    }
  }
  public function buildContent($view_mode = 'full', $langcode = NULL) {
    $content = array();

    // Assume newly create objects are still empty.
    if (!empty($this->is_new)) {
      $content['empty']['#markup'] = '<em class="profile2-no-data">' . t('There is no profile data yet.') . '</em>';
    }
    return entity_get_controller($this->entityType)
      ->buildContent($this, $view_mode, $langcode, $content);
  }
  public function save() {

    // Don't create a new profile if the user already has one of the same type.
    $existing_profile = profile2_load_by_user($this->uid, $this->type);
    if (empty($this->pid) && !empty($existing_profile)) {
      watchdog('profile2_create', '@type profile already exists for user @uid', array(
        '@uid' => $this->uid,
        '@type' => $this->type,
      ), WATCHDOG_WARNING);
      return;
    }

    // Care about setting created and changed values. But do not automatically
    // set a created values for already existing profiles.
    if (empty($this->created) && (!empty($this->is_new) || !$this->pid)) {
      $this->created = REQUEST_TIME;
    }
    $this->changed = REQUEST_TIME;

    // Clear the static cache from profile2_load_by_user() before saving, so
    // that profiles are correctly loaded in insert/update hooks.
    $cache =& drupal_static('profile2_load_by_user', array());
    unset($cache[$this->uid]);
    return parent::save();
  }

}

/**
 * Use a separate class for profile types so we can specify some defaults
 * modules may alter.
 */
class ProfileType extends Entity {

  /**
   * Whether the profile type appears in the user categories.
   */
  public $userCategory = TRUE;

  /**
   * Whether the profile is displayed on the user account page.
   */
  public $userView = TRUE;
  public $type;
  public $label;
  public $weight = 0;
  public function __construct($values = array()) {
    parent::__construct($values, 'profile2_type');
  }

  /**
   * Returns whether the profile type is locked, thus may not be deleted or renamed.
   *
   * Profile types provided in code are automatically treated as locked, as well
   * as any fixed profile type.
   */
  public function isLocked() {
    return isset($this->status) && empty($this->is_new) && ($this->status & ENTITY_IN_CODE || $this->status & ENTITY_FIXED);
  }

  /**
   * Overridden, to introduce the method for old entity API versions (BC).
   *
   * @todo Remove once we bump the required entity API version.
   */
  public function getTranslation($property, $langcode = NULL) {
    if (module_exists('profile2_i18n')) {
      return parent::getTranslation($property, $langcode);
    }
    return $this->{$property};
  }

  /**
   * Overrides Entity::save().
   */
  public function save() {
    parent::save();

    // Clear field info caches such that any changes to extra fields get
    // reflected.
    field_info_cache_clear();
  }

}

/**
 * View a profile.
 *
 * @see Profile::view()
 */
function profile2_view($profile, $view_mode = 'full', $langcode = NULL, $page = NULL) {
  return $profile
    ->view($view_mode, $langcode, $page);
}

/**
 * Implements hook_form_FORMID_alter().
 *
 * Adds a checkbox for controlling field view access to fields added to
 * profiles.
 */
function profile2_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
  if (!empty($form['instance']['entity_type']['#value']) && $form['instance']['entity_type']['#value'] == 'profile2') {
    $form['field']['settings']['profile2_private'] = array(
      '#type' => 'checkbox',
      '#title' => t('Make the content of this field private.'),
      '#default_value' => !empty($form['#field']['settings']['profile2_private']),
      '#description' => t('If checked, the content of this field is only shown to the profile owner and administrators.'),
    );
  }
  else {

    // Add the value to the form so it isn't lost.
    $form['field']['settings']['profile2_private'] = array(
      '#type' => 'value',
      '#value' => !empty($form['#field']['settings']['profile2_private']),
    );
  }
}

/**
 * Implements hook_field_access().
 */
function profile2_field_access($op, $field, $entity_type, $profile = NULL, $account = NULL) {
  if ($entity_type == 'profile2' && $op == 'view' && isset($profile)) {

    // Check if the profile type is accessible (e.g. applicable to the role).
    if (!profile2_role_access($profile)) {
      return FALSE;
    }

    // Deny view access, if someone else views a private field.
    if (!empty($field['settings']['profile2_private']) && !user_access('administer profiles', $account)) {
      $account = isset($account) ? $account : $GLOBALS['user'];
      if ($account->uid != $profile->uid) {
        return FALSE;
      }
    }
  }
}

/**
 * Implements hook_field_extra_fields().
 *
 * We need to add pseudo fields for profile types to allow for weight settings
 * when viewing a user or filling in the profile types while registrating.
 */
function profile2_field_extra_fields() {
  $extra = array();
  foreach (profile2_get_types() as $type_name => $type) {

    // Appears on: admin/config/people/accounts/display
    if (!empty($type->userView)) {
      $extra['user']['user']['display']['profile_' . $type_name] = array(
        'label' => t('Profile: @profile', array(
          '@profile' => $type->label,
        )),
        'weight' => $type->weight,
      );
    }

    // Appears on: admin/config/people/accounts/fields
    if (!empty($type->data['registration'])) {
      $extra['user']['user']['form']['profile_' . $type_name] = array(
        'label' => t('Profile: @profile', array(
          '@profile' => $type->label,
        )),
        'description' => t('Appears during registration only.'),
        'weight' => $type->weight,
      );
    }
  }
  return $extra;
}

/**
 * Entity metadata callback to load profiles for the given user account.
 */
function profile2_user_get_properties($account, array $options, $name) {

  // Remove the leading 'profile_' from the property name to get the type name.
  $profile = profile2_load_by_user($account, substr($name, 8));
  return $profile ? $profile : NULL;
}

/**
 * Implements hook_ctools_plugin_directory().
 */
function profile2_ctools_plugin_directory($owner, $plugin_type) {
  if ($owner == 'ctools' && !empty($plugin_type)) {
    return 'plugins/' . $plugin_type;
  }
}

/**
 * Implements hook_preprocess_ctools_context_item_form().
 *
 * When the User context is added, CTools will update the relationship dropdown
 * with ajax. The dropdown is passed through theme_ctools_context_item_form
 * before being passed to ajax_render, so that is our best opportunity to
 * alter it.
 *
 * @see ctools_context_ajax_item_add
 */
function profile2_preprocess_ctools_context_item_form(&$vars) {
  unset($vars['form']['buttons']['relationship']['item']['#options']['entity_from_schema:uid-user-profile2']);
}

/**
 * Determines whether the given user has access to delete a profile.
*/
function profile2_delete_access($uid, $type_name) {
  $profile = profile2_by_uid_load($uid, $type_name);
  return is_object($profile) ? profile2_access('edit', $profile) : FALSE;
}

/**
 * Menu load callback.
 *
 * Returns the profile object for the given user. If there is none yet, a new
 * object is created.
 */
function profile2_by_uid_load($uid, $type_name) {
  if ($uid && is_numeric($uid) && ($account = user_load($uid))) {
    $profile = profile2_load_by_user($account, $type_name);
    if (!$profile) {
      $profile = profile2_create(array(
        'type' => $type_name,
      ));
      $profile
        ->setUser($account);
      $profile->is_new = TRUE;
    }
    return $profile;
  }
  return FALSE;
}

/**
 * Implements hook_preprocess_page().
 *
 * Fix the page titles on the profile2 edit tabs.
 * We want the titles to be the full profile label, giving the user name & profile name.
 */
function profile2_preprocess_page(&$vars) {

  // This is true when editing a profile in a tab.
  if (!empty($vars['page']['content']['system_main']['#user_category'])) {
    $ptype = $vars['page']['content']['system_main']['#user_category'];
    if (!empty($vars['page']['content']['system_main']["profile_{$ptype}"])) {
      $item = $vars['page']['content']['system_main']["profile_{$ptype}"];

      // If we've found an item, and it has a profile2 entity, display the title.
      if (!empty($item['#entity'])) {
        $vars['title'] = $item['#entity']
          ->label();
      }
    }
  }
}

Functions

Namesort descending Description
profile2_access Determines whether the given user has access to a profile.
profile2_attach_form Attaches the profile forms of the profiles set in $form_state['profiles'].
profile2_by_uid_load Menu load callback.
profile2_category_access Menu item access callback - check if a user has access to a profile category.
profile2_create Create a new profile object.
profile2_ctools_plugin_directory Implements hook_ctools_plugin_directory().
profile2_delete Deletes a profile.
profile2_delete_access Determines whether the given user has access to delete a profile.
profile2_delete_multiple Delete multiple profiles.
profile2_entity_info Implements hook_entity_info().
profile2_field_access Implements hook_field_access().
profile2_field_extra_fields Implements hook_field_extra_fields().
profile2_form_after_user_register_submit_handler User registration form submit handler that executes right after user_register_submit().
profile2_form_before_user_register_submit_handler User registration form submit handler that executes right before user_register_submit().
profile2_form_field_ui_field_edit_form_alter Implements hook_form_FORMID_alter().
profile2_form_submit_build_profile Submit builder. Extracts the form values and updates the profile entities.
profile2_form_submit_cleanup Cleans up the form values as the user modules relies on clean values.
profile2_form_submit_handler Submit handler that builds and saves all profiles in the form.
profile2_form_submit_own_delete Profile form submit handler for the delete button.
profile2_form_user_profile_form_alter Implements hook_form_FORM_ID_alter() for the user edit form.
profile2_form_user_register_form_alter Implements hook_form_FORM_ID_alter() for the registration form.
profile2_form_validate_handler Validation handler for the profile form.
profile2_get_types Gets an array of all profile types, keyed by the type name.
profile2_load Fetch a profile object.
profile2_load_by_user Fetch profiles by account.
profile2_load_multiple Load multiple profiles based on certain conditions.
profile2_menu Implements hook_menu().
profile2_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
profile2_permission Implements hook_permission().
profile2_preprocess_ctools_context_item_form Implements hook_preprocess_ctools_context_item_form().
profile2_preprocess_page Implements hook_preprocess_page().
profile2_profile2_access Implements hook_profile2_access().
profile2_profile2_delete Implements hook_profile2_delete().
profile2_profile2_type_delete Implements hook_profile2_type_delete()
profile2_profile2_type_insert Implements hook_profile2_type_insert().
profile2_profile2_type_update Implements hook_profile2_type_update().
profile2_role_access Determines if profile user has current profile available by role.
profile2_save Saves a profile to the database.
profile2_theme Implements hook_theme().
profile2_type_access Access callback for the entity API.
profile2_type_delete Deletes a profile type from.
profile2_type_load Menu argument loader; Load a profile type by string.
profile2_type_save Saves a profile type to the db.
profile2_user_categories Implements hook_user_categories().
profile2_user_delete Implements hook_user_delete().
profile2_user_get_properties Entity metadata callback to load profiles for the given user account.
profile2_user_view Implements hook_user_view().
profile2_view View a profile.
profile_create Deprecated. Use profile2_create().

Classes

Namesort descending Description
Profile The class used for profile entities.
ProfileType Use a separate class for profile types so we can specify some defaults modules may alter.