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_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' => '', ); $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) { $str_button_value = t('Delete profile'); } elseif (user_access("delete own $profile->type profile") && $user->uid === $profile->uid) { $str_button_value = t('Delete this 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) { foreach (profile2_get_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_type) { $form['profile_' . $type_name] += array( '#type' => 'fieldset', '#title' => check_plain($profile_type->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 Profile types 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']; } // Otherwise just remove this value from a global variables array. 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); } // No administrator approval required. elseif ($account->status || $notify) { $op = $notify ? 'register_admin_created' : 'register_no_approval_required'; _user_mail_notify($op, $account); } // Administrator approval required. 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) { // As there might be no profile yet, create a new object for being able to run // a proper access check. $profile = profile2_create(array('type' => $type_name, 'uid' => $account->uid)); return ($account->uid > 0 && $profile->type()->userCategory && profile2_access('edit', $profile)); } /** * 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->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; } } } // 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() { // Return a label that combines the type name and user name, translatable. return t('@type profile for @user', array( '@type' => profile2_get_types($this->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'] = '' . t('There is no profile data yet.') . ''; } 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 have 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', serialize(array( 'message' => 'Profile already exists', 'uid' => $this->uid, 'type' => $this->type, 'path' => current_path(), 'logged_in_user' => $GLOBALS['user']->uid, )), array(), 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_access($op, $profile, $account)) { 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(); } } } }