diff --git a/lib/WebGUI/Account/Inbox.pm b/lib/WebGUI/Account/Inbox.pm index 5e09d3e54..fa60f4706 100644 --- a/lib/WebGUI/Account/Inbox.pm +++ b/lib/WebGUI/Account/Inbox.pm @@ -1533,7 +1533,7 @@ sub www_view { my $userSql = $inbox->getMessageSql(undef, { 'select' => <new($session, 'Account_Inbox'); diff --git a/lib/WebGUI/Admin.pm b/lib/WebGUI/Admin.pm index bd06dcee0..3cf077fc4 100644 --- a/lib/WebGUI/Admin.pm +++ b/lib/WebGUI/Admin.pm @@ -467,7 +467,7 @@ sub www_findUser { } my $sql = 'SELECT userId, CONCAT(firstName,lastName) AS name, username, alias, avatar - FROM users JOIN userProfileData USING (userId) WHERE ' . join( ' || ', @places ); + FROM users WHERE ' . join( ' || ', @places ); my $params = [ ( $query ) x scalar @places ]; my $sth = $db->read( $sql, $params ); diff --git a/lib/WebGUI/Asset/Wobject/InOutBoard.pm b/lib/WebGUI/Asset/Wobject/InOutBoard.pm index d10f2ef5f..c74032385 100644 --- a/lib/WebGUI/Asset/Wobject/InOutBoard.pm +++ b/lib/WebGUI/Asset/Wobject/InOutBoard.pm @@ -89,7 +89,6 @@ sub _fetchNames { my %nameHash; my $sql = "SELECT users.username, users.userId, firstName, lastName FROM users -LEFT JOIN userProfileData ON users.userId=userProfileData.userId WHERE users.userId=?"; my $sth = $self->session->db->prepare($sql); foreach my $userId (@userIds) { @@ -337,7 +336,6 @@ sub www_selectDelegates { from users left join groupings on users.userId=groupings.userId left join InOutBoard on groupings.groupId=InOutBoard.inOutGroup - left join userProfileData on users.userId=userProfileData.userId left join InOutBoard_status on users.userId=InOutBoard_status.userId and InOutBoard_status.assetId=? where users.userId<>'1' diff --git a/lib/WebGUI/Asset/Wobject/ProjectManager.pm b/lib/WebGUI/Asset/Wobject/ProjectManager.pm index 0773b43e3..5cc5e0df6 100644 --- a/lib/WebGUI/Asset/Wobject/ProjectManager.pm +++ b/lib/WebGUI/Asset/Wobject/ProjectManager.pm @@ -385,7 +385,6 @@ sub _userSearchQuery { my $query = <<"SQL"; SELECT 'user' AS resourceKind, users.userId AS resourceId FROM users - LEFT JOIN userProfileData ON users.userId = userProfileData.userId WHERE (LOWER(lastName) LIKE ? OR LOWER(firstName) LIKE ? OR LOWER(users.username) LIKE ?) AND (users.userId NOT IN $excludePlaceholders) ORDER BY lastName, firstName diff --git a/lib/WebGUI/Asset/Wobject/UserList.pm b/lib/WebGUI/Asset/Wobject/UserList.pm index 2b113e44c..095a5f674 100644 --- a/lib/WebGUI/Asset/Wobject/UserList.pm +++ b/lib/WebGUI/Asset/Wobject/UserList.pm @@ -179,7 +179,7 @@ sub getAlphabetSearchLoop { my $htmlEncodedLetter = encode_entities($letter); my $searchURL = "?searchExact_".$fieldName."=".$letter."%25"; my $hasResults; - my $users = $self->session->db->read("select userId from userProfileData where `$fieldName` like '".$letter."%'"); + my $users = $self->session->db->read("select userId from users join userProfileData using (userId) where `$fieldName` like '".$letter."%'"); while (my $user = $users->hashRef){ my $showGroupId = $self->showGroupId; if ($showGroupId eq '0' || ($showGroupId && $self->isInGroup($showGroupId,$user->{userId}))){ @@ -430,10 +430,10 @@ sub view { } # Query user profile data. Exclude the visitor account and users that have been deactivated. - $sql = "select distinct users.userId, users.userName, userProfileData.publicProfile "; + $sql = "select distinct users.userId, users.userName, users.publicProfile "; # Include remaining profile fields in the query foreach my $profileField (@profileFields){ - $sql .= ", userProfileData." . $dbh->quote_identifier($profileField->{fieldName}); + $sql .= ", " . $dbh->quote_identifier($profileField->{fieldName}); } $sql .= " from users"; $sql .= " left join userProfileData using(userId) where users.userId != '1' and users.status = 'active'"; @@ -447,14 +447,14 @@ sub view { # Normal search with one keyword in a limited number of fields foreach my $profileField (@profileFields){ if ($form->process('includeInSearch_'.$profileField->{fieldName})){ - push(@profileSearchFields, 'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName}) + push(@profileSearchFields, $dbh->quote_identifier($profileField->{fieldName}) .' like '. $dbh->quote('%'.$form->process('search').'%')); } } } else{ # Normal search with one keyword in all fields - $constraint = "(".join(' or ', map {'userProfileData.'.$dbh->quote_identifier($_->{fieldName}) + $constraint = "(".join(' or ', map {$dbh->quote_identifier($_->{fieldName}) .' like '.$dbh->quote('%'.$form->process('search').'%')} @profileFields).")"; } } @@ -464,14 +464,14 @@ sub view { # Exact search with one keyword in a limited number of fields foreach my $profileField (@profileFields){ if ($form->process('includeInSearch_'.$profileField->{fieldName})){ - push(@profileSearchFields,'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName}) + push(@profileSearchFields,$dbh->quote_identifier($profileField->{fieldName}) .' like '.$dbh->quote($form->process('search'))); } } } else{ # Exact search with one keyword in all fields - $constraint = "(".join(' or ', map {'userProfileData.'.$dbh->quote_identifier($_->{fieldName}) + $constraint = "(".join(' or ', map {$dbh->quote_identifier($_->{fieldName}) .' like ' . $dbh->quote($form->process('searchExact'))} @profileFields).")"; } } @@ -480,11 +480,11 @@ sub view { foreach my $profileField (@profileFields){ # Exact search has precedence over normal search if ($form->process('searchExact_'.$profileField->{fieldName})){ - push(@profileSearchFields,'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName}) + push(@profileSearchFields,$dbh->quote_identifier($profileField->{fieldName}) .' like '. $dbh->quote($form->process('searchExact_'.$profileField->{fieldName}))); } elsif ($form->process('search_'.$profileField->{fieldName})){ - push(@profileSearchFields,'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName}) + push(@profileSearchFields,$dbh->quote_identifier($profileField->{fieldName}) .' like '. $dbh->quote('%'.$form->process('search_'.$profileField->{fieldName}))); } } diff --git a/lib/WebGUI/Auth/WebGUI.pm b/lib/WebGUI/Auth/WebGUI.pm index 9ccfd4f96..b704f1f18 100644 --- a/lib/WebGUI/Auth/WebGUI.pm +++ b/lib/WebGUI/Auth/WebGUI.pm @@ -997,7 +997,7 @@ sub www_profileRecoverPasswordFinish { my @fieldNames = keys %fieldValues; my @fieldValues = values %fieldValues; - my $wheres = join(' ', map{"AND upd.$fieldNames[$_] = ?"} (0..$#fieldNames)); + my $wheres = join(' ', map{"AND $fieldNames[$_] = ?"} (0..$#fieldNames)); $wheres .= ' AND u.username = ?' if defined $username; my $sql = "SELECT u.userId FROM users AS u JOIN userProfileData AS upd ON u.userId=upd.userId WHERE u.authMethod = ? $wheres"; my @userIds = $self->session->db->buildArray($sql, [$self->authMethod, @fieldValues, (defined($username)? ($username) : ())]); diff --git a/lib/WebGUI/Inbox.pm b/lib/WebGUI/Inbox.pm index b321597db..292b44c25 100644 --- a/lib/WebGUI/Inbox.pm +++ b/lib/WebGUI/Inbox.pm @@ -482,7 +482,7 @@ sub getMessageSql { $select =<form->get( 'showMessageOnLoginReset' ) ) { $session->db->write( - "UPDATE userProfileData SET showMessageOnLoginSeen=0" + "UPDATE users SET showMessageOnLoginSeen=0" ); $session->cache->clear; } diff --git a/lib/WebGUI/Operation/User.pm b/lib/WebGUI/Operation/User.pm index 04605104e..479917619 100644 --- a/lib/WebGUI/Operation/User.pm +++ b/lib/WebGUI/Operation/User.pm @@ -227,10 +227,9 @@ sub doUserSearch { $keyword = "%".$keyword; } my $sql = "select users.userId, users.username, users.status, users.dateCreated, users.lastUpdated, - userProfileData.email from users - left join userProfileData on users.userId=userProfileData.userId + users.email from users where $selectedStatus and (users.username like ? or alias like ? or email like ? - or firstName like ? or lastName like ?) + or firstName like ? or lastName like ? or CONCAT(firstName, ' ', lastName) LIKE ? ) and users.userId not in (".$session->db->quoteAndJoin($userFilter).") order by users.username"; if ($returnPaginator) { my $p = WebGUI::Paginator->new($session,$session->url->page("op=".$op)); diff --git a/lib/WebGUI/ProfileField.pm b/lib/WebGUI/ProfileField.pm index f3b535811..7e41881cb 100644 --- a/lib/WebGUI/ProfileField.pm +++ b/lib/WebGUI/ProfileField.pm @@ -607,7 +607,7 @@ sub isDuplicate { my $value = shift; my $userId = shift || $session->user->userId; - my $sql = qq{select count(*) from userProfileData where $fieldId = ? and userId <> ?}; + my $sql = qq{select count(*) from users join userProfileData using( userId ) where $fieldId = ? and userId <> ?}; my $duplicate = $session->db->quickScalar($sql,[$value, $userId]); return ($duplicate > 0); } @@ -918,7 +918,7 @@ sub set { } # If the fieldType has changed, modify the userProfileData column - if ($properties->{fieldType} ne $originalFieldType) { + if ($properties->{fieldType} ne $originalFieldType && !$self->isProtected) { # Create a copy of the new properties so we don't mess them up my $fieldClass = $self->getFormControlClass; eval { WebGUI::Pluggable::load($fieldClass) }; diff --git a/lib/WebGUI/User.pm b/lib/WebGUI/User.pm index 28bab2d24..e5159b895 100644 --- a/lib/WebGUI/User.pm +++ b/lib/WebGUI/User.pm @@ -98,7 +98,7 @@ sub _create { $privacy->{$field->get('fieldName')} = $privacySetting; } my $json = JSON->new->encode($privacy); - $session->db->write("update userProfileData set wg_privacySettings=? where userId=?",[$json,$userId]); + $session->db->write("update users set privacyFields=? where userId=?",[$json,$userId]); WebGUI::Group->new($session,2)->addUsers([$userId]); WebGUI::Group->new($session,7)->addUsers([$userId]); @@ -578,7 +578,13 @@ sub get { my $session = $self->session; if ( $field ) { + return if $field eq 'privacyFields'; if ( exists $self->{_user}->{$field} ) { + if ( !defined $self->{_user}->{$field} ) { + my $default = $session->db->quickScalar("SELECT dataDefault FROM userProfileField WHERE fieldName=?", [$field]); + $self->{_user}{$field} + = WebGUI::Operation::Shared::secureEval($session, $default); + } return $self->{_user}->{$field}; } else { @@ -606,7 +612,11 @@ sub get { "SELECT fieldName, dataDefault FROM userProfileField", ); for my $key ( keys %default ) { - if ( !exists $self->{_profile}{$key} ) { + if ( exists $self->{_user}->{$key} && !defined $self->{_user}->{$key} ) { + $self->{_user}{$key} + = WebGUI::Operation::Shared::secureEval($session, $default{$key}); + } + elsif ( !exists $self->{_user}->{$key} && !exists $self->{_profile}{$key} ) { $self->{_profile}{$key} = WebGUI::Operation::Shared::secureEval($session, $default{$key}); } @@ -788,7 +798,7 @@ sub getProfileFieldPrivacySetting { 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=?}, + q{select privacyFields from users where userId=?}, [$self->userId] ); $privacySettings = "{}" unless $privacySettings; @@ -798,7 +808,7 @@ sub getProfileFieldPrivacySetting { return $self->{_privacySettings} unless ($field); #No privacy settings returned the privacy setting field - return "none" if($field eq "wg_privacySettings"); + return "none" if($field eq "privacyFields"); return $self->{_privacySettings}->{$field}; } @@ -1092,7 +1102,6 @@ sub new { [$user{userId}] ); delete $profile{userId}; - delete $profile{wg_privacySettings}; # Fill in dataDefault my $default = $session->db->buildHashRef( @@ -1105,8 +1114,8 @@ sub new { } } - if (($profile{alias} =~ /^\W+$/ || $profile{alias} eq "") and $user{username}) { - $profile{alias} = $user{username}; + if (($user{alias} =~ /^\W+$/ || $user{alias} eq "") and $user{username}) { + $user{alias} = $user{username}; } $self->{_userId} = $userId; $self->{_user} = \%user, @@ -1138,7 +1147,7 @@ sub newByEmail { my $class = shift; my $session = shift; my $email = shift; - my ($id) = $session->dbSlave->quickArray("select userId from userProfileData where email=?",[$email]); + my ($id) = $session->dbSlave->quickArray("select userId from users where email=?",[$email]); my $user = $class->new($session, $id); return undef if ($user->isVisitor); # visitor is never valid for this method return undef unless $user->username; @@ -1304,7 +1313,7 @@ sub setProfileFieldPrivacySetting { #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]); + $session->db->write("update users set privacyFields=? where userId=?",[$json,$self->userId]); #Recache the current settings $self->{_privacySettings} = $currentSettings; @@ -1365,14 +1374,38 @@ name => value pairs of user properties and/or profile fields. Valid user properties: - authMethod - dateCreated - friendsGroup + authMethod - The default auth method for the user. DEPRECATED, all users + can be authed by all auth methods + dateCreated - The unix timestamp when the user was created + friendsGroup - The WebGUI::Group containing the user's friends karma - NOTE: To add karma, use the karma() method - lastUpdated - referringAffiliate + lastUpdated - The unix timestamp when the user was last updated + referringAffiliate - A WebGUI::User who referred the user status - One of "Activated", "Deactivated", or "Selfdestructed" - username + username - The username + + ableToBeFriend - Whether the user can be added as a friend + alias - Show this instead of the username + allowPrivateMessages - Whether this user can receive private messages + avatar - A WebGUI::Storage containing an avatar image + cellPhone - The user's cell number. Used for the SMS gateway + dateFormat - The desired date format. See WebGUI::DateTime for fields + email - The user's email + firstDayOfWeek - The preferred first day of the week + firstName - The first name + language - The user's i18n language + lastName - The last name + publicProfile - Whether the profile is visible to the public + receiveInboxEmailNotifications - Should inbox messages be sent to the user's email + receiveInboxSmsNotifications - Should inbox messages be sent to user's SMS + showMessageOnLoginSeen - How many times they've seen the login message + showOnline - Should we reveal if the user is online? + signature - The signature for private messages and forum posts + timeFormat - The desired time format. See WebGUI::DateTime for fields + timeZone - The user's time zone. All datetimes will appear local to this + toolbar - Customize the toolbar for an i18n language + uiLevel - The UI Level. Allow users to see more based on their comprehension + versionTagMode - Their version tag mode Anything else is a profile field. @@ -1401,7 +1434,8 @@ sub update { delete $properties->{userId}; # This is an internal field with its own api to set it - delete $properties->{wg_privacySettings}; + ### CHANGE THIS to be settable by update + delete $properties->{privacyFields}; # $self->{_user} contains all fields in `users` table my @userFields = (); diff --git a/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm b/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm index bf626844d..5b9ef502e 100644 --- a/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm +++ b/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm @@ -163,11 +163,11 @@ sub getSql { return < 0 diff --git a/lib/WebGUI/Workflow/Activity/NotifyAdminsWithOpenVersionTags.pm b/lib/WebGUI/Workflow/Activity/NotifyAdminsWithOpenVersionTags.pm index a078c53bd..ec66514e4 100644 --- a/lib/WebGUI/Workflow/Activity/NotifyAdminsWithOpenVersionTags.pm +++ b/lib/WebGUI/Workflow/Activity/NotifyAdminsWithOpenVersionTags.pm @@ -86,7 +86,7 @@ sub execute { SELECT email, count(distinct(tagId)) AS count FROM assetVersionTag JOIN assetData USING (tagId) - JOIN userProfileData ON assetVersionTag.createdBy = userProfileData.userId + JOIN users ON assetVersionTag.createdBy = users.userId WHERE isCommitted = 0 AND DATE_ADD(FROM_UNIXTIME(creationDate), INTERVAL $daysLeftOpen DAY) < NOW() GROUP BY userId @@ -108,7 +108,7 @@ sub _notify { my $i18n = shift; my $hostname = $self->session->config->get('sitename')->[0]; - my($from) = $self->session->db->quickScalar(" SELECT email FROM userProfileData WHERE userId = 3 "); + my($from) = $self->session->db->quickScalar(" SELECT email FROM users WHERE userId = 3 "); my $s = $dataHashRef->{count} > 1 ? 's' : ''; my $subject = sprintf($i18n->get('email subject'), $s, $hostname); diff --git a/share/upgrades/7.10.4-8.0.0/moveRequiredProfileFields.pl b/share/upgrades/7.10.4-8.0.0/moveRequiredProfileFields.pl new file mode 100644 index 000000000..fbf6df09f --- /dev/null +++ b/share/upgrades/7.10.4-8.0.0/moveRequiredProfileFields.pl @@ -0,0 +1,40 @@ + +use WebGUI::Upgrade::Script; + +use WebGUI::Pluggable; +use WebGUI::ProfileField; + +start_step "Move core profile fields to users table..."; + +my @fields = qw( ableToBeFriend alias allowPrivateMessages avatar cellPhone dateFormat + email firstDayOfWeek firstName language lastName publicProfile receiveInboxEmailNotifications + receiveInboxSmsNotifications showMessageOnLoginSeen showOnline signature timeFormat timeZone + toolbar uiLevel versionTagMode ); + +# Create the new columns +for my $fieldName ( @fields ) { + my $field = WebGUI::ProfileField->new( session, $fieldName ); + my $fieldClass = $field->getFormControlClass; + eval { WebGUI::Pluggable::load( $fieldClass ) }; + my $dbType = $fieldClass->getDatabaseFieldType; + session->db->write( sprintf q{ ALTER TABLE users ADD COLUMN `%s` %s }, $fieldName, $dbType ); +} + +# Update the table +my @pairs = map { q{`users`.`} . $_ . q{`=`userProfileData`.`} . $_ . q{`} } @fields; +session->db->write( + q{ UPDATE `users`,`userProfileData` SET } . join( ", ", @pairs ) . + q{ WHERE `users`.`userId` = `userProfileData`.`userId` } +); + +# Drop the old tables +for my $fieldName ( @fields ) { + session->db->write( qq{ ALTER TABLE userProfileData DROP COLUMN `$fieldName` } ); +} + +# Move not-profile fields in userProfileData +session->db->write( qq{ ALTER TABLE users ADD privacyFields LONGTEXT } ); +session->db->write( qq{ UPDATE users,userProfileData SET users.privacyFields = userProfileData.wg_privacySettings } ); +session->db->write( qq{ ALTER TABLE userProfileData DROP COLUMN wg_privacySettings } ); + +done; diff --git a/t/User.t b/t/User.t index 3ff3cbb74..ec0bdb370 100644 --- a/t/User.t +++ b/t/User.t @@ -13,6 +13,7 @@ use strict; use WebGUI::Test; use WebGUI::Session; #use Exception::Class; +use List::MoreUtils qw( uniq ); use WebGUI::User; use WebGUI::ProfileField; @@ -150,7 +151,7 @@ is( ); is( - $session->db->quickScalar("SELECT firstName FROM userProfileData WHERE userId=?",[$user->getId]), + $session->db->quickScalar("SELECT firstName FROM users WHERE userId=?",[$user->getId]), "John", "update() updates profile firstName", ); @@ -161,7 +162,7 @@ is( ); is( - $session->db->quickScalar("SELECT lastName FROM userProfileData WHERE userId=?",[$user->getId]), + $session->db->quickScalar("SELECT lastName FROM users WHERE userId=?",[$user->getId]), "Lumbergh", "update() updates profile lastName", ); @@ -188,7 +189,7 @@ ok( $user->update({ lastName => "Lumberg" }), is( - $session->db->quickScalar("SELECT lastName FROM userProfileData WHERE userId=?",[$user->getId]), + $session->db->quickScalar("SELECT lastName FROM users WHERE userId=?",[$user->getId]), "Lumberg", "update() updates lastName again", ); @@ -210,7 +211,7 @@ my $expectValues = { }; # expects all user properties and all profile fields -my @expectFields = ( +my @expectFields = uniq( $session->db->buildArray('DESCRIBE users'), $session->db->buildArray('SELECT fieldName FROM userProfileField'), ); @@ -244,9 +245,9 @@ is($user->profileField('notAProfileField'), undef, 'getting non-existant profile my $newProfileField = WebGUI::ProfileField->create($session, 'testField', {dataDefault => 'this is a test', fieldType => 'Text'}); is($user->profileField('testField'), 'this is a test', 'getting profile fields not cached in the user object returns the profile field default'); -ok(!$user->profileField('wg_privacySettings'), '... wg_privacySettings may not be retrieved'); -$user->profileField('wg_privacySettings', '{"email"=>"all"}'); -ok(!$user->profileField('wg_privacySettings'), '... wg_privacySettings may not be set'); +ok(!$user->profileField('privacyFields'), '... privacyFields may not be retrieved'); +$user->profileField('privacyFields', '{"email"=>"all"}'); +ok(!$user->profileField('privacyFields'), '... privacyFields may not be set'); ################################################################ # @@ -562,6 +563,8 @@ is( $buster->profileField('timeZone'), 'America/Chicago', 'buster received origi my $profileField = WebGUI::ProfileField->new($session, 'timeZone'); my %originalFieldData = %{ $profileField->get() }; +use Data::Dumper; +note( Dumper \%originalFieldData ); my %copiedFieldData = %originalFieldData; $copiedFieldData{'dataDefault'} = "'America/Hillsboro'"; $profileField->set(\%copiedFieldData); @@ -569,9 +572,12 @@ $profileField->set(\%copiedFieldData); is($profileField->get('dataDefault'), "'America/Hillsboro'", 'default timeZone set to America/Hillsboro'); # now let's make sure it has an extras field, and that we can get/set it. -$profileField->set( { extras => '' } ); +# DID YOU KNOW? you have to set everything otherwise things get messed up! +$copiedFieldData{ 'extras' } = ''; +$profileField->set( \%copiedFieldData ); is($profileField->getExtras, '', 'extras field for profileField'); -$profileField->set( { extras => '' } ); +$copiedFieldData{ 'extras' } = ''; +$profileField->set( \%copiedFieldData ); my $busterCopy = WebGUI::User->new($session, $buster->userId); @@ -930,8 +936,8 @@ is($neighbor->getProfileFieldPrivacySetting('email'), 'none', '...get and set 1 $neighbor->setProfileFieldPrivacySetting({email => 'only Tony'}); is($neighbor->getProfileFieldPrivacySetting('email'), 'none', '...set will not set invalid profile settings'); -is($admin->getProfileFieldPrivacySetting('publicEmail'), 'all', '...get on a user with existing settings'); -is($neighbor->getProfileFieldPrivacySetting('wg_privacySettings'), 'none', '...the privacy field always returns "none"'); +is($admin->getProfileFieldPrivacySetting('email'), 'all', '...get on a user with existing settings'); +is($neighbor->getProfileFieldPrivacySetting('privacyFields'), 'none', '...the privacy field always returns "none"'); ################################################################ #