diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 7400f9cce..b54283dbf 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -10,6 +10,7 @@ - New Friends Account module added providing a better interface into WebGUI's friends system - rfe: Event hover detail exclusions (#8761) - rfe: Database Link test (#513) + - rfe: User Profile Privacy Settings (#507) - users now have granular control of whether or not their profile fields are viewable - admin settings still apply - The Display Account and My Purchases interfaces have been added to the new Account system - Added a new macro which returns the self deactivation link if the setting is enabled. - Added a new macro which returns the back to site link diff --git a/docs/upgrades/packages-7.6.4/root_import_account.wgpkg b/docs/upgrades/packages-7.6.4/root_import_account.wgpkg index afb5c559f..a9ca7293c 100644 Binary files a/docs/upgrades/packages-7.6.4/root_import_account.wgpkg and b/docs/upgrades/packages-7.6.4/root_import_account.wgpkg differ diff --git a/docs/upgrades/upgrade_7.6.3-7.6.4.pl b/docs/upgrades/upgrade_7.6.3-7.6.4.pl index ea73e82d2..88bdf20f0 100644 --- a/docs/upgrades/upgrade_7.6.3-7.6.4.pl +++ b/docs/upgrades/upgrade_7.6.3-7.6.4.pl @@ -24,6 +24,7 @@ use WebGUI::Asset::Wobject::Survey; use WebGUI::Asset::Wobject::Survey::SurveyJSON; use WebGUI::Asset::Wobject::Survey::ResponseJSON; use WebGUI::ProfileField; +use JSON; my $toVersion = '7.6.4'; my $quiet; # this line required @@ -37,9 +38,48 @@ addPosMode($session); fixFriendsGroups( $session ); upgradeAccount( $session ); removeProcessRecurringPaymentsFromConfig( $session ); +addExtendedProfilePrivileges( $session ); addStorageUrlMacro( $session ); finish($session); # this line required +#---------------------------------------------------------------------------- +sub addExtendedProfilePrivileges { + my $session = shift; + + print qq{\tExtending User Profile Privileges..} if !$quiet; + + my $userProfDesc = $session->db->buildHashRef('describe userProfileData'); + if(grep { $_ =~ /^wg_privacySettings/ } keys %{$userProfDesc}) { + $session->db->write("alter table userProfileData drop column wg_privacySettings"); + } + $session->db->write("alter table userProfileData add wg_privacySettings longtext"); + + my $fields = WebGUI::ProfileField->getFields($session); + + my $users = $session->db->buildArrayRef("select userId from users"); + foreach my $userId (@{$users}) { + my $hash = {}; + foreach my $field (@{$fields}) { + if($field->getId eq "publicEmail") { + my $u = WebGUI::User->new($session,$userId); + $hash->{$field->getId} = $u->profileField("publicEmail") ? "all" : "none"; + next; + } + $hash->{$field->getId} = $field->isViewable ? "all" : "none"; + } + my $json = JSON->new->encode($hash); + $session->db->write("update userProfileData set wg_privacySettings=? where userId=?",[$json,$userId]); + } + + #Delete the public email field + my $publicEmail = WebGUI::ProfileField->new($session,"publicEmail"); + if(defined $publicEmail) { + $publicEmail->delete; + } + + print qq{Finished\n} if !$quiet; +} + #---------------------------------------------------------------------------- sub addPosMode { @@ -447,7 +487,6 @@ sub upgradeAccount { $setting->add("shopStyleTemplateId",""); #Use the userStyle by default $setting->add("shopLayoutTemplateId","aUDsJ-vB9RgP-AYvPOy8FQ"); - #Add inbox changes $session->db->write(q{ create table inbox_messageState ( diff --git a/lib/WebGUI/Account/Profile.pm b/lib/WebGUI/Account/Profile.pm index a8fbb4c2b..f819e69bb 100644 --- a/lib/WebGUI/Account/Profile.pm +++ b/lib/WebGUI/Account/Profile.pm @@ -353,37 +353,50 @@ sub www_edit { #Initialize the category template loop which gets filled inside the loop $var->{'profile_category_loop'} = []; + #Cache the privacy settings + my $privacySettingsHash = WebGUI::ProfileField->getPrivacyOptions($session); #Get the editable categories my $categories = WebGUI::ProfileCategory->getCategories($session, { editable => 1 } ); foreach my $category (@{ $categories } ) { my @fields = (); - use Data::Dumper; foreach my $field (@{ $category->getFields( { editable => 1 } ) }) { - my $fieldId = $field->getId; - my $fieldLabel = $field->getLabel; - my $fieldForm = $field->formField({ extras=>$self->getExtrasStyle($field,\@errorFields,$user->profileField($fieldId)) }); - my $fieldSubtext = $field->isRequired ? "*" : undef; - my $fieldExtras = $field->getExtras; - my $fieldPrivacy = WebGUI::Form::radoList($session,{ + my $fieldId = $field->getId; + my $fieldLabel = $field->getLabel; + my $fieldForm = $field->formField({ extras=>$self->getExtrasStyle($field,\@errorFields,$user->profileField($fieldId)) }); + my $fieldRequired = $field->isRequired; + my $fieldExtras = $field->getExtras; + my $fieldViewable = $field->isViewable; + my $rawPrivacySetting = $user->getProfileFieldPrivacySetting($fieldId); + my $fieldPrivacySetting = $privacySettingsHash->{$rawPrivacySetting}; + + my $fieldPrivacy = WebGUI::Form::selectBox($session,{ name => "privacy_$fieldId", - options => $field->getPrivacyOptions($session), - value => $user->getProfileFieldPrivacySetting($fieldId) + options => $privacySettingsHash, + value => $rawPrivacySetting, + extras => (!$fieldViewable) ? " disabled" : "" }); #Create a seperate template var for each field - $var->{'profile_field_'.$fieldId.'_form' } = $fieldForm; - $var->{'profile_field_'.$fieldId.'_label' } = $fieldLabel; - $var->{'profile_field_'.$fieldId.'_subtext'} = $fieldSubtext; - $var->{'profile_field_'.$fieldId.'_extras' } = $fieldExtras; - $var->{'profile_field_'.$fieldId.'_privacy'} = $fieldPrivacy; + my $fieldBase = 'profile_field_'.$fieldId; + $var->{$fieldBase.'_form' } = $fieldForm; + $var->{$fieldBase.'_label' } = $fieldLabel; + $var->{$fieldBase.'_required' } = $fieldRequired; + $var->{$fieldBase.'_extras' } = $fieldExtras; + $var->{$fieldBase.'_privacy_form' } = $fieldPrivacy; + $var->{$fieldBase.'_field_viewable' } = $fieldViewable; + $var->{$fieldBase.'_privacy_setting' } = $fieldPrivacySetting; + $var->{$fieldBase.'_privacy_is_'.$rawPrivacySetting } = $rawPrivacySetting; push(@fields, { - 'profile_field_id' => $fieldId, - 'profile_field_form' => $fieldForm, - 'profile_field_label' => $fieldLabel, - 'profile_field_subtext' => $field->isRequired ? "*" : undef, - 'profile_field_extras' => $field->getExtras, - 'profile_field_privacy' => $fieldPrivacy, + 'profile_field_id' => $fieldId, + 'profile_field_form' => $fieldForm, + 'profile_field_label' => $fieldLabel, + 'profile_field_required' => $fieldRequired, + 'profile_field_extras' => $fieldExtras, + 'profile_field_viewable' => $fieldViewable, + 'profile_field_privacy_form' => $fieldPrivacy, + 'profile_field_privacy_setting' => $fieldPrivacySetting, + 'profile_field_privacy_is_'.$rawPrivacySetting => $rawPrivacySetting, }); } @@ -420,7 +433,15 @@ sub www_editSave { push (@{$retHash->{errors}},@{$retHash->{warnings}}); unless(scalar(@{$retHash->{errors}})) { - $session->user->updateProfileFields( $retHash->{profile} ); + my $profile = $retHash->{profile}; + my $privacy = {}; + foreach my $fieldName (keys %{$profile}) { + $session->user->profileField($fieldName,$profile->{$fieldName}); + my $privacySetting = $session->form->get("privacy_".$fieldName); + next unless $privacySetting; + $privacy->{$fieldName} = $privacySetting; + } + $session->user->setProfileFieldPrivacySetting($privacy); } #Store the category the error occurred in the object for reference @@ -457,6 +478,8 @@ sub www_view { #Overwrite these $var->{'user_full_name' } = $user->getWholeName; $var->{'user_member_since' } = $user->dateCreated; + $var->{'profile_user_id' } = $user->userId; + $var->{'can_edit_profile' } = $uid eq $session->user->userId; #Check user privileges unless ($user->profileIsViewable($session->user)) { @@ -469,28 +492,38 @@ sub www_view { ); } + #Cache the privacy settings + my $privacySettingsHash = WebGUI::ProfileField->getPrivacyOptions($session); $var->{'profile_category_loop' } = []; foreach my $category (@{WebGUI::ProfileCategory->getCategories($session,{ visible => 1})}) { my @fields = (); foreach my $field (@{$category->getFields({ visible => 1 })}) { - my $fieldId = $field->getId; - my $fieldLabel = $field->getLabel; - my $fieldValue = $field->formField(undef,2,$user); - my $fieldRaw = $user->profileField($fieldId);; + next unless ($user->canViewField($field->getId,$session->user)); + my $rawPrivacySetting = $user->getProfileFieldPrivacySetting($field->getId); + my $privacySetting = $privacySettingsHash->{$rawPrivacySetting}; + my $fieldId = $field->getId; + my $fieldLabel = $field->getLabel; + my $fieldValue = $field->formField(undef,2,$user); + my $fieldRaw = $user->profileField($fieldId);; #Create a seperate template var for each field - $var->{'profile_field_'.$fieldId.'_label' } = $fieldLabel; - $var->{'profile_field_'.$fieldId.'_value' } = $fieldValue; - $var->{'profile_field_'.$fieldId.'_raw' } = $fieldRaw; - + my $fieldBase = 'profile_field_'.$fieldId; + $var->{$fieldBase.'_label' } = $fieldLabel; + $var->{$fieldBase.'_value' } = $fieldValue; + $var->{$fieldBase.'_raw' } = $fieldRaw; + $var->{$fieldBase.'_privacySetting' } = $privacySetting; + $var->{$fieldBase.'_privacy_is_'.$rawPrivacySetting } = "true"; push(@fields, { - 'profile_field_id' => $fieldId, - 'profile_field_is_'.$fieldId => "true", - 'profile_field_label' => $fieldLabel, - 'profile_field_value' => $fieldValue, - 'profile_field_raw' => $fieldRaw + 'profile_field_id' => $fieldId, + 'profile_field_is_'.$fieldId => "true", + 'profile_field_label' => $fieldLabel, + 'profile_field_value' => $fieldValue, + 'profile_field_raw' => $fieldRaw, + 'profile_field_privacySetting' => $privacySetting, + 'profile_field_privacy_is_'.$rawPrivacySetting => "true", }); } - + #Don't bother displaying the category if there's nothing in it. + next unless (scalar(@fields)); #Append the category variables $self->appendCategoryVars($var,$category,\@fields); } @@ -503,8 +536,6 @@ sub www_view { my $privacySetting = $user->profileField("publicProfile") || "none"; $var->{'profile_privacy_'.$privacySetting } = "true"; - $var->{'profile_user_id' } = $user->userId; - $var->{'can_edit_profile' } = $uid eq $session->user->userId; $var->{'acceptsPrivateMessages'} = $user->acceptsPrivateMessages($session->user->userId); $var->{'acceptsFriendsRequests'} = $user->acceptsFriendsRequests($session->user); diff --git a/lib/WebGUI/ProfileField.pm b/lib/WebGUI/ProfileField.pm index bb56e8b36..ac1fa7900 100755 --- a/lib/WebGUI/ProfileField.pm +++ b/lib/WebGUI/ProfileField.pm @@ -66,7 +66,7 @@ Return true iff fieldName is reserved and therefore not usable as a profile fiel sub isReservedFieldName { my $class = shift; my $fieldName = shift; - return isIn($fieldName, ('func', 'op')); + return isIn($fieldName, ('func', 'op', 'wg_privacySettings')); } #------------------------------------------------------------------- @@ -337,7 +337,10 @@ Returns a WebGUI::ProfileCategory object for the category that this profile fiel sub getCategory { my $self = shift; - return WebGUI::ProfileCategory->new($self->session,$self->get("profileCategoryId")); + unless ($self->{_category}) { + $self->{_category} = WebGUI::ProfileCategory->new($self->session,$self->get("profileCategoryId")); + } + return $self->{_category}; } @@ -440,6 +443,27 @@ sub getFormControlClass { #------------------------------------------------------------------- +=head2 getPrivacyOptions ( session ) + +Class method which returns a hash reference containing the privacy options available. + +=cut + +sub getPrivacyOptions { + my $class = shift; + my $session = shift; + my $i18n = WebGUI::International->new($session); + tie my %hash, "Tie::IxHash"; + %hash = ( + all => $i18n->get('user profile field private message allow label'), + friends => $i18n->get('user profile field private message friends only label'), + none => $i18n->get('user profile field private message allow none label'), + ); + return \%hash; +} + +#------------------------------------------------------------------- + =head2 getRequiredFields ( session ) Returns an array reference of WebGUI::ProfileField objects that are marked "required". This is a class method. @@ -512,7 +536,7 @@ Returns a boolean indicating whether this field may be editable by a user. sub isEditable { my $self = shift; - return $self->get("editable") || $self->isRequired; + return $self->getCategory->isEditable && ($self->get("editable") || $self->isRequired); } @@ -575,8 +599,8 @@ Returns a boolean indicating whether this field may be viewed by a user. =cut sub isViewable { - my $self = shift; - return $self->get("visible"); + my $self = shift; + return $self->getCategory->isViewable && $self->get("visible"); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/User.pm b/lib/WebGUI/User.pm index c87419ce6..5e026246d 100644 --- a/lib/WebGUI/User.pm +++ b/lib/WebGUI/User.pm @@ -21,6 +21,7 @@ use WebGUI::DatabaseLink; use WebGUI::Exception; use WebGUI::Utility; use WebGUI::Operation::Shared; +use JSON; =head1 NAME @@ -258,6 +259,42 @@ sub canUseAdminMode { #------------------------------------------------------------------- +=head2 canViewField ( field, user) + +Returns whether or not the user passed in can view the field value for the user. +This will only check the user level privileges. + +=head3 field + +Field to check privileges on + +=head3 user + +User to check field privileges for + +=cut + +sub canViewField { + my $self = shift; + my $session = $self->session; + my $field = shift; + my $user = shift; + + return 0 unless ($field && $user); + #Always true for yourself + return 1 if ($self->userId eq $user->userId); + + my $privacySetting = $self->getProfileFieldPrivacySetting($field); + return 0 unless (WebGUI::Utility::isIn($privacySetting,qw(all none friends))); + return 1 if ($privacySetting eq "all"); + return 0 if ($privacySetting eq "none"); + + #It's friends so return whether or not user is a friend + return WebGUI::Friends->new($session,$self)->isFriend($user->userId); +} + +#------------------------------------------------------------------- + =head2 dateCreated ( ) Returns the epoch for when this user was created. @@ -449,6 +486,42 @@ sub getGroupIdsRecursive { return [ keys %groupIds ]; } +#------------------------------------------------------------------- + +=head2 getProfileFieldPrivacySetting ( [field ]) + +Returns the privacy setting for the field passed in. If no field is passed in the entire hash is returned + +=head3 field + +Field to get privacy setting for. + +=cut + +sub getProfileFieldPrivacySetting { + my $self = shift; + my $session = $self->session; + my $field = shift; + + unless ($self->{_privacySettings}) { + #Look it up manually because we want to cache this separately. + my $privacySettings = $session->db->quickScalar( + q{select wg_privacySettings from userProfileData where userId=?}, + [$self->userId] + ); + $privacySettings = "{}" unless $privacySettings; + $self->{_privacySettings} = JSON->new->decode($privacySettings); + } + + return $self->{_privacySettings} unless ($field); + + #No privacy settings returned the privacy setting field + return "none" if($field eq "wg_privacySettings"); + + return $self->{_privacySettings}->{$field}; +} + + #------------------------------------------------------------------- =head2 getProfileUrl ( [page] ) @@ -806,6 +879,7 @@ sub profileField { my $fieldName = shift; my $value = shift; my $db = $self->session->db; + return "" if ($fieldName eq "wg_privacySettings"); # this is a special internal field, don't try to process it. if (!exists $self->{_profile}{$fieldName} && !$self->session->db->quickScalar("SELECT COUNT(*) FROM userProfileField WHERE fieldName = ?", [$fieldName])) { $self->session->errorHandler->warn("No such profile field: $fieldName"); return undef; @@ -902,6 +976,43 @@ sub session { } +#------------------------------------------------------------------- + +=head2 setProfileFieldPrivacySetting ( settings ) + +Sets the profile field privacy settings + +=head3 settings + +hash ref containing the field and it's corresponding privacy setting + +=cut + +sub setProfileFieldPrivacySetting { + my $self = shift; + my $session = $self->session; + my $settings = shift; + + return undef unless scalar(keys %{$settings}); + + #Get the current settings + my $currentSettings = $self->getProfileFieldPrivacySetting; + + foreach my $fieldId (keys %{$settings}) { + my $privacySetting = $settings->{$fieldId}; + next unless (WebGUI::Utility::isIn($privacySetting,qw(all none friends))); + $currentSettings->{$fieldId} = $settings->{$fieldId}; + } + + #Store the data in the database + my $json = JSON->new->encode($currentSettings); + $session->db->write("update userProfileData set wg_privacySettings=? where userId=?",[$json,$self->userId]); + + #Recache the current settings + $self->{_privacySettings} = $currentSettings; +} + + #------------------------------------------------------------------- =head2 status ( [ value ] ) diff --git a/lib/WebGUI/i18n/English/Account_Profile.pm b/lib/WebGUI/i18n/English/Account_Profile.pm index 83c2e926c..5f3bc7d42 100644 --- a/lib/WebGUI/i18n/English/Account_Profile.pm +++ b/lib/WebGUI/i18n/English/Account_Profile.pm @@ -160,7 +160,15 @@ our $I18N = { lastUpdated => 1225724810, }, + 'set by admin' => { + message => q{site administrator has set this field to not be visible }, + lastUpdated => 1225724810, + }, + 'required field' => { + message => q{required }, + lastUpdated => 1225724810, + }, }; 1; diff --git a/lib/WebGUI/i18n/English/WebGUI.pm b/lib/WebGUI/i18n/English/WebGUI.pm index c2c009969..bcdfcfc1a 100755 --- a/lib/WebGUI/i18n/English/WebGUI.pm +++ b/lib/WebGUI/i18n/English/WebGUI.pm @@ -3603,17 +3603,17 @@ LongTruncOk=1

}, 'user profile field private message allow label' => { - message => q|Allow All|, + message => q|Public|, lastUpdated => 1181019679, }, 'user profile field private message friends only label' => { - message => q|Allow From My Friends Only|, + message => q|Friends Only|, lastUpdated => 1181019679, }, 'user profile field private message allow none label' => { - message => q|Allow None|, + message => q|Private|, lastUpdated => 1181019679, }, @@ -4306,6 +4306,12 @@ Users may override this setting in their profile. context => q{i18n label for time duration in WebGUI::DateTime}, }, + 'profile privacy settings' => { + message => q{Privacy Settings}, + lastUpdated => 1226706547, + context => q{i18n label for time duration in WebGUI::DateTime}, + }, + }; 1;