diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 681566da3..dd48f1336 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -78,6 +78,7 @@ - Added more variables for Photo Resolutions - Added better handling of Pending albums and photos - added: Database link access for SQL Macro (Yung Han Khoe) + - Added UserList wobject (United Knowledge and Yung Han Khoe) 7.5.10 - fix: Syntax error in GetCsMail diff --git a/docs/upgrades/packages-7.5.11/root_import_userlist.wgpkg b/docs/upgrades/packages-7.5.11/root_import_userlist.wgpkg new file mode 100644 index 000000000..578044bd2 Binary files /dev/null and b/docs/upgrades/packages-7.5.11/root_import_userlist.wgpkg differ diff --git a/docs/upgrades/upgrade_7.5.10-7.5.11.pl b/docs/upgrades/upgrade_7.5.10-7.5.11.pl index 7952f8495..67f5ccd53 100644 --- a/docs/upgrades/upgrade_7.5.10-7.5.11.pl +++ b/docs/upgrades/upgrade_7.5.10-7.5.11.pl @@ -67,6 +67,7 @@ addAssetManager( $session ); removeSqlForm($session); migratePaymentPlugins( $session ); removeRecurringPaymentActivity( $session ); +addUserListWobject( $session ); finish($session); # this line required @@ -1475,6 +1476,31 @@ sub removeRecurringPaymentActivity { print "Done.\n"; } +#---------------------------------------------------------------------------- + +sub addUserListWobject { + my $session = shift; + print "\tInstall UserList wobject.\n" unless ($quiet); + $session->db->write("create table UserList ( + assetId varchar(22) not null, + revisionDate bigint(20), + templateId varchar(22), + showGroupId varchar(22), + hideGroupId varchar(22), + usersPerPage int(11), + alphabet text, + alphabetSearchField varchar(128), + showOnlyVisibleAsNamed int(11), + sortBy varchar(128), + sortOrder varchar(4), + overridePublicEmail int(11), + overridePublicProfile int(11), + PRIMARY KEY (`assetId`,`revisionDate`) + )"); + $session->config->addToArray("assets","WebGUI::Asset::Wobject::UserList"); + +} + # -------------- DO NOT EDIT BELOW THIS LINE -------------------------------- #---------------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/UserList.pm b/lib/WebGUI/Asset/Wobject/UserList.pm new file mode 100644 index 000000000..0ca105b5b --- /dev/null +++ b/lib/WebGUI/Asset/Wobject/UserList.pm @@ -0,0 +1,783 @@ +package WebGUI::Asset::Wobject::UserList; + +#$VERSION = "2.0.0"; + +use strict; +use warnings; +use HTML::Entities; +use Tie::CPHash; +use Tie::IxHash; +use WebGUI::Utility; +use WebGUI::Asset::Wobject; +use WebGUI::Operation::Shared; +use WebGUI::International; +use WebGUI::Pluggable; +use WebGUI::Form::Image; +use WebGUI::Form::File; +use base 'WebGUI::Asset::Wobject'; + +=head1 LEGAL + +Copyright 2004-2008 United Knowledge + +http://www.unitedknowledge.nl +developmentinfo@unitedknowledge.nl + +=head1 NAME + +Package WebGUI::Asset::Wobject::UserList + +=head1 DESCRIPTION + +This wobject gives a list of webgui users. +The username is always shown. +The userId is only shown in Admin mode. +The wobject also checks the publicProfile and publicEmail setting for each user. + +=head1 SYNOPSIS + +use WebGUI::Asset::Wobject::UserList; + +=cut + +#------------------------------------------------------------------- + +=head2 getAlphabetSearchLoop ( ) + +Returns an array ref that contains tmpl_vars for the Alphabet Search. + +=cut + +sub getAlphabetSearchLoop { + my $self = shift; + my $fieldName = shift || 'lastName'; + my $alphabet = shift; + my (@alphabet, @alphabetLoop); + $alphabet ||= "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"; + @alphabet = split(/,/,$alphabet); + foreach my $letter (@alphabet){ + my $htmlEncodedLetter = encode_entities($letter); + my $searchURL = "?searchExact_".$fieldName."=".$letter."%25"; + my $hasResults = $self->session->db->quickScalar("select if (" + ."(select count(*) from userProfileData where ".$fieldName." like '".$letter."%')<>0, 1, 0)"); + push @alphabetLoop, { + alphabetSearch_loop_label => $htmlEncodedLetter || $letter, + alphabetSearch_loop_hasResults => $hasResults, + alphabetSearch_loop_searchURL => $searchURL, + } + } + return \@alphabetLoop; +} + +#------------------------------------------------------------------- + +=head2 getFormElement ( data ) + +Returns the form element tied to this field. + +=head3 data + +A hashref containing the properties of this field. + +=cut + +sub getFormElement { + + my $self = shift; + my $data = shift; + my %param; + + $param{name} = $data->{name}; + my $name = $param{name}; + $param{value} = $data->{value} || WebGUI::Operation::Shared::secureEval($self->session,$data->{dataDefault}); + $param{fieldType} = $data->{fieldType}; + + if ($data->{fieldType} eq "Checkbox") { + $param{value} = ($data->{defaultValue} =~ /checked/xi) ? 1 : ""; + } + + if (WebGUI::Utility::isIn($data->{fieldType},qw(SelectList CheckList SelectBox Attachments SelectSlider))) { + my @defaultValues; + if ($self->session->form->param($name)) { + @defaultValues = $self->session->form->selectList($name); + } else { + foreach (split(/\n/x, $data->{value})) { + s/\s+$//x; # remove trailing spaces + push(@defaultValues, $_); + } + } + $param{value} = \@defaultValues; + } + + if ($data->{possibleValues}){ + my $values = WebGUI::Operation::Shared::secureEval($self->session,$data->{possibleValues}); + unless (ref $values eq 'HASH') { + if ($self->get('possibleValues') =~ /\S/) { + $self->session->errorHandler->warn("Could not get a hash out of possible values for profile field " + .$self->getId); + } + $values = {}; + } + $param{options} = $values; + } + + if ($data->{fieldType} eq "YesNo") { + if ($data->{defaultValue} =~ /yes/xi) { + $param{value} = 1; + } elsif ($data->{defaultValue} =~ /no/xi) { + $param{value} = 0; + } + } + + my $formElement = eval { WebGUI::Pluggable::instanciate("WebGUI::Form::". ucfirst $param{fieldType}, "new", +[$self->session, \%param ])}; + return $formElement->toHtml(); + +} + +#------------------------------------------------------------------- + +=head2 definition ( properties ) + +Defines wobject properties for UserList instances. + +=head3 properties + +A hash reference containing the properties of this wobject. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my %properties; + my $i18n = WebGUI::International->new($session, 'Asset_UserList'); + + my %profileFields; + tie %profileFields, 'Tie::IxHash'; + my $fields = $session->db->read("SELECT field.fieldName, field.label FROM userProfileField as field " + ."left join userProfileCategory as cat USING(profileCategoryId) ORDER BY cat.sequenceNumber, field.sequenceNumber"); + while (my $field = $fields->hashRef){ + my $label = WebGUI::Operation::Shared::secureEval($session,$field->{label}); + $profileFields{$field->{fieldName}} = $label; + } + + tie %properties, 'Tie::IxHash'; + %properties = ( + templateId =>{ + fieldType=>"template", + defaultValue=>'UserListTmpl0000001', + namespace=>'UserList', + tab=>"display", + }, + + showGroupId=>{ + fieldType=>"group", + defaultValue=>"7", + label=>$i18n->get("Group to show label"), + hoverHelp=>$i18n->get('Group to show description'), + tab=>"display", + }, + hideGroupId=>{ + fieldType=>"group", + defaultValue=>"3", + label=>$i18n->get("Group to hide label"), + hoverHelp=>$i18n->get('Group to hide description'), + tab=>"display", + }, + usersPerPage=>{ + fieldType=>"integer", + defaultValue=>"25", + tab=>"display", + hoverHelp=>$i18n->get('Users per page description'), + label=>$i18n->get("Users per page label"), + }, + alphabet=>{ + fieldType=>"text", + defaultValue=>"a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z", + tab=>"display", + label=>$i18n->get("alphabet label"), + hoverHelp=>$i18n->get('alphabet description'), + }, + alphabetSearchField=>{ + fieldType=>"selectBox", + defaultValue=>"lastName", + tab=>"display", + options=>\%profileFields, + label=>$i18n->get("alphabetSearchField label"), + hoverHelp=>$i18n->get('alphabetSearchField description'), + }, + showOnlyVisibleAsNamed=>{ + fieldType=>"yesNo", + defaultValue=>"0", + tab=>"display", + label=>$i18n->get("showOnlyVisibleAsNamed label"), + hoverHelp=>$i18n->get('showOnlyVisibleAsNamed description'), + }, + sortOrder =>{ + fieldType=>"selectBox", + defaultValue=>'asc', + tab=>'display', + options=>{ asc => $i18n->get('ascending'), + desc => $i18n->get('descending') }, + label=>$i18n->get('sort order'), + hoverHelp=>$i18n->get('sort order description'), + }, + sortBy =>{ + fieldType=>"selectBox", + defaultValue=>'lastName', + tab=>'display', + options=>\%profileFields, + label=>$i18n->get('sort by'), + hoverHelp=>$i18n->get('sort by description'), + }, + ); + + push(@{$definition}, { + assetName=>$i18n->get('assetName'), + icon=>'userlist.gif', + autoGenerateForms=>1, + tableName=>'UserList', + className=>'WebGUI::Asset::Wobject::UserList', + properties=>\%properties + }); + return $class->SUPER::definition($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 isInGroup ( [ groupId ] ) + +Returns a boolean (0|1) value signifying that the user has the required privileges. Always returns true for Admins. +The UserList Wobject has its own isInGroup method for performance reasons, instead of using the WebGUI::User API. +To use the API a User object would have to be created for every user. This in turn would mean to unnessecary hits on the +database. + +=head3 groupId + +The group that you wish to verify against the user. Defaults to group with Id 3 (the Admin group). + +=cut + +sub isInGroup { + my (@data, $groupId); + my ($self, $gid, $uid, $secondRun) = @_; + $gid = 3 unless (defined $gid); + ### The following several checks are to increase performance. If this section were removed, everything would continue to work as normal. + return 1 if ($gid eq '7'); # everyone is in the everyone group + return 1 if ($gid eq '1' && $uid eq '1'); # visitors are in the visitors group + return 1 if ($gid eq '2' && $uid ne '1'); # if you're not a visitor, then you're a registered user + ### Get data for auxillary checks. + my $isInGroup = $self->session->stow->get("isInGroup"); + ### Look to see if we've already looked up this group. + return $isInGroup->{$uid}{$gid} if exists $isInGroup->{$uid}{$gid}; + ### Lookup the actual groupings. + my $group = WebGUI::Group->new($self->session,$gid); + # Cope with non-existant groups. Default to the admin group if the groupId is invalid. + $group = WebGUI::Group->new($self->session, 3) unless $group; + ### Check for groups of groups. + my $users = $group->getAllUsers(); + foreach my $user (@{$users}) { + $isInGroup->{$user}{$gid} = 1; + if ($uid eq $user) { + $self->session->stow->set("isInGroup",$isInGroup); + return 1; + } + } + $isInGroup->{$uid}{$gid} = 0; + $self->session->stow->set("isInGroup",$isInGroup); + return 0; +} + +#------------------------------------------------------------------- + +=head2 prepareView ( ) + +See WebGUI::Asset::prepareView() for details. + +=cut + +sub prepareView { + my $self = shift; + $self->SUPER::prepareView(); + my $templateId = $self->get("templateId"); + if ($self->session->form->process("overrideTemplateId") ne "") { + $templateId = $self->session->form->process("overrideTemplateId"); + } + my $template = WebGUI::Asset::Template->new($self->session, $templateId); + $template->prepare; + $self->{_viewTemplate} = $template; + + return undef; +} + +#------------------------------------------------------------------- + +=head2 view ( ) + +=cut + +sub view { + + my $self = shift; + my $form = $self->session->form; + my $url = $self->session->url; + my $i18n = WebGUI::International->new($self->session, "Asset_UserList"); + my (%var, @users, @profileField_loop, @profileFields); + my ($defaultPublicProfile, $defaultPublicEmail, $user, $sth, $sql, $profileField); + + my $currentUrlWithoutSort = $self->getUrl(); + foreach ($form->param) { + unless (WebGUI::Utility::isIn($_,qw(sortBy sortOrder op func)) || $_ =~ /identifier/i || $_ =~ /password/i) { + $currentUrlWithoutSort = $url->append($currentUrlWithoutSort, $url->escape($_) + .'='.$url->escape($form->process($_))); + } + } + + $sth = $self->session->db->read( + "SELECT field.fieldName, field.label, field.sequenceNumber, field.visible, field.fieldType, " + ."field.dataDefault, field.possibleValues " + ."FROM userProfileField as field " + ."left join userProfileCategory as category USING(profileCategoryId) " + ."ORDER BY category.sequenceNumber, field.sequenceNumber"); + while ($profileField = $sth->hashRef){ + my $label = WebGUI::Operation::Shared::secureEval($self->session,$profileField->{label}); + my $fieldName = $profileField->{fieldName}; + my $sortByURL = $url->append($currentUrlWithoutSort,'sortBy='.$url->escape($fieldName)); + if ($form->process('sortOrder') eq 'asc' && $form->process('sortBy') eq $fieldName){ + $sortByURL = $url->append($sortByURL,'sortOrder=desc'); + } + else{ + $sortByURL = $url->append($sortByURL,'sortOrder=asc'); + } + push(@profileFields, { + "fieldName"=>$fieldName, + "label"=>$label, + "sequenceNumber"=>$profileField->{sequenceNumber}, + "visible"=>$profileField->{visible}, + "fieldType"=>$profileField->{fieldType}, + }); + if($profileField->{visible}){ + push (@profileField_loop, { + "profileField_label"=>$label, + "profileField_sortByURL"=>$sortByURL, + }); + } + unless($self->get("showOnlyVisibleAsNamed") && $profileField->{visible} != 1){ + $var{'profileField_'.$fieldName.'_label'} = $label; + $var{'profileField_'.$fieldName.'_sortByURL'} = $sortByURL; + } + # create field specific templ_vars for search + my %formElementProperties = %{$profileField}; + + $formElementProperties{value} = $form->process('search_'.$fieldName); + $formElementProperties{name} = 'search_'.$fieldName; + $var{'search_'.$fieldName.'_form'} = $self->getFormElement(\%formElementProperties); + $var{'search_'.$fieldName.'_text'} = WebGUI::Form::Text($self->session, { + -name => 'search_'.$fieldName, + -value => $form->process('search_'.$fieldName), + }); + + $formElementProperties{value} = $form->process('search_Exact'.$fieldName); + $formElementProperties{name} = 'searchExact_'.$fieldName; + $var{'searchExact_'.$fieldName.'_form'} = $self->getFormElement(\%formElementProperties); + $var{'searchExact_'.$fieldName.'_text'} = WebGUI::Form::Text($self->session, { + -name => 'searchExact_'.$fieldName, + -value => $form->process('searchExact_'.$fieldName), + }); + + $var{'includeInSearch_'.$fieldName.'_hidden'} = WebGUI::Form::Hidden($self->session, { + -name => 'includeInSearch_'.$fieldName, + -value => '1', + }); + + $var{'includeInSearch_'.$fieldName.'_checkBox'} = WebGUI::Form::Checkbox($self->session, { + -name => 'includeInSearch_'.$fieldName, + -value => '1', + -checked=> $form->process('includeInSearch_'.$fieldName), + }); + } + + $sql = "select distinct users.userId, users.userName, userProfileData.publicProfile, userProfileData.publicEmail "; + + foreach my $profileField (@profileFields){ + $sql .= ", userProfileData.$profileField->{fieldName}"; + } + $sql .= " from users"; + $sql .= " left join userProfileData using(userId) where users.userId != '1'"; + + my $constraint; + my @profileSearchFields = (); + my $searchType = $form->process('searchType') || 'or'; + if ($form->process('search')){ + # Normal search with one keyword takes precedence over other search options + if($form->process('limitSearch')){ + # Normal search with one keyword in a limited number of fields + foreach my $profileField (@profileFields){ + if ($form->process('includeInSearch_'.$profileField->{fieldName})){ + push(@profileSearchFields,'userProfileData.'.$profileField->{fieldName} + .' like "%'.$form->process('search').'%"'); + } + } + } + else{ + # Normal search with one keyword in all fields + $constraint = "(".join(' or ', map {'userProfileData.'.$_->{fieldName} + .' like "%'.$form->process('search').'%"'} @profileFields).")"; + } + } + elsif ($form->process('searchExact')){ + # Exact search with one keyword + if($form->process('limitSearch')){ + # Exact search with one keyword in a limited number of fields + foreach my $profileField (@profileFields){ + if ($form->process('includeInSearch_'.$profileField->{fieldName})){ + push(@profileSearchFields,'userProfileData.'.$profileField->{fieldName} + .' like "'.$form->process('search').'"'); + } + } + } + else{ + # Exact search with one keyword in all fields + $constraint = "(".join(' or ', map {'userProfileData.'.$_->{fieldName} + .' like "'.$form->process('searchExact').'"'} @profileFields).")"; + } + } + else{ + # Mixed normal and exact search with different queries for each field. + foreach my $profileField (@profileFields){ + # Exact search has precedence over normal search + if ($form->process('searchExact_'.$profileField->{fieldName})){ + push(@profileSearchFields,'userProfileData.'.$profileField->{fieldName} + .' like "'.$form->process('searchExact_'.$profileField->{fieldName}).'"'); + } + elsif ($form->process('search_'.$profileField->{fieldName})){ + push(@profileSearchFields,'userProfileData.'.$profileField->{fieldName} + .' like "%'.$form->process('search_'.$profileField->{fieldName}).'%"'); + } + } + } + if (scalar(@profileSearchFields) > 0){ + $constraint = '('.join(' '.$searchType.' ',@profileSearchFields).')'; + } + $sql .= " and ".$constraint if ($constraint); + + my $sortBy = $form->process('sortBy') || $self->get('sortBy') || 'users.username'; + my $sortOrder = $form->process('sortOrder') || $self->get('sortOrder') || 'asc'; + + my @sortByUserProperties = ('dateCreated', 'lastUpdated', 'karma', 'userId'); + if(isIn($sortBy,@sortByUserProperties)){ + $sortBy = 'users.'.$sortBy; + } + $sql .= " order by ".$sortBy." ".$sortOrder; + + ($defaultPublicProfile) = $self->session->db->quickArray("SELECT dataDefault FROM userProfileField WHERE fieldName='publicProfile'"); + ($defaultPublicEmail) = $self->session->db->quickArray("SELECT dataDefault FROM userProfileField WHERE fieldName='publicEmail'"); + + my $paginatePage = $form->param('pn') || 1; + my $currentUrl = $self->getUrl(); + foreach ($form->param) { + unless ($_ eq "pn" || $_ eq "op" || $_ eq "func" || $_ =~ /identifier/i || $_ =~ /password/i) { + $currentUrl = $url->append($currentUrl, $url->escape($_) + .'='.$url->escape($form->process($_))); + } + } + + my $p = WebGUI::Paginator->new($self->session,$currentUrl,$self->getValue("usersPerPage"), undef, $paginatePage); + + $sth = $self->session->db->read($sql); + my @visibleUsers; + while (my $user = $sth->hashRef){ + my $showGroupId = $self->get("showGroupId"); + if ($showGroupId eq '0' || ($showGroupId && $self->isInGroup($showGroupId,$user->{userId}))){ + unless ($self->get("hideGroupId") ne '0' && $self->isInGroup($self->get("hideGroupId"),$user->{userId})){ + push(@visibleUsers,$user); + } + } + } + $p->setDataByArrayRef(\@visibleUsers); + my $users = $p->getPageData($paginatePage); + foreach my $user (@$users){ + if ($user->{publicProfile} eq "1" || ($user->{publicProfile} eq "" && $defaultPublicProfile eq "1")){ + my (@profileFieldValues); + my %userProperties; + my $emailNotPublic; + $emailNotPublic = 1 if ($user->{publicEmail} eq "0" || ($user->{publicEmail} eq "" && $defaultPublicEmail ne "1")); + foreach my $profileField (@profileFields){ + if ($profileField->{fieldName} eq "email" && $emailNotPublic){ + push (@profileFieldValues, { + "profile_emailNotPublic"=>1, + }); + } + else{ + my $profileFieldName = $profileField->{fieldName}; + $profileFieldName =~ s/ /_/g; + $profileFieldName =~ s/\./_/g; + my $value = $user->{$profileField->{fieldName}}; + my %profileFieldValues; + if (WebGUI::Utility::isIn(ucfirst $profileField->{fieldType},qw(File Image)) && $value ne ''){ + my $file = WebGUI::Form::DynamicField->new($self->session, + fieldType=>$profileField->{fieldType}, + value=>$value + )->getValueAsHtml(); + $profileFieldValues{profile_file} = $file; + $userProperties{'user_profile_'.$profileFieldName.'_file'} = $file; + } + $profileFieldValues{profile_value} = $value; + if($profileField->{visible}){ + push (@profileFieldValues, \%profileFieldValues); + } + unless($self->get("showOnlyVisibleAsNamed") && $profileField->{visible} != 1){ + $userProperties{'user_profile_'.$profileFieldName.'_value'} = $value; + } + } + } + $userProperties{"user_profile_emailNotPublic"} = $emailNotPublic; + $userProperties{"user_id"} = $user->{userId}; + $userProperties{"user_name"} = $user->{userName}; + $userProperties{"user_profile_loop"} = \@profileFieldValues; + push(@users,\%userProperties); + } + else{ + push(@users, { + "user_id"=>$user->{userId}, + "user_name"=>$user->{userName}, + }); + } + } + $p->appendTemplateVars(\%var); + + $var{numberOfProfileFields} = scalar(@profileFields); + + $var{profileField_loop} = \@profileField_loop; + $var{user_loop} = \@users; + $var{alphabetSearch_loop} = $self->getAlphabetSearchLoop($self->get("alphabetSearchField"),$self->get("alphabet")); + + $var{searchFormHeader} = WebGUI::Form::formHeader($self->session,{action => $self->getUrl}); + $var{searchFormSubmit} = WebGUI::Form::submit($self->session,{value => $i18n->get('submit search label')}); + $var{searchFormFooter} = WebGUI::Form::formFooter($self->session); + + $var{limitSearch} = WebGUI::Form::hidden($self->session, {name=>'limitSearch', value=>'1'}); + $var{searchFormTypeOr} = WebGUI::Form::hidden($self->session, {name=>'searchType', value=>'or'}); + $var{searchFormTypeAnd} = WebGUI::Form::hidden($self->session, {name=>'searchType', value=>'and'}); + $var{searchFormTypeSelect} = WebGUI::Form::selectBox($self->session,{ + name => 'searchType', + value => $form->process('searchType') || 'or', + options => { + 'or' => $i18n->get('or label'), + 'and' => $i18n->get('and label'), + } + }); + $var{searchFormQuery_form} = WebGUI::Form::text($self->session,{ + name => 'search', + value => $form->process("search"), + }); + + + my $out = $self->processTemplate(\%var,$self->get("templateId")); + return $out; +} +# Everything below here is to make it easier to install your custom +# wobject, but has nothing to do with wobjects in general +# ------------------------------------------------------------------- +# cd /data/WebGUI/lib +# perl -MWebGUI::Asset::Wobject::NewWobject -e install www.example.com.conf [ /path/to/WebGUI ] +# - or - +# perl -MWebGUI::Asset::Wobject::NewWobject -e uninstall www.example.com.conf [ /path/to/WebGUI ] +# ------------------------------------------------------------------- + + +use base 'Exporter'; +our @EXPORT = qw(install uninstall); +use WebGUI::Session; + +#------------------------------------------------------------------- +sub install { + my $config = $ARGV[0]; + my $home = $ARGV[1] || "/data/WebGUI"; + die "usage: perl -MWebGUI::Asset::Wobject::UserList -e install www.example.com.conf\n" unless ($home && +$config); + print "Installing asset.\n"; + my $session = WebGUI::Session->open($home, $config); + $session->config->addToArray("assets","WebGUI::Asset::Wobject::NewWobject"); + $session->db->write("create table UserList ( + assetId varchar(22) not null, + revisionDate bigint(20), + templateId varchar(22), + showGroupId varchar(22), + hideGroupId varchar(22), + usersPerPage int(11), + alphabet text, + alphabetSearchField varchar(128), + showOnlyVisibleAsNamed int(11), + sortBy varchar(128), + sortOrder varchar(4), + overridePublicEmail int(11), + overridePublicProfile int(11), + PRIMARY KEY (`assetId`,`revisionDate`) + )"); + my $import = WebGUI::Asset->getImportNode($session); + + $import->addChild({ + + className=>"WebGUI::Asset::Template", + + template=>q| + +

+
+ + +

+
+ +

+ + + + + + + + + | + +
+
+ +Search with one keyword in one or more fields that the user can select
+ + +
+
+
+ +
+
+ +Search with one keyword in one or more fields that are defined by hidden form fields
+ + + + + + +
+
+ +Search with multiple keywords
+ +^International('searchFormTypeSelect label','Asset_UserList');
+:
+ (exact):
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdUsername + + +
+ +^International('Email not public message','Asset_UserList'); + + + +
^International('No users message','Asset_UserList');
+ +

+
+|, + + ownerUserId=>'3', + + groupIdView=>'7', + + groupIdEdit=>'12', + + title=>"Default UserList", + + menuTitle=>"Default UserList", + + url=>"templates/userlist", + + namespace=>"UserList" + + },'UserListTmpl0000001'); + + my $versionTag = WebGUI::VersionTag->getWorking($session); + $versionTag->set({name=>"Install UserList Template"}); + $versionTag->commit; + + $session->var->end; + $session->close; + print "Done. Please restart Apache.\n"; +} +#------------------------------------------------------------------- +sub uninstall { + my $config = $ARGV[0]; + my $home = $ARGV[1] || "/data/WebGUI"; + die "usage: perl -MWebGUI::Asset::Wobject::UserList -e uninstall www.example.com.conf\n" unless ($home && +$config); + print "Uninstalling asset.\n"; + my $session = WebGUI::Session->open($home, $config); + $session->config->deleteFromArray("assets","WebGUI::Asset::Wobject::UserList"); + my $rs = $session->db->read("select assetId from asset where +className='WebGUI::Asset::Wobject::UserList'"); + while (my ($id) = $rs->array) { + my $asset = WebGUI::Asset->new($session, $id, "WebGUI::Asset::Wobject::UserList"); + $asset->purge if defined $asset; + } + + $rs = $session->db->read("select distinct(assetId) from template where namespace='UserList'"); + + while (my ($id) = $rs->array) { + my $asset = WebGUI::Asset->new($session, $id, "WebGUI::Asset::Template"); + $asset->purge if defined $asset; + } + + $session->db->write("drop table UserList"); + $session->var->end; + $session->close; + print "Done. Please restart Apache.\n"; +} + + +1; diff --git a/lib/WebGUI/Help/Asset_UserList.pm b/lib/WebGUI/Help/Asset_UserList.pm new file mode 100644 index 000000000..83d39fae0 --- /dev/null +++ b/lib/WebGUI/Help/Asset_UserList.pm @@ -0,0 +1,93 @@ +package WebGUI::Help::Asset_UserList; + +our $HELP = { + 'userlist template' => { + title => 'UserList Template', + body => 'UserList Template Help Body', + isa => [ + { namespace => "Asset_UserList", + tag => "userlist asset template variables" + }, + { namespace => "Asset_Template", + tag => "template variables" + }, + { namespace => "Asset", + tag => "asset template" + }, + { tag => 'pagination template variables', + namespace => 'WebGUI' + }, + ], + variables => [ + { 'name' => 'searchFormHeader' }, + { 'name' => 'searchFormSubmit' }, + { 'name' => 'searchFormFooter' }, + { 'name' => 'searchFormTypeOr' }, + { 'name' => 'searchFormTypeAnd' }, + { 'name' => 'searchFormTypeSelect' }, + { 'name' => 'searchFormQuery_form' }, + { 'name' => 'search_PROFILEFIELDNAME_text' }, + { 'name' => 'search_PROFILEFIELDNAME_form' }, + { 'name' => 'searchExact_PROFILEFIELDNAME_text' }, + { 'name' => 'searchExact_PROFILEFIELDNAME_form' }, + { 'name' => 'limitSearch' }, + { 'name' => 'includeInSearch_PROFILEFIELDNAME_hidden' }, + { 'name' => 'includeInSearch_PROFILEFIELDNAME_checkBox' }, + { 'name' => 'numberOfProfileFields' }, + { 'name' => 'profileField_PROFILEFIELDNAME_label' }, + { 'name' => 'profileField_PROFILEFIELDNAME_sortByURL' }, + { 'name' => 'profileField_loop', + 'variables' => [ + { 'name' => 'profileField_label' }, + { 'name' => 'profileField_sortByURL' }, + ], + }, + { 'name' => 'alphabetSearch_loop', + 'variables' => [ + { 'name' => 'alphabetSearch_loop_label' }, + { 'name' => 'alphabetSearch_loop_hasResults' }, + { 'name' => 'alphabetSearch_loop_searchURL' }, + ], + }, + { 'name' => 'user_loop', + 'variables' => [ + { 'name' => 'user_name' }, + { 'name' => 'user_id' }, + { 'name' => 'user_profile_PROFILEFIELDNAME_value' }, + { 'name' => 'user_profile_PROFILEFIELDNAME_file' }, + { 'name' => 'user_profile_emailNotPublic' }, + { 'name' => 'user_profile_loop', + 'variables' => [ + { 'name' => 'profile_emailNotPublic' }, + { 'name' => 'profile_value' }, + { 'name' => 'profile_file' }, + ], + }, + ], + }, + ], + related => [] + }, + + 'userlist asset template variables' => { + private => 1, + title => 'UserList Template', + body => 'UserList Template Help Body', + isa => [ + { namespace => "Asset_Wobject", + tag => "wobject template variables", + }, + ], + variables => [ + { 'name' => 'alphabet' }, + { 'name' => 'showGroupId' }, + { 'name' => 'hideGroupId' }, + { 'name' => 'usersPerPage' }, + { 'name' => 'templateId' }, + { 'name' => 'showOnlyVisibleAsNamed' }, + ], + related => [] + }, +}; + +1; diff --git a/lib/WebGUI/i18n/English/Asset_UserList.pm b/lib/WebGUI/i18n/English/Asset_UserList.pm new file mode 100644 index 000000000..cac9a56fb --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_UserList.pm @@ -0,0 +1,401 @@ +package WebGUI::i18n::English::Asset_UserList; + +our $I18N = { + 'assetName' => { + message => q|User List|, + lastUpdated => 1081514049, + }, + + 'UserList Add/Edit' => { + message => q|User List Add/Edit|, + lastUpdated => 1081514049 + }, + + 'UserList Template' => { + message => q|User List Template|, + lastUpdated => 1081514049 + }, + + 'Edit/Add UserList' => { + message => q|Edit/Add User List|, + lastUpdated => 1081514049 + }, + + 'templateId' => { + message => q|The ID of the template used to display the UserList Asset.|, + lastUpdated => 1168897708, + }, + + 'showGroupId' => { + message => q|Only users that are in this group will be shown.|, + lastUpdated => 1081514049 + }, + + 'Group to show label' => { + message => q|Group to show|, + lastUpdated => 1081514049 + }, + + 'Group to show description' => { + message => q|Only users in this group will be shown in the user list. The default value is +'Everyone'.|, + lastUpdated => 1081514049 + }, + + 'hideGroupId' => { + message => q|Users in this group will be hidden.|, + lastUpdated => 1081514049 + }, + + 'Group to hide label' => { + message => q|Group to hide|, + lastUpdated => 1081514049 + }, + + 'Group to hide description' => { + message => q|Select a group to hide from the user list. The default value is 'Admins'.|, + lastUpdated => 1081514049 + }, + + 'usersPerPage' => { + message => q|The number of users per page|, + lastUpdated => 1081514049 + }, + + 'Users per page label' => { + message => q|Users per page|, + lastUpdated => 1081514049 + }, + + 'Users per page description' => { + message => q|The number of users per page|, + lastUpdated => 1081514049 + }, + + 'showOnlyVisibleAsNamed' => { + message => q|Boolean. If true then only fields that are set to 'visible' in the user profile settings +will be available as named tmpl_vars|, + lastUpdated => 1081514049 + }, + + 'showOnlyVisibleAsNamed label' => { + message => q|Show only visible fields as named tmpl_vars|, + lastUpdated => 1081514049 + }, + + 'showOnlyVisibleAsNamed description' => { + message => q|If set to Yes then only fields that are set to 'visible' in the user profile settings +will be available as named tmpl_vars|, + lastUpdated => 1081514049 + }, + + 'alphabet' => { + message => q|The alphabet that is used for the alphabet search. This is a string of comma +seperated values|, + lastUpdated => 1081514049 + }, + + 'alphabet label' => { + message => q|Alphabet|, + lastUpdated => 1081514049 + }, + + 'alphabet description' => { + message => q|The alphabet that is used for the alphabet search. Has to be a string of comma +seperated values|, + lastUpdated => 1081514049 + }, + + 'alphabetSearchField' => { + message => q|The field in which the alphabet search will be done.|, + lastUpdated => 1081514049 + }, + + 'alphabetSearchField label' => { + message => q|Alphabet Search Field|, + lastUpdated => 1081514049 + }, + + 'alphabetSearchField description' => { + message => q|Select the profile field in which the alphabet search will be done.|, + lastUpdated => 1081514049 + }, + + 'Profile not public message' => { + message => q|Profile not public|, + lastUpdated => 1081514049 + }, + 'No users message' => { + message => q|No users found|, + lastUpdated => 1081514049 + }, + + 'Email not public message' => { + message => q|Email not public|, + lastUpdated => 1081514049 + }, + + 'id label' => { + message => q|Id|, + lastUpdated => 1081514049 + }, + + 'username label' => { + message => q|Username|, + lastUpdated => 1081514049 + }, + + 'query label' => { + message => q|Query|, + lastUpdated => 1081514049 + }, + + 'search in label' => { + message => q|Search in:|, + lastUpdated => 1081514049 + }, + + 'submit search label' => { + message => q|Search|, + lastUpdated => 1081514049 + }, + + 'or label' => { + message => q|Or|, + lastUpdated => 1081514049 + }, + + 'and label' => { + message => q|And|, + lastUpdated => 1081514049 + }, + + + 'searchFormHeader' => { + message => q|The header tag for the search form.|, + lastUpdated => 1081514049 + }, + + 'searchFormSubmit' => { + message => q|The submit form element for the search form|, + lastUpdated => 1081514049 + }, + + 'searchFormFooter' => { + message => q|The footer tag for the search form|, + lastUpdated => 1081514049 + }, + + 'searchFormTypeOr' => { + message => q|A hidden form element to set the search type to 'or'.|, + lastUpdated => 1081514049 + }, + + 'searchFormTypeAnd' => { + message => q|A hidden form element to set the search type to 'and'.|, + lastUpdated => 1081514049 + }, + + 'searchFormTypeSelect' => { + message => q|A select box to let the user select the search type.|, + lastUpdated => 1081514049 + }, + + 'searchFormTypeSelect label' => { + message => q|Select search type|, + lastUpdated => 1081514049 + }, + + 'searchFormQuery_form' => { + message => q|A text input for the search query.|, + lastUpdated => 1081514049 + }, + + 'search_PROFILEFIELDNAME_text' => { + message => q|A text input to do a normal search in profile field PROFILEFIELDNAME. Example: +<tmpl_var search_lastName_text>.|, + lastUpdated => 1081514049 + }, + + 'search_PROFILEFIELDNAME_form' => { + message => q|The form element tied to this field to do a normal search in profile field PROFILEFIELDNAME. Example: +<tmpl_var search_timeZone_form> will be a select box.|, + lastUpdated => 1081514049 + }, + + 'searchExact_PROFILEFIELDNAME_text' => { + message => q|A text input to do an exact search in profile field PROFILEFIELDNAME. Example: +<tmpl_var searchExact_email_text>.|, + lastUpdated => 1081514049 + }, + + 'searchExact_PROFILEFIELDNAME_form' => { + message => q|The form element tied to this field to do an exact search in profile field PROFILEFIELDNAME. Example: +<tmpl_var searchExact_email_form>.|, + lastUpdated => 1081514049 + }, + + 'limitSearch' => { + message => q|A hidden form element to indicate that the search is limited to certain profile +fields. Use includeInSearch_PROFILEFIELDNAME_hidden or includeInSearch_PROFILEFIELDNAME_checkBox tmpl_vars to +select which fields the search is limited to.|, + lastUpdated => 1081514049 + }, + + 'includeInSearch_PROFILEFIELDNAME_hidden' => { + message => q|A hidden form element to indicate that profile field PROFILEFIELDNAME will be +searched. This will only have an effect if the limitSearch tmpl_var is part of the search form.|, + lastUpdated => 1081514049 + }, + + 'includeInSearch_PROFILEFIELDNAME_checkBox' => { + message => q|A checkBox that the user can use to choose whether profile field PROFILEFIELDNAME will +be searched or not. This will only have an effect if the limitSearch tmpl_var is part of the search form.|, + lastUpdated => 1081514049 + }, + + 'numberOfProfileFields' => { + message => q|The number of profile fields.|, + lastUpdated => 1081514049 + }, + + 'alphabetSearch_loop' => { + message => q|A loop that contains elements to create an alphabetical search.|, + lastUpdated => 1081514049 + }, + + 'alphabetSearch_loop_label' => { + message => q|The label for this alphabet search query. Usually one letter of the alphabet.|, + lastUpdated => 1081514049 + }, + + 'alphabetSearch_loop_hasResults' => { + message => q|A conditional that is true if there are any results for this alphabet query.|, + lastUpdated => 1081514049 + }, + + 'alphabetSearch_loop_searchURL' => { + message => q|The url to do an alphabet search on this query.|, + lastUpdated => 1081514049 + }, + + 'user_loop' => { + message => q|A loop containing the users that are listed by the UserList.|, + lastUpdated => 1081514049 + }, + + 'user_name' => { + message => q|The username of the user.|, + lastUpdated => 1081514049 + }, + + 'user_id' => { + message => q|The userId of the user|, + lastUpdated => 1081514049 + }, + + 'user_profile_PROFILEFIELDNAME_value' => { + message => q|The value of the profile field with the name PROFILEFIELDNAME in the users user +profile. Example <tmpl_var user_profile_firstName_value>|, + lastUpdated => 1081514049 + }, + + 'user_profile_PROFILEFIELDNAME_file' => { + message => q|The file for the profile field with the name PROFILEFIELDNAME in the users user +profile. Example <tmpl_var user_profile_firstName_file>. This is available if the profile field is a file or +an image.|, + lastUpdated => 1081514049 + }, + + 'user_profile_emailNotPublic' => { + message => q|A conditional that is true if the users email address is not public.|, + lastUpdated => 1081514049 + }, + + 'user_profile_loop' => { + message => q|A loop containing the users profile fields.|, + lastUpdated => 1081514049 + }, + + 'profile_emailNotPublic' => { + message => q|A conditional that is true if the users email address is not public. It will only be +true for the 'email' profile field.|, + lastUpdated => 1081514049 + }, + + 'profile_value' => { + message => q|The value of the profile field for this user.|, + lastUpdated => 1081514049 + }, + + 'profile_value' => { + message => q|The file for this profile field, available if the profile field is an image or file.|, + lastUpdated => 1081514049 + }, + + 'profileField_loop' => { + message => q|A loop containing profile fields|, + lastUpdated => 1081514049 + }, + + 'profileField_label' => { + message => q|The label for this profile field|, + lastUpdated => 1081514049 + }, + + + 'profileField_sortByURL' => { + message => q|The URL to sort the UserList by this profile field. The default sort order +is ascending. After clicking the sort by link for a profile field the sort order will be reversed.|, + lastUpdated => 1081514049 + }, + + 'profileField_PROFILEFIELDNAME_label' => { + message => q|The label for the profile field with fieldName PROFILEFIELDNAME. Example <tmpl_var +profileField_lastName_label>. This tmpl_var exists outside of the profileField_loop.|, + lastUpdated => 1081514049 + }, + + 'profileField_PROFILEFIELDNAME_sortByURL' => { + message => q|The URL to sort the UserList by PROFILEFIELDNAME. Example <tmpl_var +profileField_lastName_sortByURL>. This tmpl_var exists outside of the profileField_loop. The default sort order +is ascending. After clicking the sort by link for a profile field the sort order will be reversed.|, + lastUpdated => 1081514049 + }, + + 'sort by' => { + message => q|Sort By|, + lastUpdated => 1109698614, + }, + + 'sort order' => { + message => q|Sort Order|, + lastUpdated => 1109698614, + }, + + 'sort by description' => { + message => q|By default, all users are displayed in a sorted order. Use this +field to choose by what field they are sorted.|, + lastUpdated => 1119070429, + }, + + 'sort order description' => { + message => q|Sort in ascending or descending order.|, + lastUpdated => 1119070429, + }, + + 'ascending' => { + message => q|Ascending|, + lastUpdated => 1113673328, + }, + + 'descending' => { + message => q|Descending|, + lastUpdated => 1113673330, + }, + + +}; + +1; + diff --git a/t/Asset/Wobject/UserList.t b/t/Asset/Wobject/UserList.t new file mode 100644 index 000000000..f903d5a7e --- /dev/null +++ b/t/Asset/Wobject/UserList.t @@ -0,0 +1,52 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2008 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------- +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------- + +use FindBin; +use strict; +use File::Spec; +use lib "$FindBin::Bin/../../lib"; + +##The goal of this test is to test the creation of UserList Wobjects. + +use WebGUI::Test; +use WebGUI::Session; +use Test::More tests => 5; # increment this value for each test you create +use WebGUI::Asset::Wobject::UserList; + +my $session = WebGUI::Test->session; + +# Do our work in the import node +my $node = WebGUI::Asset->getImportNode($session); + +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"UserList Test"}); +my $userList = $node->addChild({className=>'WebGUI::Asset::Wobject::UserList'}); + +# Test for a sane object type +isa_ok($userList, 'WebGUI::Asset::Wobject::UserList'); + +# Test to see if we can set new values +my $newUserListSettings = { + usersPerPage => 124, + showGroupId => 7, + hideGroupId => 3, + alphabet => 'a,b,c', +}; +$userList->update($newUserListSettings); + +foreach my $newSetting (keys %{$newUserListSettings}) { + is ($userList->get($newSetting), $newUserListSettings->{$newSetting}, "updated $newSetting is ".$newUserListSettings->{$newSetting}); +} + +END { + # Clean up after thy self + $versionTag->rollback(); +} + diff --git a/www/extras/assets/small/userlist.gif b/www/extras/assets/small/userlist.gif new file mode 100644 index 000000000..8a49a417f Binary files /dev/null and b/www/extras/assets/small/userlist.gif differ diff --git a/www/extras/assets/userlist.gif b/www/extras/assets/userlist.gif new file mode 100644 index 000000000..e07548ce5 Binary files /dev/null and b/www/extras/assets/userlist.gif differ