From 6bc2c3395467bfa1592c9f78091805c1577e0356 Mon Sep 17 00:00:00 2001 From: JT Smith Date: Tue, 6 Jul 2004 21:58:58 +0000 Subject: [PATCH] enhanced user/group management interfaces --- docs/changelog/6.x.x.txt | 4 +- lib/WebGUI/Grouping.pm | 25 ++-- lib/WebGUI/Operation/Group.pm | 170 ++++++++++++++++++++---- lib/WebGUI/Operation/User.pm | 237 ++++++++++++++++++++++------------ lib/WebGUI/Session.pm | 2 + lib/WebGUI/Style.pm | 5 + 6 files changed, 324 insertions(+), 119 deletions(-) diff --git a/docs/changelog/6.x.x.txt b/docs/changelog/6.x.x.txt index 861bcea74..05372d4c1 100644 --- a/docs/changelog/6.x.x.txt +++ b/docs/changelog/6.x.x.txt @@ -53,8 +53,10 @@ - Added a better user search mechanism, which works well even with 100,000 users. - Made some minor changes to grouping lookups that resulted in an average of - 14 less database queries per page when in admin mode which results in a 30% + 18 less database queries per page when in admin mode which results in a 32% performance gain while in admin mode. + - Changed the group and user management interfaces to work and scale better + with thousands of users and groups. 6.0.3 diff --git a/lib/WebGUI/Grouping.pm b/lib/WebGUI/Grouping.pm index 47d58061d..483b2c807 100755 --- a/lib/WebGUI/Grouping.pm +++ b/lib/WebGUI/Grouping.pm @@ -233,18 +233,14 @@ sub getGroupsForUser { my $clause = "and expireDate>".time() if ($withoutExpired); if ($userId eq "") { return []; - } elsif ($session{gotGroupsForUser}{$userId} == 1) { - my @groups; - foreach my $gid (keys %{$session{isInGroup}{$userId}}) { - push(@groups,$gid); - } - return \@groups; + } elsif (exists $session{gotGroupsForUser}{$userId}) { + return $session{gotGroupsForUser}{$userId}; } else { my @groups = WebGUI::SQL->buildArray("select groupId from groupings where userId=$userId $clause"); foreach my $gid (@groups) { $session{isInGroup}{$userId}{$gid} = 1; } - $session{gotGroupsForUser}{$userId} = 1; + $session{gotGroupsForUser}{$userId} = \@groups; return \@groups; } } @@ -275,9 +271,12 @@ sub getGroupsInGroup { my $groupId = shift; my $isRecursive = shift; my $loopCount = shift; - my $groupsLookedUp = shift; - my $extraWhere = "and groupId not in (".join(",",@{$groupsLookedUp}).")" if (defined @{$groupsLookedUp}); - my $groups = WebGUI::SQL->buildArrayRef("select groupId from groupGroupings where inGroup=$groupId $extraWhere"); + if ($isRecursive && exists $session{gotGroupsInGroup}{recursive}{$groupId}) { + return $session{gotGroupsInGroup}{recursive}{$groupId}; + } elsif (exists $session{gotGroupsInGroup}{recursive}{$groupId}) { + return $session{gotGroupsInGroup}{direct}{$groupId}; + } + my $groups = WebGUI::SQL->buildArrayRef("select groupId from groupGroupings where inGroup=$groupId"); if ($isRecursive) { $loopCount++; if ($loopCount > 99) { @@ -286,11 +285,13 @@ sub getGroupsInGroup { } my @groupsOfGroups = @$groups; foreach my $group (@$groups) { - my $gog = getGroupsInGroup($group,1,$loopCount,\@groupsOfGroups); + my $gog = getGroupsInGroup($group,1,$loopCount); push(@groupsOfGroups, @$gog); } + $session{gotGroupsInGroup}{recursive}{$groupId} = \@groupsOfGroups; return \@groupsOfGroups; - } + } + $session{gotGroupsInGroup}{direct}{$groupId} = $groups; return $groups; } diff --git a/lib/WebGUI/Operation/Group.pm b/lib/WebGUI/Operation/Group.pm index 128869455..5d444ef28 100644 --- a/lib/WebGUI/Operation/Group.pm +++ b/lib/WebGUI/Operation/Group.pm @@ -17,12 +17,14 @@ use WebGUI::DatabaseLink; use WebGUI::DateTime; use WebGUI::Group; use WebGUI::Grouping; +use WebGUI::Form; use WebGUI::FormProcessor; use WebGUI::HTMLForm; use WebGUI::Icon; use WebGUI::International; use WebGUI::Mail; use WebGUI::Operation::Shared; +use WebGUI::Operation::User; use WebGUI::Paginator; use WebGUI::Privilege; use WebGUI::Session; @@ -67,6 +69,75 @@ sub _submenu { return menuWrapper($_[0],\%menu); } + +#------------------------------------------------------------------- +sub doGroupSearch { + my $op = shift; + my $returnPaginator = shift; + my $groupFilter = shift; + push(@{$groupFilter},0); + my $keyword = $session{scratch}{groupSearchKeyword}; + if ($session{scratch}{groupSearchModifier} eq "startsWith") { + $keyword .= "%"; + } elsif ($session{scratch}{groupSearchModifier} eq "contains") { + $keyword = "%".$keyword."%"; + } else { + $keyword = "%".$keyword; + } + $keyword = quote($keyword); + my $sql = "select groupId,groupName,description from groups where isEditable=1 and (groupName like $keyword or description like $keyword) + and groupId not in (".join(",",@{$groupFilter}).") order by groupName"; + if ($returnPaginator) { + my $p = WebGUI::Paginator->new(WebGUI::URL::page($op)); + $p->setDataByQuery($sql); + return $p; + } else { + my $sth = WebGUI::SQL->read($sql); + return $sth; + } +} + + +#------------------------------------------------------------------- +sub getGroupSearchForm { + my $op = shift; + my $params = shift; + WebGUI::Session::setScratch("groupSearchKeyword",$session{form}{keyword}); + WebGUI::Session::setScratch("groupSearchModifier",$session{form}{modifier}); + my $output = '
'; + my $f = WebGUI::HTMLForm->new(1); + foreach my $key (keys %{$params}) { + $f->hidden( + -name=>$key, + -value=>$params->{$key} + ); + } + $f->hidden("op",$op); + $f->hidden( + -name=>"doit", + -value=>1 + ); + $f->selectList( + -name=>"modifier", + -value=>([$session{scratch}{groupSearchModifier}] || ["contains"]), + -options=>{ + startsWith=>WebGUI::International::get("starts with"), + contains=>WebGUI::International::get("contains"), + endsWith=>WebGUI::International::get("ends with") + } + ); + $f->text( + -name=>"keyword", + -value=>$session{scratch}{groupSearchKeyword}, + -size=>15 + ); + $f->submit(WebGUI::International::get(170)); + $output .= $f->print; + $output .= '
'; + return $output; +} + + #------------------------------------------------------------------- sub www_addGroupsToGroupSave { return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); @@ -297,6 +368,35 @@ sub www_emailGroupSend { #------------------------------------------------------------------- sub www_listGroups { + return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); + my $output = helpIcon("groups manage"); + $output .= '

'.WebGUI::International::get(89).'

'; + $output .= getGroupSearchForm("listGroups"); + my ($groupCount) = WebGUI::SQL->quickArray("select count(*) from groups"); + return _submenu($output) unless ($session{form}{doit} || $groupCount<250); + $output .= ''; + $output .= ''; + my $p = doGroupSearch("op=listGroups",1); + foreach my $row (@{$p->getPageData}) { + my ($userCount) = WebGUI::SQL->quickArray("select count(*) from groupings where groupId=".$row->{groupId}); + $output .= ' + + + + + + '; + } + $output .= '
'.WebGUI::International::get(84).'' + .WebGUI::International::get(85).'' + .WebGUI::International::get(748).'
{groupId}).'">'.$row->{groupName}.''.$row->{description}.''.$userCount.'
'; + $output .= $p->getBarTraditional; + return _submenu($output); +} + + +#------------------------------------------------------------------- +sub www_listGroups2 { return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); my ($output, $p, $sth, @data, @row, $i, $userCount); $output = helpIcon("groups manage"); @@ -398,41 +498,67 @@ sub www_manageGroupsInGroup { #------------------------------------------------------------------- sub www_manageUsersInGroup { return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); - my ($output, $sth, %hash); - tie %hash, 'Tie::CPHash'; - $output = '

'.WebGUI::International::get(88).'

'; + my $output = '

'.WebGUI::International::get(88).'

'; + $output .= WebGUI::Form::formHeader() + .WebGUI::Form::hidden({ + name=>"gid", + value=>$session{form}{gid} + }) + .WebGUI::Form::hidden({ + name=>"return", + value=>"manageUsersInGroup" + }) + .WebGUI::Form::hidden({ + name=>"op", + value=>"deleteGrouping" + }); + $output .= ' + + '; + my $p = WebGUI::Paginator->new("op=manageUsersInGroup&gid=".$session{form}{gid}); + $p->setDataByQuery("select users.username,users.userId,groupings.expireDate + from groupings,users where groupings.groupId=$session{form}{gid} and groupings.userId=users.userId + order by users.username"); + foreach my $row (@{$p->getPageData}) { + $output .= ''; + $output .= ''; + $output .= ''; + } + $output .= '
'.WebGUI::International::get(50).''.WebGUI::International::get(369).'
' + .WebGUI::Form::checkbox({ + name=>"uid", + value=>$row->{userId} + }) + .deleteIcon('op=deleteGrouping&return=manageUsersInGroup&uid='.$row->{userId}.'&gid='.$session{form}{gid}) + .editIcon('op=editGrouping&uid='.$row->{userId}.'&gid='.$session{form}{gid}) + .''.$row->{username}.''.epochToHuman($row->{expireDate},"%z").'
'; + $output .= '

'.WebGUI::International::get(976).'

'; + $output .= WebGUI::Operation::User::getUserSearchForm("manageUsersInGroup",{gid=>$session{form}{gid}}); + my ($userCount) = WebGUI::SQL->quickArray("select count(*) from users"); + return _submenu($output) unless ($session{form}{doit} || $userCount < 250); my $f = WebGUI::HTMLForm->new; $f->hidden("gid",$session{form}{gid}); $f->hidden("op","addUsersToGroupSave"); my $existingUsers = WebGUI::Grouping::getUsersInGroup($session{form}{gid}); push(@{$existingUsers},"1"); - my $users = WebGUI::SQL->buildHashRef("select userId,username from users where status='Active' and userId not in (".join(",",@{$existingUsers}).") order by username"); + my %users; + tie %users, "Tie::IxHash"; + my $sth = WebGUI::Operation::User::doUserSearch("op=manageUsersInGroup&gid=".$session{form}{gid},0,$existingUsers); + while (my $data = $sth->hashRef) { + $users{$data->{userId}} = $data->{username}; + $users{$data->{userId}} .= " (".$data->{email}.")" if ($data->{email}); + } + $sth->finish; $f->selectList( -name=>"users", -label=>WebGUI::International::get(976), - -options=>$users, + -options=>\%users, -multiple=>1, -size=>7 ); $f->submit; $output .= $f->print; - $output .= ' - - '; - $sth = WebGUI::SQL->read("select users.username,users.userId,groupings.expireDate - from groupings,users where groupings.groupId=$session{form}{gid} and groupings.userId=users.userId - order by users.username"); - while (%hash = $sth->hash) { - $output .= ''; - $output .= ''; - $output .= ''; - } - $sth->finish; - $output .= '
 '.WebGUI::International::get(50).''.WebGUI::International::get(369).'
' - .deleteIcon('op=deleteGrouping&return=manageUsersInGroup&uid='.$hash{userId}.'&gid='.$session{form}{gid}) - .editIcon('op=editGrouping&uid='.$hash{userId}.'&gid='.$session{form}{gid}) - .'' - .$hash{username}.''.epochToHuman($hash{expireDate},"%z").'
'; return _submenu($output); } diff --git a/lib/WebGUI/Operation/User.pm b/lib/WebGUI/Operation/User.pm index 09f7bfef7..179ec2ce7 100644 --- a/lib/WebGUI/Operation/User.pm +++ b/lib/WebGUI/Operation/User.pm @@ -26,6 +26,7 @@ use WebGUI::Paginator; use WebGUI::Privilege; use WebGUI::Session; use WebGUI::SQL; +use WebGUI::Style; use WebGUI::URL; use WebGUI::User; use WebGUI::Utility; @@ -60,13 +61,101 @@ sub _submenu { return menuWrapper($_[0],\%menu); } + +#------------------------------------------------------------------- +sub doUserSearch { + my $op = shift; + my $returnPaginator = shift; + my $userFilter = shift; + push(@{$userFilter},0); + my $selectedStatus; + if ($session{scratch}{userSearchStatus}) { + $selectedStatus = "status='".$session{scratch}{userSearchStatus}."'"; + } else { + $selectedStatus = "status like '%'"; + } + my $keyword = $session{scratch}{userSearchKeyword}; + if ($session{scratch}{userSearchModifier} eq "startsWith") { + $keyword .= "%"; + } elsif ($session{scratch}{userSearchModifier} eq "contains") { + $keyword = "%".$keyword."%"; + } else { + $keyword = "%".$keyword; + } + $keyword = quote($keyword); + my $sql = "select users.userId, users.username, users.status, users.dateCreated, users.lastUpdated, + email.fieldData as email from users left join userProfileData email on users.userId=email.userId and email.fieldName='email' + where $selectedStatus and (users.username like ".$keyword." or email.fieldData like ".$keyword.") + and users.userId not in (".join(",",@{$userFilter}).") order by users.username"; + if ($returnPaginator) { + my $p = WebGUI::Paginator->new(WebGUI::URL::page($op)); + $p->setDataByQuery($sql); + return $p; + } else { + my $sth = WebGUI::SQL->read($sql); + return $sth; + } +} + + +#------------------------------------------------------------------- +sub getUserSearchForm { + my $op = shift; + my $params = shift; + WebGUI::Session::setScratch("userSearchKeyword",$session{form}{keyword}); + WebGUI::Session::setScratch("userSearchStatus",$session{form}{status}); + WebGUI::Session::setScratch("userSearchModifier",$session{form}{modifier}); + my $output = '
'; + my $f = WebGUI::HTMLForm->new(1); + $f->hidden("op",$op); + foreach my $key (keys %{$params}) { + $f->hidden( + -name=>$key, + -value=>$params->{$key} + ); + } + $f->hidden( + -name=>"doit", + -value=>1 + ); + $f->selectList( + -name=>"modifier", + -value=>([$session{scratch}{userSearchModifier}] || ["contains"]), + -options=>{ + startsWith=>WebGUI::International::get("starts with"), + contains=>WebGUI::International::get("contains"), + endsWith=>WebGUI::International::get("ends with") + } + ); + $f->text( + -name=>"keyword", + -value=>$session{scratch}{userSearchKeyword}, + -size=>15 + ); + $f->selectList( + -name => "status", + -value => [$session{scratch}{userSearchStatus} || "users.status like '%'"], + -options=> { + "" => WebGUI::International::get(821), + Active => WebGUI::International::get(817), + Deactivated => WebGUI::International::get(818), + Selfdestructed => WebGUI::International::get(819) + } + ); + $f->submit(WebGUI::International::get(170)); + $output .= $f->print; + $output .= '
'; + return $output; +} + + #------------------------------------------------------------------- sub www_addUser { my ($output, $f, $cmd, $html, %status); return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3) || WebGUI::Grouping::isInGroup(11)); $output .= helpIcon("user add/edit"); $output .= '

'.WebGUI::International::get(163).'

'; - $output .= WebGUI::Form::_javascriptFile("swapLayers.js"); + WebGUI::Style::setScript($session{config}{extrasURL}."/swapLayers.js", {language=>"JavaScript"}); $output .= ''; if ($session{form}{op} eq "addUserSave") { @@ -167,8 +256,12 @@ sub www_deleteGrouping { if (($session{user}{userId} == $session{form}{uid} || $session{form}{uid} == 3) && $session{form}{gid} == 3) { return WebGUI::Privilege::vitalComponent(); } - my $u = WebGUI::User->new($session{form}{uid}); - $u->deleteFromGroups([$session{form}{gid}]); + my @users = $session{cgi}->param('uid'); + my @groups = $session{cgi}->param("gid"); + foreach my $user (@users) { + my $u = WebGUI::User->new($user); + $u->deleteFromGroups(\@groups); + } if ($session{form}{return} eq "manageUsersInGroup") { return WebGUI::Operation::Group::www_manageUsersInGroup(); } @@ -242,7 +335,7 @@ sub www_editUser { return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); my ($output, $f, $u, $cmd, $html, %status); $u = WebGUI::User->new($session{form}{uid}); - $output .= WebGUI::Form::_javascriptFile("swapLayers.js"); + WebGUI::Style::setScript($session{config}{extrasURL}."/swapLayers.js", {language=>"JavaScript"}); $output .= ''; $output .= helpIcon("user add/edit"); $output .= '

'.WebGUI::International::get(168).'

'; @@ -315,41 +408,64 @@ sub www_editUserSave { #------------------------------------------------------------------- sub www_editUserGroup { return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); - my ($output, $f, $groups, $sth, %hash); + my %hash; tie %hash, 'Tie::CPHash'; - $output .= '

'.WebGUI::International::get(372).'

'; - $f = WebGUI::HTMLForm->new; + my $output = '

'.WebGUI::International::get(372).'

'; + $output .= WebGUI::Form::formHeader() + .WebGUI::Form::hidden({ + name=>"uid", + value=>$session{form}{uid} + }) + .WebGUI::Form::hidden({ + name=>"op", + value=>"deleteGrouping" + }); + $output .= ''; + my $p = WebGUI::Paginator->new("op=editUserGroups&uid=".$session{form}{uid}); + $p->setDataByQuery("select groups.groupId,groups.groupName,groupings.expireDate + from groupings,groups where groupings.groupId=groups.groupId and groupings.userId=$session{form}{uid} order by groups.groupName"); + foreach my $row (@{$p->getPageData}) { + $output .= ''; + $output .= ''; + $output .= ''; + } + $output .= '
'.WebGUI::International::get(84). + ''.WebGUI::International::get(369).'
' + .WebGUI::Form::checkbox({ + name=>"gid", + value=>$row->{groupId} + }) + .deleteIcon('op=deleteGrouping&uid='.$session{form}{uid}.'&gid='.$row->{groupId}) + .editIcon('op=editGrouping&uid='.$session{form}{uid}.'&gid='.$row->{groupId}) + .''.$row->{groupName}.''.epochToHuman($row->{expireDate},"%z").'
'; + $output .= '

'.WebGUI::International::get(605).'

'; + $output .= WebGUI::Operation::Group::getGroupSearchForm("editUserGroup",{uid=>$session{form}{uid}}); + my ($groupCount) = WebGUI::SQL->quickArray("select count(*) from users"); + return _submenu($output) unless ($session{form}{doit} || $groupCount < 250); + my $f = WebGUI::HTMLForm->new; $f->hidden("op","addUserToGroupSave"); $f->hidden("uid",$session{form}{uid}); - $groups = WebGUI::Grouping::getGroupsForUser($session{form}{uid}); - push(@$groups,1); #visitors - push(@$groups,2); #registered users - push(@$groups,7); #everyone - $f->group( + my $existingGroups = WebGUI::Grouping::getGroupsForUser($session{form}{uid}); + push(@$existingGroups,1); #visitors + push(@$existingGroups,2); #registered users + push(@$existingGroups,7); #everyone + my %groups; + tie %groups, "Tie::IxHash"; + my $sth = WebGUI::Operation::Group::doGroupSearch("op=editUserGroup&uid=".$session{form}{uid},0,$existingGroups); + while (my $data = $sth->hashRef) { + $groups{$data->{groupId}} = $data->{groupName}; + } + $sth->finish; + $f->selectList( -name=>"groups", - -excludeGroups=>$groups, -label=>WebGUI::International::get(605), - -size=>5, - -multiple=>1 + -size=>7, + -multiple=>1, + -options=>\%groups ); $f->submit; $output .= $f->print; - $output .= '

'; - $sth = WebGUI::SQL->read("select groups.groupId,groups.groupName,groupings.expireDate - from groupings,groups where groupings.groupId=groups.groupId and - groupings.userId=$session{form}{uid} order by groups.groupName"); - while (%hash = $sth->hash) { - $output .= ''; - $output .= ''; - $output .= ''; - } - $sth->finish; - $output .= '
'.WebGUI::International::get(89). - ''.WebGUI::International::get(84). - ''.WebGUI::International::get(369).'
' - .deleteIcon('op=deleteGrouping&uid='.$session{form}{uid}.'&gid='.$hash{groupId}) - .editIcon('op=editGrouping&uid='.$session{form}{uid}.'&gid='.$hash{groupId}) - .''.$hash{groupName}.''.epochToHuman($hash{expireDate},"%z").'
'; return _submenu($output); } @@ -466,48 +582,18 @@ sub www_editUserProfileSave { #------------------------------------------------------------------- sub www_listUsers { return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3)); - WebGUI::Session::setScratch("userSearchKeyword",$session{form}{keyword}); - WebGUI::Session::setScratch("userSearchStatus",$session{form}{status}); - my ($data, $rows, $p, %status, $selectedStatus); + my %status; my $output = helpIcon("users manage"); $output .= '

'.WebGUI::International::get(149).'

'; - $output .= '
'; + $output .= getUserSearchForm("listUsers"); + my ($userCount) = WebGUI::SQL->quickArray("select count(*) from users"); + return _submenu($output) unless ($session{form}{doit} || $userCount<250); tie %status, 'Tie::IxHash'; %status = ( - "" => WebGUI::International::get(821), Active => WebGUI::International::get(817), Deactivated => WebGUI::International::get(818), Selfdestructed => WebGUI::International::get(819) ); - my $f = WebGUI::HTMLForm->new(1); - $f->hidden("op","listUsers"); - $f->hidden( - -name=>"doit", - -value=>1 - ); - $f->selectList( - -name=>"modifier", - -value=>([$session{form}{modifier}] || ["contains"]), - -options=>{ - startsWith=>WebGUI::International::get("starts with"), - contains=>WebGUI::International::get("contains"), - endsWith=>WebGUI::International::get("ends with") - } - ); - $f->text( - -name=>"keyword", - -value=>$session{scratch}{userSearchKeyword}, - -size=>15 - ); - $f->selectList( - -name => "status", - -value => [$session{form}{status} || "users.status like '%'"], - -options=> \%status - ); - $f->submit(WebGUI::International::get(170)); - $output .= $f->print; - $output .= '
'; - return _submenu($output) unless ($session{form}{doit}); $output .= ''; $output .= ' @@ -518,25 +604,8 @@ sub www_listUsers { '; - if ($session{scratch}{userSearchStatus}) { - $selectedStatus = "status='".$session{scratch}{userSearchStatus}."'"; - } else { - $selectedStatus = "status like '%'"; - } - my $keyword = $session{scratch}{userSearchKeyword}; - if ($session{form}{modifier} eq "startsWith") { - $keyword .= "%"; - } elsif ($session{form}{modifier} eq "contains") { - $keyword = "%".$keyword."%"; - } else { - $keyword = "%".$keyword; - } - $p = WebGUI::Paginator->new(WebGUI::URL::page("op=listUsers")); - $p->setDataByQuery("select users.userId, users.username, users.status, users.dateCreated, users.lastUpdated, - email.fieldData as email from users left join userProfileData email on users.userId=email.userId and email.fieldName='email' - where $selectedStatus and (users.username like ".quote($keyword)." or email.fieldData like ".quote($keyword).") order by users.username"); - $rows = $p->getPageData; - foreach $data (@$rows) { + my $p = doUserSearch("listUsers",1); + foreach my $data (@{$p->getPageData}) { $output .= ''; $output .= ''; $output .= '
'.WebGUI::International::get(816).''.WebGUI::International::get(429).' '.WebGUI::International::get(434).'
'.$status{$data->{status}}.'