updated webform, webform_localization, profile2, term_merge, search_api_saved_pages, rules, redirect, overide_node_options

This commit is contained in:
2019-05-13 18:47:27 +02:00
parent 58cd990c8c
commit 9adc940a67
281 changed files with 28658 additions and 7138 deletions

View File

@@ -6,6 +6,7 @@
Maintainers:
* Wolfgang Ziegler (fago), nuppla@zites.net
* Joachim Noreiko (joachim), joachim.n+drupal@gmail.com
* Rick Jones (RickJ), rick@activeservice.co.uk
This modules is designed to be the successor of the core profile module. In
@@ -24,8 +25,26 @@ Usage
-----
* Go to /admin/structure/profiles for managing profile types.
* By default users may view their profile at /user and edit them at
'user/X/edit'.
* By default users' profile information is displayed on the
account view page (/user/X). The relative positioning can be
controlled using Account Settings -> Manage Display
(/admin/config/people/accounts/display)
Profile titles
--------------
A new feature in 7.x-1.6 gives each profile an unambiguous title.
Previously, profile titles defaulted to the profile type, which was
ambiguous, and contained other bugs. In this release the default
profile title is "<type> profile for <user>".
This format is a translatable string, so can be easily customised.
If you have locales installed you can use that, otherwise the
String Overrides module provides a simple way to replace text in
the current language.
The string to override is "@type profile for @user". To simulate
the previous behaviour (without the bugs), just use "@type".
@@ -34,31 +53,30 @@ Usage
--------------------------------------------------------------------------------
Maintainers:
* Wolfgang Ziegler (fago), nuppla@zites.net
This module provides an alternative way for your users to edit their profiles.
Instead of integrating with the user account page, it generates a separate page
allowing your users to view and edit their profile.
* Rick Jones (RickJ), rick@activeservice.co.uk
This module provides alternative ways for users to view and edit their profiles.
There are two options, instead of integrating with the user account page.
1. Generate a separate page for users to view and edit their profiles.
2. Display the profile in a separate sub-tab of the account page.
In this case the editing mode of the profile is unchanged.
These options are mutually exclusive, but are set per profile type, so different
profiles can display in different ways.
Installation
-------------
* Once profile2 is installed, just active the profile pages module.
* Once profile2 is installed, just active the Profile2-pages module.
Usage
-----
* The module may be enabled per profile-type by checking the checkbox
"Provide a separate page for editing profiles." in the profile type's
settings.
* Users with sufficient permissions (check user permissions) receive a menu
item in their user menu, just beside the "My account" menu item.
--------------------------------------------------------------------------------
General notes
--------------------------------------------------------------------------------
* Automatic profile labels can be easily generated based upon a pre-configured
pattern using the Rules module. See http://drupal.org/node/1392716 for more
details.
* The module's options may be enabled per profile-type by checking one of the
checkboxes "Provide a separate page for editing profiles." or
"Provide a separate tab for viewing profiles." in the profile type's settings.
* In the first case, users with sufficient permissions (check user permissions)
receive a menu item in their user menu, next to the "My account" menu item.

View File

@@ -5,9 +5,8 @@ dependencies[] = i18n_string
package = Multilingual - Internationalization
core = 7.x
; Information added by drupal.org packaging script on 2012-12-26
version = "7.x-1.3"
; Information added by Drupal.org packaging script on 2019-01-01
version = "7.x-1.6"
core = "7.x"
project = "profile2"
datestamp = "1356482021"
datestamp = "1546363384"

View File

@@ -4,9 +4,8 @@ core = 7.x
dependencies[] = profile2
dependencies[] = og
package = "Organic groups"
; Information added by drupal.org packaging script on 2012-12-26
version = "7.x-1.3"
; Information added by Drupal.org packaging script on 2019-01-01
version = "7.x-1.6"
core = "7.x"
project = "profile2"
datestamp = "1356482021"
datestamp = "1546363384"

View File

@@ -25,13 +25,14 @@ function profile2_page_own($base_path) {
* Profile view page.
*/
function profile2_page_view($profile) {
return $profile->view('page');
return $profile->view('page', NULL, TRUE);
}
/**
* The profile edit form.
*/
function profile2_form($form, &$form_state, $profile) {
global $user;
if (empty($form_state['profiles'])) {
$form_state['profiles'][$profile->type] = $profile;
}
@@ -46,10 +47,16 @@ function profile2_form($form, &$form_state, $profile) {
'#value' => t('Save'),
'#weight' => 40,
);
if (empty($profile->is_new) && user_access('administer profiles')) {
if (user_access('administer profiles') && $user->uid != $profile->uid) {
$delete_button_label = t('Delete profile');
}
elseif (user_access("delete own $profile->type profile") && $user->uid === $profile->uid) {
$delete_button_label = t('Delete this profile');
}
if (empty($profile->is_new) && !empty($delete_button_label)) {
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete profile'),
'#value' => $delete_button_label,
'#weight' => 45,
'#limit_validation_errors' => array(),
'#submit' => array('profile2_form_submit_delete')
@@ -79,14 +86,27 @@ function profile2_form_submit_delete($form, &$form_state) {
* Confirm form for deleting a profile.
*/
function profile2_page_delete_confirm_form($form, &$form_state, $profile) {
global $user;
$form_state += array('profile2' => $profile);
$confirm_question = t('Are you sure you want to delete profile %label?', array('%label' => $profile->label()));
$type = profile2_get_types($profile->type);
if (isset($profile->uid) && $profile->uid === $user->uid) {
$confirm_question = t('Are you sure you want to delete your own %label profile ?',
array('%label' => $type->getTranslation('label')));
}
elseif (user_access('administer profiles')) {
$user_account = user_load($profile->uid);
if (!empty($user_account)) {
$confirm_question = t("Are you sure you want to delete profile %label of user %user?",
array('%label' => $type->getTranslation('label'), '%user' => $user_account->name));
}
}
return confirm_form($form, $confirm_question, $profile->path());
}
function profile2_page_delete_confirm_form_submit($form, &$form_state) {
$profile = $form_state['profile2'];
$type = profile2_get_types($profile->type);
$profile->delete();
drupal_set_message(t('Deleted %label.', array('%label' => $profile->label)));
drupal_set_message(t('Deleted %label.', array('%label' => $type->getTranslation('label'))));
$form_state['redirect'] = 'user/' . $profile->uid;
}

View File

@@ -2,9 +2,8 @@ name = Profile2 pages
description = Adds separate pages for viewing and editing profiles.
core = 7.x
dependencies[] = profile2
; Information added by drupal.org packaging script on 2012-12-26
version = "7.x-1.3"
; Information added by Drupal.org packaging script on 2019-01-01
version = "7.x-1.6"
core = "7.x"
project = "profile2"
datestamp = "1356482021"
datestamp = "1546363384"

View File

@@ -16,41 +16,34 @@ function profile2_page_menu() {
return;
}
$profile2_view_tab_count = 0;
foreach (profile2_get_types() as $type_name => $type) {
if (!empty($type->data['use_page'])) {
$path = profile2_page_get_base_path($type);
$count = count(explode('/', $path));
// This is the menu item for opening the user's own profile page.
$items[$path] = array(
'title callback' => 'profile2_page_title',
'title arguments' => array($type_name),
'title arguments' => array($type_name, TRUE),
'page callback' => 'profile2_page_own',
'page arguments' => array($path),
'access callback' => 'user_access',
'access arguments' => array("edit own $type_name profile"),
'access arguments' => array("view own $type_name profile"),
'file' => 'profile2_page.inc',
'menu_name' => 'user-menu',
);
// This is the router item that opens the page view.
$items[$path . '/%profile2_by_uid'] = array(
'title callback' => 'profile2_page_title',
'title arguments' => array($type_name, $count),
// Title is added in profile2_page_preprocess_page().
'page callback' => 'profile2_page_view',
'page arguments' => array($count),
'load arguments' => array($type_name),
'access callback' => 'profile2_access',
'access arguments' => array('view', $count),
'file' => 'profile2_page.inc',
// Copied over the following hack from user_menu() to avoid $path
// appearing in the breadcrumb:
//
// By assigning a different menu name, this item (and all registered
// child paths) are no longer considered as children of 'user'. When
// accessing the user account pages, the preferred menu link that is
// used to build the active trail (breadcrumb) will be found in this
// menu (unless there is more specific link), so the link to 'user' will
// not be in the breadcrumb.
'menu_name' => 'navigation',
'type' => MENU_CALLBACK,
);
$items[$path . '/%profile2_by_uid/view'] = array(
'title' => 'View',
@@ -109,27 +102,57 @@ function profile2_page_menu() {
);
}
}
else if (!empty($type->data['use_tab'])) {
// Make tab(s) under user profile page.
$items['user/%profile2_by_uid/view/' . $type_name] = array(
'title callback' => 'profile2_page_title',
'title arguments' => array($type_name),
'page callback' => 'profile2_page_view',
'page arguments' => array(1),
'load arguments' => array($type_name),
'access callback' => 'profile2_access',
'access arguments' => array('view', 1),
'type' => MENU_LOCAL_TASK,
'file' => 'profile2_page.inc',
);
}
$profile2_view_tab_count++;
}
// If there exists at least one tab for profile2 type,
// then we need to create default tab for user account page.
if ($profile2_view_tab_count) {
$items['user/%user/view/account'] = array(
'title' => 'Account',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
}
return $items;
}
/**
* Menu load callback.
* Implements hook_menu_local_tasks_alter().
*
* Returns the profile object for the given user. If there is none yet, a new
* object is created.
* If viewing a profile sub-tab, change the URL of the edit tab (if present)
* so it edits the profile, not the main account page.
* Subject to edit permissions.
*/
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;
function profile2_page_menu_local_tasks_alter(&$data, $router_item, $root_path) {
if (strpos($root_path, 'user/%/view/') === 0) {
// Get the part of the URL containing "user/<uid>".
$href = $router_item['tab_root_href'] . '/edit';
// Get the uid & profile type name.
$uid = $router_item['original_map'][1];
$ptype = $router_item['original_map'][3];
if (profile2_access('edit', profile2_by_uid_load($uid, $ptype))) {
//Walk the menu tree to find the account-edit link, and append the profile type.
array_walk_recursive($data, function (&$item) use ($href, $ptype) {
if ($item == $href) {
$item .= "/$ptype";
}
});
}
return $profile;
}
return FALSE;
}
/**
@@ -155,7 +178,12 @@ function profile2_page_forms($form_id, $args) {
$forms['profile2_edit_' . $bundle . '_form']['callback'] = 'profile2_form';
$forms['profile2_edit_' . $bundle . '_form']['wrapper callback'] = 'entity_ui_form_defaults';
}
return $forms;
if (!empty($forms)) {
// Include the file with profile2_form() callback. This needed when the
// form is loaded from the outside, for example, from the ajax callback.
form_load_include($form_state, 'inc', 'profile2_page');
return $forms;
}
}
}
@@ -169,6 +197,9 @@ function profile2_page_profile2_type_load($types) {
$type->userCategory = FALSE;
$type->userView = FALSE;
}
elseif (!empty($type->data['use_tab'])) {
$type->userView = FALSE;
}
}
}
@@ -187,6 +218,12 @@ function profile2_page_entity_info_alter(&$entity_info) {
);
$entity_info['profile2']['uri callback'] = 'profile2_page_uri_callback';
$entity_info['profile2']['form callback'] = 'profile2_page_form_callback';
// Integrate with Metatag module to enable metatags support for separate
// profile pages.
if (module_exists('metatag')) {
$entity_info['profile2']['metatags'] = TRUE;
}
}
/**
@@ -214,16 +251,14 @@ function profile2_page_form_callback($profile) {
}
/**
* Menu title callback.
* Menu title callbacks.
*/
function profile2_page_title($type_name, $profile2 = NULL) {
$type = profile2_get_types($type_name);
// If no profile is given, we are at the general path pointing to the own
// profile.
if (!isset($profile2)) {
return t('My @profile-label', array('@profile-label' => drupal_strtolower($type->getTranslation('label'))));
function profile2_page_title($type_name, $my = FALSE) {
$label = profile2_get_types($type_name)->getTranslation('label');
if ($my) {
$label = t('My @title', array('@title' => $label));
}
return drupal_ucfirst($profile2->label());
return $label;
}
/**
@@ -234,41 +269,44 @@ function profile2_page_form_profile2_type_form_alter(&$form, &$form_state) {
$form['data']['use_page'] = array(
'#type' => 'checkbox',
'#title' => t('Provide a separate page for editing profiles.'),
'#description' => t('If enabled, a separate menu item for editing the profile is generated and the profile is hidden from the user account page.'),
'#default_value' => !empty($type->is_new) || !empty($type->data['use_page']),
'#description' => t('If enabled, a separate menu item for viewing and editing the profile is generated, and the profile is hidden from the user account page.'),
'#default_value' => empty($type->is_new) && !empty($type->data['use_page']),
'#states' => array(
'invisible' => array(
':input[name="data[use_tab]"]' => array('checked' => TRUE),
),
),
);
$form['data']['use_tab'] = array(
'#type' => 'checkbox',
'#title' => t('Provide a separate tab for viewing profiles.'),
'#description' => t('If enabled, the profile is shown under a separate tab on the user account page.'),
'#default_value' => empty($type->is_new) && !empty($type->data['use_tab']),
'#states' => array(
'invisible' => array(
':input[name="data[use_page]"]' => array('checked' => TRUE),
),
),
);
$form['data']['#tree'] = TRUE;
}
/**
* Implements hook_profile2_type_insert().
* Implements hook_preprocess_page().
*
* Fix the page titles on the profile view pages.
* We want the titles to be the full profile label, giving the user name & profile name.
*
* Note: the title for the separate edit page is already correct.
*/
function profile2_page_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.
if (!empty($type->data['use_page'])) {
variable_set('menu_rebuild_needed', TRUE);
}
}
/**
* Implements hook_profile2_type_update().
*/
function profile2_page_profile2_type_update(ProfileType $type) {
// Rebuild the menu if use_page or the type name has been changed.
// @see profile2_page_profile2_type_insert()
if (empty($type->data['use_page']) != empty($type->original->data['use_page']) || ($type->data['use_page'] && $type->type != $type->original->type)) {
variable_set('menu_rebuild_needed', TRUE);
}
}
/**
* Implements hook_profile2_type_delete()
*/
function profile2_page_profile2_type_delete($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.
if (!empty($type->data['use_page'])) {
variable_set('menu_rebuild_needed', TRUE);
function profile2_page_preprocess_page(&$vars) {
// This is true for profile2 view pages, both as a tab and a separate page.
if (!empty($vars['page']['content']['system_main']['profile2'])) {
// Get the one item, index is unknown..
$item = reset($vars['page']['content']['system_main']['profile2']);
// If we've found an item, and it has a profile2 entity, display the title.
if (!empty($item['#entity'])) {
$vars['title'] = $item['#entity']->label();
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* @file
* Plugin to provide an relationship handler for profile2 from user.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t('Profile2 from user'),
'keyword' => 'profile2',
'description' => t('Adds a profile2 type from a user context.'),
'required context' => new ctools_context_required(t('User'), 'entity:user'),
'context' => 'profile2_from_user_context',
'edit form' => 'profile2_from_user_settings_form',
'defaults' => array('type' => 'main'),
);
/**
* Return a new context based on an existing context.
*/
function profile2_from_user_context($context, $conf) {
// If unset it wants a generic, unfilled context, which is just NULL.
if (empty($context->data) || !isset($context->data->uid)) {
return ctools_context_create_empty('entity:profile2', NULL);
}
// Load user's profile and return a ctools context from it.
if ($profile = profile2_load_by_user($context->data, $conf['type'])) {
return ctools_context_create('entity:profile2', $profile);
}
// In case when we can't load a profile, just return an empty context.
return ctools_context_create_empty('entity:profile2', NULL);
}
/**
* Settings form for the relationship.
*/
function profile2_from_user_settings_form($form, &$form_state) {
$conf = $form_state['conf'];
$options = array();
foreach (profile2_get_types() as $type => $info) {
$options[$type] = $info->label;
}
$form['type'] = array(
'#type' => 'select',
'#title' => t('Select Profile2 Type'),
'#options' => $options,
'#default_value' => $conf['type'],
'#prefix' => '<div class="clearfix">',
'#suffix' => '</div>',
);
return $form;
}

View File

@@ -58,6 +58,17 @@ function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
'#default_value' => !empty($profile_type->data['registration']),
);
$user_roles = user_roles();
// Exclude anonymous user role.
unset($user_roles[DRUPAL_ANONYMOUS_RID]);
$form['data']['roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Roles'),
'#description' => t('Check user roles that should have this profile.'),
'#options' => $user_roles,
'#default_value' => !empty($profile_type->data['roles']) ? $profile_type->data['roles'] : array_keys($user_roles)
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
@@ -88,6 +99,7 @@ function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
* Form API submit callback for the type form.
*/
function profile2_type_form_submit(&$form, &$form_state) {
$form_state['values']['data']['roles'] = array_filter($form_state['values']['data']['roles']);
$profile_type = entity_ui_form_submit_build_entity($form, $form_state);
// Save and go back.
$profile_type->save();

View File

@@ -138,7 +138,7 @@ function hook_profile2_view($profile, $view_mode, $langcode) {
*
* @see hook_entity_view_alter()
*/
function hook_profile2_view_alter($build) {
function hook_profile2_view_alter(&$build) {
if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
// Change its weight.
$build['an_additional_field']['#weight'] = -10;

View File

@@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains functions for Profile Delete.
*/
/**
* Confirm form for deleting own profile.
*/
function profile2_delete_confirm_form($form, &$form_state, $profile) {
global $user;
if (isset($profile) && is_object($profile)) {
$form_state += array('profile2' => $profile);
if ($user->uid === $profile->uid) {
$confirm_question = t('Are you sure you want to delete your own %label profile ?', array('%label' => $profile->label));
}
elseif (user_access('administer profiles')) {
$user_account = user_load($profile->uid);
if (!empty($user_account)) {
$confirm_question = t("Are you sure you want to delete profile %label of user %user?", array('%label' => $profile->label, '%user' => $user_account->name));
}
}
return confirm_form($form, $confirm_question, 'user/' . $profile->uid);
}
}
/**
* Confirm form submit for deleting own profile.
*/
function profile2_delete_confirm_form_submit($form, &$form_state) {
$profile = isset($form_state['profile2']) ? $form_state['profile2'] : '';
if (isset($profile) && is_object($profile)) {
$profile->delete();
drupal_set_message(t('Deleted %label.', array('%label' => $profile->label)));
}
$form_state['redirect'] = 'user/' . $profile->uid;
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* @file
* Contains integration with Devel generate modules.
* Provides possibility to generate dummy profiles for users.
*/
/**
* Form that allows to generate a user profiles with dummy data.
*/
function profile2_generate_form($form, &$form_state) {
// Generate a list with available profile types.
$profile_types = profile2_get_types();
foreach ($profile_types as $id => $type) {
$profile_types[$id] = $type->label;
}
$form['profile2_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Generate profiles of the following types'),
'#description' => t('Select profile type(s) to create profile. If no types are selected, profiles of all types will be generated.'),
'#options' => $profile_types,
);
$roles_list = user_roles(TRUE);
// Don't show authorized role.
unset($roles_list[DRUPAL_AUTHENTICATED_RID]);
$form['profile2_roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Generate profiles for following user roles'),
'#options' => $roles_list,
);
$form['profile2_generate_limit'] = array(
'#type' => 'textfield',
'#title' => t('Maximum number of profiles per type'),
'#element_validate' => array('element_validate_integer_positive'),
'#default_value' => 50,
'#size' => 10,
);
$form['profile2_delete'] = array(
'#type' => 'checkbox',
'#title' => t('Delete existing profiles'),
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Generate'),
);
return $form;
}
/**
* Submit callback for profile2_generate_form().
* Generates profiles for users.
*/
function profile2_generate_form_submit($form, &$form_state) {
$values = $form_state['values'];
// Initial database query that allows to fetch all user ids except anonymous.
$query = db_select('users', 'u');
$query->fields('u', array('uid'));
$query->condition('u.uid', 0, '<>');
// If user selected certain user roles - we need to filter by them.
$roles_selected = array_filter($values['profile2_roles']);
if (!empty($roles_selected)) {
$query->innerJoin('users_roles', 'ur', 'ur.uid = u.uid');
$query->condition('ur.rid', $roles_selected);
}
// Fetch uids for which profiles should be generated.
$uids = $query->execute()->fetchCol('uid');
// Delete all profiles before generation.
if (!empty($values['profile2_delete'])) {
$profile_ids = db_select('profile')
->fields('profile', array('pid'))
->execute()
->fetchCol('pid');
profile2_delete_multiple($profile_ids);
// Set message that indicates how much profiles were deleted.
$message = format_plural(count($profile_ids), t('1 profile was deleted.'), t('@count profiles were deleted.'));
drupal_set_message($message);
}
$new_pids = array();
if (!empty($uids)) {
// Get selected profile types. Load them all if no profile type was chosen.
$profile_types = array_filter($values['profile2_types']);
if (empty($profile_types)) {
$profile_types = profile2_get_types();
}
// Generate user-defined amount of certain profile types.
foreach ($profile_types as $profile_type_name => $profile_type) {
$counter = 0;
$uids_to_generate = $uids;
while ($counter < $values['profile2_generate_limit'] && !empty($uids_to_generate)) {
$uid = array_shift($uids_to_generate);
// If user already has profile of certain type - skip the generation for it.
if (profile2_load_by_user($uid, $profile_type_name)) {
continue;
}
$profile2 = entity_create('profile2', array('type' => $profile_type_name, 'uid' => $uid));
// Populate all core fields on behalf of field.module.
module_load_include('fields.inc', 'devel_generate');
module_load_include('inc', 'devel_generate');
devel_generate_fields($profile2, 'profile2', $profile2->type);
// Set profile language.
$profile2->language = LANGUAGE_NONE;
// Create new profile of the certain type.
$new_pids[] = entity_save('profile2', $profile2);
// Increase counter of generated profiles of certain type.
$counter++;
}
}
}
// Show message that indicates how much profiles were created.
$message = format_plural(count($new_pids), '1 profile were generated', '@count profiles were generated.');
drupal_set_message($message);
}

View File

@@ -6,9 +6,8 @@ files[] = profile2.info.inc
files[] = profile2.test
dependencies[] = entity
configure = admin/structure/profiles
; Information added by drupal.org packaging script on 2012-12-26
version = "7.x-1.3"
; Information added by Drupal.org packaging script on 2019-01-01
version = "7.x-1.6"
core = "7.x"
project = "profile2"
datestamp = "1356482021"
datestamp = "1546363384"

View File

@@ -16,7 +16,7 @@ function profile2_install() {
'type' => 'main',
'label' => t('Main profile'),
'weight' => 0,
'data' => array('registration' => TRUE, 'use_page' => TRUE),
'data' => array('registration' => TRUE),
));
$type->save();
user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('edit own main profile', 'view own main profile'));
@@ -81,6 +81,9 @@ function profile2_schema() {
'columns' => array('type' => 'type'),
),
),
'unique keys' => array(
'user_profile_type' => array('type', 'uid'),
),
'primary key' => array('pid'),
);
@@ -143,6 +146,22 @@ function profile2_schema() {
return $schema;
}
/**
* Implements hook_uninstall()
*/
function profile2_uninstall() {
// Select all available profile2 bundles from the database directly, instead
// of entity_load() call. See https://drupal.org/node/1330598 for details.
$types = db_select('profile_type', 'p')
->fields('p')
->execute()
->fetchAllAssoc('type');
foreach ($types as $name => $type) {
field_attach_delete_bundle('profile2', $name);
}
}
/**
* Add in the exportable entity db columns as required by the entity API.
*/
@@ -204,3 +223,46 @@ function profile2_update_7102() {
'not null' => FALSE,
));
}
/**
* Remove duplicate profile records in batches of 50.
*/
function profile2_update_7103(&$sandbox) {
// Query to get duplicate profiles.
$query = db_select('profile', 'p1');
$query->distinct();
$query->fields('p1', array('pid'));
$query->join('profile', 'p2', 'p1.type = p2.type AND p1.uid = p2.uid AND p1.label = p2.label AND p1.pid < p2.pid');
// Setup initial batch variables.
if (!isset($sandbox['progress'])) {
// The number of duplicate profiles deleted so far.
$sandbox['progress'] = 0;
// Total number of duplicate profiles that will be deleted.
$sandbox['total'] = $query->execute()->rowCount();
}
// Query the next 50 profiles to be deleted.
$query->range(0, 50);
$result = $query->execute();
// Update progress of removing duplicate profiles.
$sandbox['progress'] = $sandbox['progress'] + $result->rowCount();
// Delete duplicate profiles.
profile2_delete_multiple($result->fetchCol());
// Update batch status.
$sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['total']) ? TRUE : ($sandbox['progress'] / $sandbox['total']);
if ($sandbox['#finished']) {
return t('@total duplicate profiles were removed from the system.', array('@total' => $sandbox['progress']));
}
}
/**
* The combination of profile type and uid should be unique.
*/
function profile2_update_7104() {
db_add_unique_key('profile', 'user_profile_type', array('type', 'uid'));
}

View File

@@ -105,6 +105,39 @@ 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().
*/
@@ -135,6 +168,9 @@ function profile2_permission() {
"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;
@@ -212,24 +248,17 @@ function profile2_load_by_user($account, $type_name = NULL) {
$uid = is_object($account) ? $account->uid : $account;
if (!isset($cache[$uid])) {
if (empty($type_name)) {
$profiles = profile2_load_multiple(FALSE, array('uid' => $uid));
// Cache ids for further lookups.
$cache[$uid] = array();
foreach ($profiles as $pid => $profile) {
$cache[$uid][$profile->type] = $pid;
}
return $profiles ? array_combine(array_keys($cache[$uid]), $profiles) : array();
}
$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];
}
@@ -308,8 +337,25 @@ function profile2_type_delete(ProfileType $type) {
}
/**
* Implements hook_profile2_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)) {
@@ -317,6 +363,8 @@ function profile2_profile2_type_delete(ProfileType $type) {
if ($pids) {
profile2_delete_multiple($pids);
}
// @see profile2_profile2_type_insert()
variable_set('menu_rebuild_needed', TRUE);
}
}
@@ -329,10 +377,10 @@ function profile2_user_view($account, $view_mode, $langcode) {
if (profile2_access('view', $profile)) {
$account->content['profile_' . $type] = array(
'#type' => 'user_profile_category',
'#title' => $profile->label,
'#title' => $profile_type->getTranslation('label'),
'#prefix' => '<a id="profile-' . $profile->type . '"></a>',
);
$account->content['profile_' . $type]['view'] = $profile->view('account');
$account->content['profile_' . $type]['view'] = $profile->view($view_mode);
}
}
}
@@ -345,18 +393,46 @@ function profile2_user_view($account, $view_mode, $langcode) {
* @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.
*/
@@ -366,8 +442,15 @@ function profile2_form_user_register_form_alter(&$form, &$form_state) {
if (empty($form_state['profiles'][$type_name])) {
$form_state['profiles'][$type_name] = profile2_create(array('type' => $type_name));
}
profile2_attach_form($form, $form_state);
// Wrap each profile form in a fieldset.
}
}
// 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')),
@@ -399,13 +482,17 @@ function profile2_attach_form(&$form, &$form_state) {
$form['profile_' . $profile->type]['#parents'] = array('profile_' . $profile->type);
field_attach_form('profile2', $profile, $form['profile_' . $profile->type], $form_state);
if (count(field_info_instances('profile2', $profile->type)) == 0) {
$form['profile_' . $profile->type]['message'] = array(
'#access' => user_access('administer profile types'),
'#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'))),
);
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().
@@ -416,7 +503,42 @@ function profile2_attach_form(&$form, &$form_state) {
}
}
$form['#validate'][] = 'profile2_form_validate_handler';
$form['#submit'][] = 'profile2_form_submit_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';
}
}
/**
@@ -435,6 +557,99 @@ function profile2_form_validate_handler(&$form, &$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.
*
@@ -531,9 +746,26 @@ function profile2_category_access($account, $type_name) {
* @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';
}
@@ -555,8 +787,7 @@ function profile2_access($op, $profile = NULL, $account = NULL) {
* Implements hook_profile2_access().
*/
function profile2_profile2_access($op, $profile = NULL, $account = NULL) {
// Don't grant access for users to delete their profile.
if (isset($profile) && ($type_name = $profile->type) && $op != 'delete') {
if (isset($profile) && ($type_name = $profile->type)) {
if (user_access("$op any $type_name profile", $account)) {
return TRUE;
}
@@ -701,12 +932,11 @@ class Profile extends Entity {
}
public function defaultLabel() {
if (module_exists('profile2_i18n')) {
// Run the label through i18n_string() using the profile2_type label
// context, so the default label (= the type's label) gets translated.
return entity_i18n_string('profile2:profile2_type:' . $this->type . ':label', $this->label);
}
return $this->label;
// 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) {
@@ -719,6 +949,19 @@ class Profile extends Entity {
}
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)) {
@@ -726,12 +969,12 @@ class Profile extends Entity {
}
$this->changed = REQUEST_TIME;
parent::save();
// Update the static cache from profile2_load_by_user().
// 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());
if (isset($cache[$this->uid])) {
$cache[$this->uid][$this->type] = $this->pid;
}
unset($cache[$this->uid]);
return parent::save();
}
}
@@ -808,7 +1051,7 @@ function profile2_view($profile, $view_mode = 'full', $langcode = NULL, $page =
* profiles.
*/
function profile2_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
if ($form['instance']['entity_type']['#value'] == 'profile2') {
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.'),
@@ -829,15 +1072,17 @@ function profile2_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
* Implements hook_field_access().
*/
function profile2_field_access($op, $field, $entity_type, $profile = NULL, $account = NULL) {
if ($entity_type == 'profile2' && $op == 'view' && !empty($field['settings']['profile2_private']) && !user_access('administer profiles', $account)) {
// For profiles, deny general view access for private fields.
if (!isset($profile)) {
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;
}
// Also deny view access, if someone else views a private field.
$account = isset($account) ? $account : $GLOBALS['user'];
if ($account->uid != $profile->uid) {
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;
}
}
}
}
@@ -879,3 +1124,72 @@ function profile2_user_get_properties($account, array $options, $name) {
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();
}
}
}
}

View File

@@ -62,8 +62,10 @@ class Profile2CRUDTestCase extends DrupalWebTestCase {
profile2_save($profile);
$profiles = profile2_load_by_user($user1);
$this->assertEqual($profiles['test']->label(), 'label', 'Created and loaded profile 1.');
$this->assertEqual($profiles['test2']->label(), 'label2', 'Created and loaded profile 2.');
$label = t('@type profile for @user', array('@type' => 'label', '@user' => format_username($user1)));
$label2 = t('@type profile for @user', array('@type' => 'label2', '@user' => format_username($user1)));
$this->assertEqual($profiles['test']->label(), $label, 'Created and loaded profile 1.');
$this->assertEqual($profiles['test2']->label(), $label2, 'Created and loaded profile 2.');
// Test looking up from static cache works also.
$profiles = profile2_load_by_user($user1);
@@ -134,4 +136,30 @@ class Profile2CRUDTestCase extends DrupalWebTestCase {
$this->assertText(check_plain($edit['profile_main[profile_fullname][und][0][value]']), 'Profile displayed.');
}
/**
* Tests optional access parameters.
*/
function testAccess() {
global $user;
$admin_user = $this->drupalCreateUser(array('administer profiles'));
$user2 = $this->drupalCreateUser();
// Create profiles for the admin user.
$profile = profile2_create(array('type' => 'test', 'uid' => $admin_user->uid));
profile2_save($profile);
// Make sure access is denied to the profile.
$this->drupalLogin($user2);
$this->drupalGet('user/' . $admin_user->uid . '/edit/main');
$this->assertText(t('Access denied'), 'Access has been denied.');
// Set the global user to ensure the defaults are respected.
$user = $user2;
// Ensure optional parameters check access for the current logged in user.
$this->assertFalse(profile2_access('edit'), 'No edit access for user 2');
// Ensure optional parameters check access for the admin user.
$this->assertTrue(profile2_access('edit', NULL, $admin_user), 'No edit access for user 1');
}
}