From 88797c1d6c41464c324bfd94fb4c422d5dc25d37 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Wed, 9 Mar 2011 21:33:44 -0800 Subject: [PATCH] Add Asset Dashlets to Dashboard. Add required and static properties to Dashboard Assets. Add caching to StockData and WeatherData assets. Add LastModifiedBy macro. Add GroupManager to the Group form control. --- docs/changelog/7.x.x.txt | 5 + docs/gotcha.txt | 15 + .../root_import_dashboard.wgpkg | Bin 0 -> 3927 bytes docs/upgrades/upgrade_7.10.11-7.10.12.pl | 66 ++ etc/WebGUI.conf.original | 1 + lib/WebGUI/Asset.pm | 13 + lib/WebGUI/Asset/Wobject/Dashboard.pm | 238 +++++- lib/WebGUI/Asset/Wobject/Folder.pm | 32 +- lib/WebGUI/Asset/Wobject/Layout.pm | 30 + lib/WebGUI/Asset/Wobject/StockData.pm | 71 +- lib/WebGUI/Asset/Wobject/WeatherData.pm | 105 ++- lib/WebGUI/AssetAspect/Dashlet.pm | 129 +++ lib/WebGUI/Form/Group.pm | 216 ++++- lib/WebGUI/Form/User.pm | 20 + lib/WebGUI/Help/Asset_Dashboard.pm | 60 ++ lib/WebGUI/Macro/LastUpdatedBy.pm | 48 ++ lib/WebGUI/Operation.pm | 2 + lib/WebGUI/i18n/English/Asset_Dashboard.pm | 172 +++- lib/WebGUI/i18n/English/Asset_StockData.pm | 6 + lib/WebGUI/i18n/English/Asset_WeatherData.pm | 6 + lib/WebGUI/i18n/English/Form_Group.pm | 33 + lib/WebGUI/i18n/English/Icon.pm | 6 + lib/WebGUI/i18n/English/Macro_LastModified.pm | 6 + lib/WebGUI/i18n/English/WebGUI.pm | 6 + sbin/testEnvironment.pl | 1 + t/Asset/Asset.t | 34 +- t/Asset/Wobject/Folder.t | 67 ++ t/Asset/Wobject/Layout.t | 67 ++ t/Asset/Wobject/StockData.t | 65 ++ t/Asset/Wobject/WeatherData.t | 111 +++ t/Form/Group.t | 178 ++++ t/Form/User.t | 74 ++ t/Macro/LastUpdatedBy.t | 71 ++ www/extras/toolbar/bullet/add.gif | Bin 0 -> 2497 bytes www/extras/wobject/Dashboard/dashboard.css | 514 ++++++------ www/extras/wobject/Dashboard/draggable.js | 791 ++++++++++++++++-- .../wobject/Dashboard/permissions_btn.jpg | Bin 0 -> 2300 bytes .../yui-webgui/build/form/assets/add.png | Bin 0 -> 807 bytes .../build/form/assets/new_group.png | Bin 0 -> 1747 bytes .../yui-webgui/build/form/assets/remove.png | Bin 0 -> 931 bytes .../yui-webgui/build/form/groupManager.css | 170 ++++ .../yui-webgui/build/form/groupManager.js | 525 ++++++++++++ 42 files changed, 3506 insertions(+), 448 deletions(-) create mode 100644 docs/upgrades/packages-7.10.12/root_import_dashboard.wgpkg create mode 100644 lib/WebGUI/AssetAspect/Dashlet.pm create mode 100644 lib/WebGUI/Help/Asset_Dashboard.pm create mode 100644 lib/WebGUI/Macro/LastUpdatedBy.pm create mode 100644 lib/WebGUI/i18n/English/Form_Group.pm create mode 100644 t/Asset/Wobject/Folder.t create mode 100644 t/Asset/Wobject/Layout.t create mode 100644 t/Asset/Wobject/StockData.t create mode 100644 t/Asset/Wobject/WeatherData.t create mode 100644 t/Form/Group.t create mode 100644 t/Form/User.t create mode 100644 t/Macro/LastUpdatedBy.t create mode 100644 www/extras/toolbar/bullet/add.gif create mode 100644 www/extras/wobject/Dashboard/permissions_btn.jpg create mode 100644 www/extras/yui-webgui/build/form/assets/add.png create mode 100644 www/extras/yui-webgui/build/form/assets/new_group.png create mode 100755 www/extras/yui-webgui/build/form/assets/remove.png create mode 100644 www/extras/yui-webgui/build/form/groupManager.css create mode 100644 www/extras/yui-webgui/build/form/groupManager.js diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 1d5aedb4a..ffdfa7917 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -1,5 +1,10 @@ 7.10.12 - fixed #12072: Product, related and accessory assets + - added: Add Asset Dashlets to Dashboard + - added: Required and Static properties to assets in a dashboard. + - added: Caching to StockData and WeatherData asset. + - added: LastModifiedBy macro + - added: Group Manager form control 7.10.11 - fixed #12057: WebGUI::Search, assetIds search clause diff --git a/docs/gotcha.txt b/docs/gotcha.txt index ed4e29ab8..868f57f11 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -7,6 +7,21 @@ upgrading from one version to the next, or even between multiple versions. Be sure to heed the warnings contained herein as they will save you many hours of grief. +7.10.12 +-------------------------------------------------------------------- + * The Dashboard has been extended to include Asset Dashlets. This gives + Assets fine control over which properties can be extended and which can't. + Assets in the Dashboard can be set to be required, which prevents them from + being deleted, and fixed, which prevents them from moved. + * The StockData and WeatherData assets now include cache settings to reduce + server side load. The browser interface for the StockData asset still does + real time lookups. + * A new macro has been added, LastModifiedBy. This returns the username of + the user who last modified an Asset. If the asset in question is a Page + Layout or Folder, then querying that asset will also check all children. + * The Group form control has been extended to add a simple interface for + adding new Groups and changing their membership. + 7.10.11 -------------------------------------------------------------------- * Modified TimeField, now provides popupless immediate validation with diff --git a/docs/upgrades/packages-7.10.12/root_import_dashboard.wgpkg b/docs/upgrades/packages-7.10.12/root_import_dashboard.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..a80dcd47da08e8caba458546326f0d1a1abd4680 GIT binary patch literal 3927 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 literal 0 HcmV?d00001 diff --git a/docs/upgrades/upgrade_7.10.11-7.10.12.pl b/docs/upgrades/upgrade_7.10.11-7.10.12.pl index cc8ce21a9..8e272d5ac 100644 --- a/docs/upgrades/upgrade_7.10.11-7.10.12.pl +++ b/docs/upgrades/upgrade_7.10.11-7.10.12.pl @@ -31,6 +31,11 @@ my $quiet; # this line required my $session = start(); # this line required # upgrade functions go here +installNewDashboardTables($session); +addStockDataCacheColumn($session); +addWeatherDataCacheColumn($session); +addLastModifiedByMacro($session); + finish($session); # this line required @@ -44,6 +49,67 @@ finish($session); # this line required # print "DONE!\n" unless $quiet; #} +#---------------------------------------------------------------------------- +# Describe what our function does +sub addLastModifiedMacro { + my $session = shift; + print "\tAdd LastModifiedBy macro to the config file... " unless $quiet; + # and here's our code + $session->config->addToHash('macros', 'LastModifiedBy', 'LastModifiedBy'); + print "DONE!\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +# Describe what our function does +sub installNewDashboardTables { + my $session = shift; + print "\tInstall new Dashboard tables... " unless $quiet; + $session->db->write(<db->write(<db->write(<db->write(<get("revisedBy"); +} + #------------------------------------------------------------------- =head2 getValue ( key ) diff --git a/lib/WebGUI/Asset/Wobject/Dashboard.pm b/lib/WebGUI/Asset/Wobject/Dashboard.pm index 0e49358df..40e911a94 100644 --- a/lib/WebGUI/Asset/Wobject/Dashboard.pm +++ b/lib/WebGUI/Asset/Wobject/Dashboard.pm @@ -323,13 +323,17 @@ sub purge { =head2 view -Render the dashboard. +Render the dashboard. Of all the positions for content, position1 is reserved for hidden content +or content to be placed. Deleting content causes it to go into position1. =cut sub view { - my $self = shift; + my $self = shift; + my $session = $self->session; my %vars = %{$self->get()}; + $vars{canEdit} = $self->canEdit; + $vars{fullUrl} = $self->getUrl; $self->session->style->setScript( $self->session->url->extras('yui/build/utilities/utilities.js'), @@ -363,60 +367,84 @@ sub view { my @found; my $newStuff; my $showPerformance = $self->session->errorHandler->canShowPerformanceIndicators(); + my $user = $self->session->user; foreach my $position (@positions) { my @assets = split(",",$position); foreach my $asset (@assets) { - foreach my $child (@{$children}) { + CHILD: foreach my $child (@{$children}) { if ($asset eq $child->getId) { - unless (isIn($asset,@hidden) || !($child->canView)) { - $self->session->style->setRawHeadTags($child->getExtraHeadTags); - $child->{_properties}{title} = $child->getTitle; - $child->{_properties}{title} = $child->getShortcut->getTitle if (ref $child eq 'WebGUI::Asset::Shortcut'); - if ($i == 1 || $i > $numPositions) { - push(@{$vars{"position1_loop"}},{ - id=>$child->getId, - content=>'', #so things in the New Content bar don't display. - dashletTitle=>$child->{_properties}{title}, - shortcutUrl=>$child->getUrl, - canPersonalize=>$self->canPersonalize, - showReloadIcon=>$child->{_properties}{showReloadIcon}, - canEditUserPrefs=>(($self->session->user->isRegistered) && (ref $child eq 'WebGUI::Asset::Shortcut') && (scalar($child->getPrefFieldsToShow) > 0)) - }); - $newStuff .= 'available_dashlets["'.$child->getId.'"]=\''.$child->getUrl.'\';'; - - } else { - $child->prepareView; - push(@{$vars{"position".$i."_loop"}},{ - id=>$child->getId, - content=>$child->view, - dashletTitle=>$child->{_properties}{title}, - shortcutUrl=>$child->getUrl, - canPersonalize=>$self->canPersonalize, - showReloadIcon=>$child->{_properties}{showReloadIcon}, - canEditUserPrefs=>(($self->session->user->isRegistered) && (ref $child eq 'WebGUI::Asset::Shortcut') && (scalar($child->getPrefFieldsToShow) > 0)) - }); - $newStuff .= 'available_dashlets["'.$child->getId.'"]=\''.$child->getUrl.'\';'; - } - } push(@found, $child->getId); - } + ##Filter based on visibility + next CHILD unless $child->canView; + next CHILD if isIn($asset, @hidden); + ##Detect child types + my $is_shortcut = $child->isa('WebGUI::Asset::Shortcut'); + my $is_dashlet = $child->can('getOverrideFormDefinition'); + $self->session->style->setRawHeadTags($child->getExtraHeadTags); + ##Override the title for shortcuts + if ($is_shortcut) { + $child->{_properties}{title} = $child->getShortcut->getTitle; + } + ##Fetch dashlet options from the database + my $options = $session->db->quickHashRef('select * from Dashboard_dashlets where dashboardAssetId=? and dashletAssetId=?', [$self->getId, $child->getId]); + if (!($i == 1 || $i > $numPositions)) { + $child->prepareView; + } + my $spot = $i > $numPositions ? 1 : $i; + my $canMove = $self->canPersonalize && !$options->{isStatic}; + my $editFormUrl = ($is_shortcut && $child->getPrefsFieldToShow) ? $child->getUrl('func=getUserPrefsForm') + : ($is_dashlet && $child->getOverrideFormDefinition) ? $self->getUrl('func=customizeDashlet;dashletAssetId='.$child->getId) + : '' + ; + my $canEditUserPrefs = $user->isRegistered && $editFormUrl; + + push(@{$vars{"position".$spot."_loop"}},{ + id => $child->getId, + content => $child->view, + dashletTitle => $child->get('title'), + shortcutUrl => ($is_shortcut ? $child->getUrl : ''), + editFormUrl => $editFormUrl, + dashletUrl => ($is_dashlet ? $child->getUrl : ''), + canDelete => $self->canPersonalize && !$options->{isRequired}, + canMove => $canMove, + canPersonalize => $self->canPersonalize, + showReloadIcon => $is_shortcut && $child->get('showReloadIcon'), + canEditUserPrefs => $canEditUserPrefs, + }); + if ($canMove) { + $newStuff .= 'available_dashlets["'.$child->getId.'"]=\''.$child->getUrl.'\';'; + } + } } } $i++; } - # deal with unplaced children + # deal with unplaced children, they go into position 1 foreach my $child (@{$children}) { unless (isIn($child->getId, @found)||isIn($child->getId,@hidden)) { if ($child->canView) { - $child->{_properties}{title} = $child->getShortcut->get("title") if (ref $child eq 'WebGUI::Asset::Shortcut'); + my $is_shortcut = $child->isa('WebGUI::Asset::Shortcut'); + my $is_dashlet = $child->can('getOverrideFormDefinition'); + my $title = $child->{_properties}{title} = $is_shortcut ? $child->getShortcut->getTitle : $child->getTitle; + my $options = $session->db->quickHashRef('select * from Dashboard_dashlets where dashboardAssetId=? and dashletAssetId=?', [$self->getId, $child->getId]); + my $canMove = $self->canPersonalize && !$options->{isStatic}; + my $editFormUrl = ($is_shortcut && $child->getPrefsFieldToShow) ? $child->getUrl('func=getUserPrefsForm') + : ($is_dashlet && $child->getOverrideFormDefinition) ? $self->getUrl('func=customizeDashlet;dashletAssetId='.$child->getId) + : '' + ; + my $canEditUserPrefs = $user->isRegistered && $editFormUrl; push(@{$vars{"position1_loop"}},{ - id=>$child->getId, - content=>'', - dashletTitle=>$child->getTitle, - shortcutUrl=>$child->getUrl, - showReloadIcon=>$child->{_properties}{showReloadIcon}, - canPersonalize=>$self->canPersonalize, - canEditUserPrefs=>(($self->session->user->isRegistered) && (ref $child eq 'WebGUI::Asset::Shortcut') && (scalar($child->getPrefFieldsToShow) > 0)) + id => $child->getId, + content => '', + dashletTitle => $title, + shortcutUrl => ($is_shortcut ? $child->getUrl : ''), + editFormUrl => $editFormUrl, + dashletUrl => ($is_dashlet ? $child->getUrl : ''), + canDelete => $self->canPersonalize && !$options->{isRequired}, + canMove => $canMove, + canPersonalize => $self->canPersonalize, + showReloadIcon => $is_shortcut && $child->{_properties}{showReloadIcon}, + canEditUserPrefs => $canEditUserPrefs, }); $newStuff .= 'available_dashlets["'.$child->getId.'"]=\''.$child->getUrl.'\';'; } @@ -425,9 +453,9 @@ sub view { $vars{showAdmin} = ($self->session->var->isAdminOn && $self->canEdit); $vars{"dragger.init"} = ' '; return $self->processTemplate(\%vars, $templateId); @@ -435,6 +463,128 @@ sub view { #------------------------------------------------------------------- +=head2 www_customizeDashlet + +Web facing method for saving per dashlet configuration, such as being required, or movable. + +=cut + +sub www_customizeDashlet { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient() unless ($session->user->isRegistered); + my $dashletAssetId = $session->form->get('dashletAssetId'); + my $dashlet = WebGUI::Asset->newByDynamicClass($session, $dashletAssetId); + return $session->privilege->insufficient() unless ($dashlet && $dashlet->canView && $dashlet->can('getOverrideFormDefinition')); + + my $i18n = WebGUI::International->new($session, 'Asset_Dashboard'); + + my $form = $session->form; + my $html_form = WebGUI::HTMLForm->new($session, action => $self->getUrl, method => 'POST', ); + $html_form->hidden(name => 'func', value => 'customizeDashletSave', ); + $html_form->hidden(name => 'dashletAssetId', value => $dashletAssetId, ); + $html_form->readOnly(name => $i18n->get(), value => $dashlet->getTitle, ); + + my $overrides = $dashlet->fetchUserOverrides($self->getId); + my @dashlet_properties = $dashlet->getOverrideFormDefinition; + foreach my $property (@dashlet_properties) { + my %properties = %{ $property }; + $properties{value} = $overrides->{$property->{name}} || $dashlet->get($property->{name}); + $html_form->dynamicField(%properties); + } + + $html_form->submit(); + return $html_form->print; +} + +#------------------------------------------------------------------- + +=head2 www_customizeDashletSave + +Web facing method for saving per dashlet configuration, such as being required, or movable. + +=cut + +sub www_customizeDashletSave { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient() unless ($session->user->isRegistered); + my $dashletAssetId = $session->form->get('dashletAssetId'); + my $dashlet = WebGUI::Asset->newByDynamicClass($session, $dashletAssetId); + return $session->privilege->insufficient() unless ($dashlet && $dashlet->canView && $dashlet->can('getOverrideFormDefinition')); + my $overrides = {}; + my @dashlet_properties = $dashlet->getOverrideFormDefinition; + my $form = $session->form; + foreach my $property (@dashlet_properties) { + my $value = $form->process($property->{name}, $property->{fieldType}, $property->{value}); + $overrides->{$property->{name}} = $value; + } + $dashlet->storeUserOverrides($self->getId, $overrides); + return $self->www_view; +} + +##------------------------------------------------------------------- + +=head2 www_editDashlet + +Web facing method for saving per dashlet configuration, such as being required, or movable. + +=cut + +sub www_editDashlet { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient() unless ($self->canEdit); + my $dashletAssetId = $session->form->get('dashletAssetId'); + my $dashlet = WebGUI::Asset->newByDynamicClass($session, $dashletAssetId); + return $session->privilege->insufficient() unless ($dashletAssetId); + + my $i18n = WebGUI::International->new($session, 'Asset_Dashboard'); + + my $form = $session->form; + my $html_form = WebGUI::HTMLForm->new($session, action => $self->getUrl, method => 'POST', ); + $html_form->hidden(name => 'func', value => 'editDashletSave', ); + $html_form->hidden(name => 'dashletAssetId', value => $dashletAssetId, ); + $html_form->readOnly(name => $i18n->get(), value => $dashlet->getTitle, ); + my $options = $session->db->quickHashRef('select * from Dashboard_dashlets where dashboardAssetId=? and dashletAssetId=?', [$self->getId, $dashletAssetId]); + $html_form->yesNo( + name => 'isStatic', + label => $i18n->get('Is static'), + hoverHelp => $i18n->get('Is static help'), + value => $form->get('isStatic') || $options->{isStatic}, + ); + $html_form->yesNo( + name => 'isRequired', + label => $i18n->get('Is required'), + hoverHelp => $i18n->get('Is required help'), + value => $form->get('isRequired') || $options->{isRequired}, + ); + $html_form->submit(); + return $html_form->print; +} + +#------------------------------------------------------------------- + +=head2 www_editDashletSave + +Web facing method for saving per dashlet configuration, such as being required, or movable. + +=cut + +sub www_editDashletSave { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient() unless ($self->canEdit); + my $dashletAssetId = $session->form->get('dashletAssetId'); + my $isStatic = $session->form->get('isStatic', 'yesNo'); + my $isRequired = $session->form->get('isRequired', 'yesNo'); + $session->db->write('DELETE FROM Dashboard_dashlets where dashboardAssetId=? and dashletAssetId=?',[$self->getId, $dashletAssetId, ]); + $session->db->write('INSERT INTO Dashboard_dashlets (dashboardAssetId, dashletAssetId, isStatic, isRequired) VALUES (?,?,?,?)', [$self->getId, $dashletAssetId, $isStatic, $isRequired, ]); + return $self->www_view; +} + +#------------------------------------------------------------------- + =head2 www_setContentPositions Web method for saving the positions of dashlets in the dashboard. diff --git a/lib/WebGUI/Asset/Wobject/Folder.pm b/lib/WebGUI/Asset/Wobject/Folder.pm index bc63e3ebe..a25ea1e17 100644 --- a/lib/WebGUI/Asset/Wobject/Folder.pm +++ b/lib/WebGUI/Asset/Wobject/Folder.pm @@ -121,7 +121,7 @@ Overridden to check the revision dates of children as well sub getContentLastModified { my $self = shift; - my $mtime = $self->get("revisionDate"); + my $mtime = $self->get("lastModified"); my $childIter = $self->getLineageIterator(["children"]); while ( 1 ) { my $child; @@ -139,6 +139,36 @@ sub getContentLastModified { #------------------------------------------------------------------- +=head2 getContentLastModifiedBy + +Overridden to check the updated dates of children as well + +=cut + +sub getContentLastModifiedBy { + my $self = shift; + my $mtime = $self->SUPER::getContentLastModified; + my $userId = $self->get('revisedBy'); + my $childIter = $self->getLineageIterator(["children"]); + while ( 1 ) { + my $child; + eval { $child = $childIter->() }; + if ( my $x = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound') ) { + $self->session->log->error($x->full_message); + next; + } + last unless $child; + my $child_mtime = $child->getContentLastModified; + if ($child_mtime > $mtime) { + $mtime = $child_mtime; + $userId = $child->get("revisedBy"); + } + } + return $userId; +} + +#------------------------------------------------------------------- + =head2 getEditForm ( ) Returns the TabForm object that will be used in generating the edit page for this asset. diff --git a/lib/WebGUI/Asset/Wobject/Layout.pm b/lib/WebGUI/Asset/Wobject/Layout.pm index 5dea30c8b..fdcd62e1a 100644 --- a/lib/WebGUI/Asset/Wobject/Layout.pm +++ b/lib/WebGUI/Asset/Wobject/Layout.pm @@ -443,6 +443,36 @@ sub getContentLastModified { #------------------------------------------------------------------- +=head2 getContentLastModifiedBy + +Extend the base class to include the userid of the person that made last modification. + +=cut + +sub getContentLastModifiedBy { + my $self = shift; + my $mtime = $self->SUPER::getContentLastModified; + my $userId = $self->get('revisedBy'); + my $childIter = $self->getLineageIterator(["children"],{excludeClasses=>['WebGUI::Asset::Wobject::Layout']}); + while ( 1 ) { + my $child; + eval { $child = $childIter->() }; + if ( my $x = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound') ) { + $self->session->log->error($x->full_message); + next; + } + last unless $child; + my $child_mtime = $child->getContentLastModified; + if ($child_mtime > $mtime) { + $mtime = $child_mtime; + $userId = $child->get("revisedBy"); + } + } + return $userId; +} + +#------------------------------------------------------------------- + =head2 www_view Extend the base method to handle caching and ad rotation. diff --git a/lib/WebGUI/Asset/Wobject/StockData.pm b/lib/WebGUI/Asset/Wobject/StockData.pm index 6f1596d3b..e0e39e351 100644 --- a/lib/WebGUI/Asset/Wobject/StockData.pm +++ b/lib/WebGUI/Asset/Wobject/StockData.pm @@ -16,7 +16,8 @@ use WebGUI::Utility; use WebGUI::Asset::Wobject; use Finance::Quote; -our @ISA = qw(WebGUI::Asset::Wobject); +use Class::C3; +use base qw/WebGUI::Asset::Wobject WebGUI::AssetAspect::Dashlet/; #------------------------------------------------------------------- @@ -165,7 +166,9 @@ sub _convertToEpoch { if($time =~ m/pm/i) { $hour += 12; } + $hour ||= 0; $hour = $self->_appendZero($hour); + $minute ||= 0; $minute = $self->_appendZero($minute); my $epoch = eval {$self->session->datetime->humanToEpoch("$year-$month-$day $hour:$minute:00")}; return $epoch; @@ -184,21 +187,47 @@ List of stock symbols to find passed in as an array reference. Stock symbols sh =cut sub _getStocks { - my $self = shift; - my $stocks = $_[0]; + my $self = shift; + my $stocks = $_[0]; + my $session = $self->session; - # Create a new Finance::Quote object - my $q = Finance::Quote->new; - # Disable failover if specified - unless ($self->getValue("failover")) { - $q->failover(0); - } + # Create a new Finance::Quote object + my $q = Finance::Quote->new; + # Disable failover if specified + unless ($self->getValue("failover")) { + $q->failover(0); + } + # Hardcoded timeout for now. + $q->timeout(15); - # Hardcoded timeout for now. - $q->timeout(15); + my $source = $self->getValue('source'); + my %stocks = (); + my @stocks_to_fetch = (); + STOCK: foreach my $stock (@{$stocks}) { + $stock = uc $stock; + my $cache = WebGUI::Cache->new($session, [$self->getId, $source, $stock]); + if ($cache->get()) { + my $value = $cache->get(); + %stocks = (%stocks, %{ $value }); + } + else { + push @stocks_to_fetch, $stock; + } + } - # Fetch the stock information and return the results - return $q->fetch($self->getValue("source"),@{$stocks}); + # Fetch the information for uncached stocks, cache them individually, and build the composite data. + my %new_stocks = $q->fetch($source, @stocks_to_fetch); + foreach my $stock (@stocks_to_fetch) { + $stock = uc $stock; + my @stock_keys = grep { /$stock\b/ } keys %new_stocks; + my $cache = WebGUI::Cache->new($session, [$self->getId, $source, $stock]); + my %slice; + @slice{ @stock_keys } = @new_stocks{ @stock_keys }; + $slice{$stock,'last_fetch'} = time(); + $cache->set(\%slice, $self->get('cacheTimeout')); + %stocks = (%stocks, %slice); + } + return \%stocks; } #------------------------------------------------------------------- @@ -284,6 +313,7 @@ sub definition { tab=>'properties', label=> $i18n->get("default_stock_label"), hoverHelp=> $i18n->get("default_stock_label_description"), + dashletOverridable => 1, }, source=>{ fieldType=>"selectList", @@ -297,8 +327,16 @@ sub definition { fieldType=>"yesNo", defaultValue=>undef, label=> $i18n->get("failover_label"), - hoverHelp=> $i18n->get("failover_description") - } + hoverHelp=> $i18n->get("failover_description"), + }, + cacheTimeout => { + tab => "display", + fieldType => "interval", + defaultValue => 3600, + uiLevel => 5, + label => $i18n->get("cache timeout", 'Asset_Snippet'), + hoverHelp => $i18n->get("cache timeout help"), + }, ); push(@{$definition}, { @@ -370,7 +408,8 @@ sub view { $var->{'stock.display.url'} = $self->getUrl("func=displayStock;symbol="); #Build list of stocks as an array - my $defaults = $self->getValue("defaultStocks"); + my $overrides = $self->fetchUserOverrides($self->getParent->getId); + my $defaults = $overrides->{defaultStocks} || $self->getValue("defaultStocks"); #replace any windows newlines $defaults =~ s/\r//; my @array = split("\n",$defaults); diff --git a/lib/WebGUI/Asset/Wobject/WeatherData.pm b/lib/WebGUI/Asset/Wobject/WeatherData.pm index 2a4b373de..a0d528154 100644 --- a/lib/WebGUI/Asset/Wobject/WeatherData.pm +++ b/lib/WebGUI/Asset/Wobject/WeatherData.pm @@ -17,7 +17,8 @@ package WebGUI::Asset::Wobject::WeatherData; use strict; use Weather::Com::Finder; use WebGUI::International; -use base 'WebGUI::Asset::Wobject'; +use Class::C3; +use base qw/WebGUI::Asset::Wobject WebGUI::AssetAspect::Dashlet/; use WebGUI::Utility; #------------------------------------------------------------------- @@ -62,8 +63,17 @@ sub definition { defaultValue=>"Madison, WI\nToronto, Canada\n53536", tab=>"properties", hoverHelp=>$i18n->get("Your list of default weather locations"), - label=>$i18n->get("Default Locations") + label=>$i18n->get("Default Locations"), + dashletOverridable => 1, }, + cacheTimeout => { + tab => "display", + fieldType => "interval", + defaultValue => 3600, + uiLevel => 5, + label => $i18n->get("cache timeout", 'Asset_Snippet'), + hoverHelp => $i18n->get("cache timeout help"), + }, }; push(@{$definition}, { tableName=>'WeatherData', @@ -110,49 +120,72 @@ to be displayed within the page style sub view { my $self = shift; + my $session = $self->session; my %var; my $url = $self->session->url; if ($self->get("partnerId") ne "" && $self->get("licenseKey") ne "") { - foreach my $location (split("\n", $self->get("locations"))) { - my $weather = Weather::Com::Finder->new({ - 'partner_id' => $self->get("partnerId"), - 'license' => $self->get("licenseKey"), - 'cache' => '/tmp', - }); - next unless defined $weather; + my $overrides = $self->fetchUserOverrides($self->getParent->getId); + my $locations = $overrides->{locations} || $self->get('locations'); + foreach my $location (split("\n", $locations)) { + my $cache = WebGUI::Cache->new($session, [$self->getId, $location]); + my $loop_data; + my $link_data = []; + my $cached_data = $cache->get(); + if ($cached_data) { + $loop_data = $cached_data->{locations}; + $link_data = $cached_data->{links} || []; + } + else { + my $weather = Weather::Com::Finder->new({ + 'partner_id' => $self->get("partnerId"), + 'license' => $self->get("licenseKey"), + 'cache' => '/tmp', + }); + next unless defined $weather; - foreach my $foundLocation(@{$weather->find($location)}) { - my $current_conditions = $foundLocation->current_conditions; - my $conditions = $current_conditions->description; - $conditions =~ s/\b(\w)/uc($1)/eg; - my $tempC = $current_conditions->temperature; - my $tempF; - $tempF = sprintf("%.0f",(((9/5)*$tempC) + 32)) if($tempC); - my $icon = $current_conditions->icon || "na"; + foreach my $foundLocation(@{$weather->find($location)}) { + my $current_conditions = $foundLocation->current_conditions; + my $conditions = $current_conditions->description; + $conditions =~ s/\b(\w)/uc($1)/eg; + my $tempC = $current_conditions->temperature; + my $tempF; + $tempF = sprintf("%.0f",(((9/5)*$tempC) + 32)) if($tempC); + my $icon = $current_conditions->icon || "na"; - push(@{$var{'ourLocations.loop'}}, { - query => $location, - cityState => $foundLocation->name || $location, - sky => $conditions || 'N/A', - tempF => (defined $tempF)?$tempF:'N/A', - tempC => (defined $tempC)?$tempC:'N/A', - smallIcon => $url->extras("wobject/WeatherData/small_icons/".$icon.".png"), - mediumIcon => $url->extras("wobject/WeatherData/medium_icons/".$icon.".png"), - largeIcon => $url->extras("wobject/WeatherData/large_icons/".$icon.".png"), - iconUrl => $url->extras("wobject/WeatherData/medium_icons/".$icon.".png"), - iconAlt => $conditions, - }); - if (!$var{links_loop}) { - $var{links_loop} = []; + push @{$loop_data}, { + query => $location, + cityState => $foundLocation->name || $location, + sky => $conditions || 'N/A', + tempF => (defined $tempF)?$tempF:'N/A', + tempC => (defined $tempC)?$tempC:'N/A', + smallIcon => $url->extras("wobject/WeatherData/small_icons/".$icon.".png"), + mediumIcon => $url->extras("wobject/WeatherData/medium_icons/".$icon.".png"), + largeIcon => $url->extras("wobject/WeatherData/large_icons/".$icon.".png"), + iconUrl => $url->extras("wobject/WeatherData/medium_icons/".$icon.".png"), + iconAlt => $conditions, + last_fetch => time(), + }; for my $lnk (@{$foundLocation->current_conditions->{WEATHER}{lnks}{link}} ) { - push @{$var{links_loop}}, { - link_url => $lnk->{l}, - link_title => $lnk->{t}, - }; + if (! $link_data) { + push @{ $link_data }, { + link_url => $lnk->{l}, + link_title => $lnk->{t}, + }; + } } } - } + my $cache = WebGUI::Cache->new($session, [$self->getId, $location]); + my $cached_data = { + locations => $loop_data, + links => $link_data, + }; + $cache->set($cached_data, $self->get('cacheTimeout')); + } + push @{$var{'ourLocations.loop'}}, @{ $loop_data }; + if (!$var{links_loop}) { + $var{links_loop} = $link_data; + } } } return $self->processTemplate(\%var, undef, $self->{_viewTemplate}); diff --git a/lib/WebGUI/AssetAspect/Dashlet.pm b/lib/WebGUI/AssetAspect/Dashlet.pm new file mode 100644 index 000000000..ecfee7ea1 --- /dev/null +++ b/lib/WebGUI/AssetAspect/Dashlet.pm @@ -0,0 +1,129 @@ +package WebGUI::AssetAspect::Dashlet; + +use strict; +use Class::C3; +use JSON qw/to_json from_json/; + +=head1 NAME + +WebGUI::AssetAspect::Dashlet - Implement features to turn Assets into Dashlets + +=head1 SYNOPSIS + +This Aspect provides methods that allow a Dashboard to determine, store and retrieve +customization options for Assets. + +=head1 DESCRIPTION + + +=head1 METHODS + +#---------------------------------------------------------------------------- + +=head2 fetchUserOverrides ($dashboardAssetId, [$userId]) + +Retrieve user preferences for a particular dashboard and user for this Asset from the database. + +=head3 $dashboardId + +The assetId of the dashboard to reference. + +=head3 $userId + +The userId to whose preferences should be returned. Uses the current session user if omitted. + +=cut + +sub fetchUserOverrides { + my $self = shift; + my $dashboardAssetId = shift; + my $userId = shift || $self->session->user->userId; + my $properties_json = $self->session->db->quickScalar('select properties from Dashboard_userPrefs where dashboardAssetId=? and userId=? and dashletAssetId=?',[$dashboardAssetId, $userId, $self->getId,]); + $properties_json ||= '{}'; + my $properties = from_json($properties_json); + return $properties; +} + +#---------------------------------------------------------------------------- + +=head2 getOverrideFormDefinition + +Return an array ref of form properties. The form properties are those that the +Asset has marked as being able to be overridden by a Dashboard asset by giving +the property the dashletOverridable flag. + +Assets that want to allow additional properties outside of their definition should +override and extend this method. + +=cut + +sub getOverrideFormDefinition { + my $self = shift; + my $session = $self->session; + my @definitions = reverse @{ $self->definition($session) }; + my @properties = (); + foreach my $definition (@definitions) { + foreach my $property_name (keys %{ $definition->{properties} }) { + my $property = $definition->{properties}->{$property_name}; + next unless $property->{dashletOverridable}; + $property->{name} = $property_name; + push @properties, $property; + } + } + return @properties; +} + +#---------------------------------------------------------------------------- + +=head2 getUserOverrides + +Store user preferences for this Asset. This is direct reference from inside the object, so +if you plan to modify the data, Clone it first. + +=cut + +sub getUserOverrides { + return shift->{_userOverrides}; +} + +#---------------------------------------------------------------------------- + +=head2 setUserOverrides + +Store user preferences for this Asset. + +=cut + +sub setUserOverrides { + shift->{_userOverrides} = shift; +} + +#---------------------------------------------------------------------------- + +=head2 storeUserOverrides ($dashboardAssetId, $properties, [$userId]) + +Store user preferences for a particular dashboard and user for this Asset to the database. + +=head3 $dashboardId + +The assetId of the dashboard to reference. + +=head3 $userId + +The userId to whose preferences should be returned. Uses the current session user if omitted. + +=cut + +sub storeUserOverrides { + my $self = shift; + my $session = $self->session; + my $dashboardAssetId = shift; + my $properties = shift; + my $userId = shift || $session->user->userId; + my $properties_json = to_json($properties); + $session->db->write('DELETE FROM Dashboard_userPrefs where dashboardAssetId=? and userId=? and dashletAssetId=?',[$dashboardAssetId, $userId, $self->getId]); + $session->db->write('INSERT INTO Dashboard_userPrefs (dashboardAssetId, userId, dashletAssetId, properties) VALUES (?,?,?,?)', [$dashboardAssetId, $userId, $self->getId, $properties_json]); +} + + +1; # You can't handle the truth diff --git a/lib/WebGUI/Form/Group.pm b/lib/WebGUI/Form/Group.pm index 6f6da33bc..04c35f21f 100644 --- a/lib/WebGUI/Form/Group.pm +++ b/lib/WebGUI/Form/Group.pm @@ -141,6 +141,31 @@ sub getValueAsHtml { } +#------------------------------------------------------------------- + +=head2 headTags ( ) + +Set the head tags for this form plugin + +=cut + +sub headTags { + my $self = shift; + my $style = $self->session->style; + my $url = $self->session->url; + $style->setLink($url->extras('yui/build/container/assets/skins/sam/container.css'), { rel => 'stylesheet', type => 'text/css' }); + $style->setLink($url->extras('yui/build/button/assets/skins/sam/button.css'), { rel => 'stylesheet', type => 'text/css' }); + $style->setScript($url->extras('yui/build/connection/connection-min.js'), { type=>'text/javascript' }); + $style->setScript($url->extras('yui/build/element/element-min.js'), { type=>'text/javascript' }); + $style->setScript($url->extras('yui/build/button/button-min.js'), { type=>'text/javascript' }); + $style->setScript($url->extras('yui/build/container/container-min.js'), { type=>'text/javascript' }); + $style->setScript($url->extras('yui-webgui/build/form/form.js'), { type=>'text/javascript' }); + $style->setScript($url->extras('yui/build/json/json-min.js'), {type => 'text/javascript'}); + $style->setScript($url->extras('yui-webgui/build/i18n/i18n.js'), {type => 'text/javascript'} ); + $style->setScript($url->extras('yui-webgui/build/form/groupManager.js'), { type=>'text/javascript' }); + $style->setLink($url->extras('yui-webgui/build/form/groupManager.css'), { rel => 'stylesheet', type => 'text/css' }); +} + #------------------------------------------------------------------- =head2 isDynamicCompatible ( ) @@ -180,9 +205,9 @@ Creates a series of hidden fields representing the data in the list. =cut sub toHtmlAsHidden { - my $self = shift; - $self->set("options", $self->session->db->buildHashRef("select groupId,groupName from groups")); - return $self->SUPER::toHtmlAsHidden(); + my $self = shift; + $self->set("options", $self->session->db->buildHashRef("select groupId,groupName from groups")); + return $self->SUPER::toHtmlAsHidden(); } #------------------------------------------------------------------- @@ -194,14 +219,187 @@ Renders the form field to HTML as a table row complete with labels, subtext, hov =cut sub toHtmlWithWrapper { - my $self = shift; - if ($self->session->user->isAdmin) { - my $subtext = $self->session->icon->manage("op=listGroups"); - $self->set("subtext",$subtext . $self->get("subtext")); + my $self = shift; + my $session = $self->session; + my $user = $session->user; + if ($user->isAdmin) { + my $subtext = $session->icon->manage("op=listGroups"); + $self->set("subtext", $subtext . $self->get("subtext")); + } + my $dialog = $self->get('name') . '_groupDialog'; + my $group_manager = $user->isInGroup($session->setting->get('groupIdAdminGroup')); + my $form; + if ($group_manager) { + my $i18n = WebGUI::International->new($self->session,'Icon'); + my $name = $self->get('name'); + my $groupId = $self->getOriginalValue; + my $extra_subtext = qq!!. $i18n->get('Edit') .qq!!; + $extra_subtext .= qq!!. $i18n->get('Add') .qq!!; + $self->set("subtext", $self->get('subtext').$extra_subtext); + } + $self->headTags; + $form .= $self->SUPER::toHtmlWithWrapper; + return $form; +} + +#------------------------------------------------------------------- + +=head2 www_groupMembers ($session) + +Returns a list of users that are in the sub-group specified by the form variable +variable C. Data returned is in JSON format. + +This is a class method. + +=head3 $session + +A WebGUI::Session object. + +=cut + +sub www_groupMembers { + my $session = shift; + return '{}' unless $session->user->isInGroup($session->setting->get('groupIdAdminGroup')); + my $groupId = $session->form->param('groupId'); + return '{}' unless $groupId; + + my $group = WebGUI::Group->new($session, $groupId); + return '{}' unless $group; + + my $results = { + groupName => $group->name, + users => [], + groups => [], + }; + my $userIds = $group->getUsers('withoutExpired'); + USER: foreach my $userId (@{ $userIds }) { + my $user = WebGUI::User->new($session, $userId); + next USER unless $user; + push @{$results->{users}}, + { + username => $user->username, + userId => $userId, + }; + } + my $groupIds = $group->getGroupsIn(0); ##Without recursion + GROUP: foreach my $groupId (@{ $groupIds }) { + my $group = WebGUI::Group->new($session, $groupId); + next GROUP unless $group; + push @{$results->{groups}}, + { + groupName => $group->name, + groupId => $groupId, + }; + } + + return JSON::to_json($results); +} + + +#------------------------------------------------------------------- + +=head2 www_saveGroup ($session) + +Save new information about the membership of a group, which users have +been added and deleted, and which groups have been added and deleted. + +This is a subroutine, not a class method, not an object method. + +=head3 $session + +A WebGUI::Session object. + +=head3 Expected form variables + +=head4 groupId + +The GUID for the group to modify. + +=head4 groupName + +The name of the group. This is always set, so it should always be included. + +=head4 usersAdded + +A list of userId's for users who were added. + +=head4 usersDeleted + +A list of userId's for users who were deleted. Deleting happens after adding. + +=head4 groupsAdded + +A list of groupId's for groups who were added. + +=head4 groupsDeleted + +A list of groupId's for groups who were deleted. Deleting happens after adding. + +=cut + +sub www_saveGroup { + my $session = shift; + $session->log->warn("hit the group plugin"); + return '{}' unless $session->user->isInGroup($session->setting->get('groupIdAdminGroup')); + my $form = $session->form; + my $groupId = $form->get('groupId'); + my $group = WebGUI::Group->new($session, $groupId); + $session->log->warn("got groupId: $groupId"); + return '{}' unless $group; + + $session->log->warn("updating group data, name = ".$form->get('groupName')); + $group->name($form->get('groupName')); + my @usersAdded = $form->get('usersAdded'); + use Data::Dumper; + $session->log->warn("users added ".Dumper(\@usersAdded)); + $group->addUsers(\@usersAdded); + my @usersDeleted = $form->get('usersDeleted'); + $group->deleteUsers(\@usersDeleted); + my @groupsAdded = $form->get('groupsAdded'); + $session->log->warn("groups added ".Dumper(\@groupsAdded)); + $group->addGroups(\@groupsAdded); + my @groupsDeleted = $form->get('groupsDeleted'); + $group->deleteGroups(\@groupsDeleted); + + return JSON::to_json({ groupId => $group->getId, groupName => $group->name, originalGroupId => $groupId }); +} + +#------------------------------------------------------------------- + +=head2 www_searchGroups ($session) + +Returns groups that match the supplied group name. Group name is specified via the form +variable C. A list of groups will be returned of up to 15 names and groupIds. + +This is a subroutine, not a class method, not an object method. + +=head3 $session + +A WebGUI::Session object. + +=head3 Sample JSON + +{ + 'results' : [ + { + 'groupId': 'someGroupId', + 'groupName' : 'Great Group' } - return $self->SUPER::toHtmlWithWrapper; + //Other hashes may be in the list, or it could be completely empty + ] +} + +=cut + +sub www_searchGroups { + my $session = shift; + return '{"results":[]}' unless $session->user->isInGroup($session->setting->get('groupIdAdminGroup')); + my $search = $session->form->param('query'); + + my $results = $session->db->buildArrayRefOfHashRefs(q|select groupId, groupName from groups where groupName like CONCAT(?, '%') and showInForms=1 LIMIT 15|, [ $search ]); + + return JSON::to_json({ results => $results }); } 1; - diff --git a/lib/WebGUI/Form/User.pm b/lib/WebGUI/Form/User.pm index 413a6f135..e4281cb25 100644 --- a/lib/WebGUI/Form/User.pm +++ b/lib/WebGUI/Form/User.pm @@ -170,5 +170,25 @@ sub toHtml { )->toHtml).$manage; } +#------------------------------------------------------------------- + +=head2 www_searchUsers + +Returns users that match the supplied username. Username is specified via the form +variable C. A list of usernames will be returned of up to 15 names and userIds. + +=cut + +sub www_searchUsers { + my $session = shift; + return '{"results":[]}' unless $session->user->isInGroup($session->setting->get('groupIdAdminUser')); + my $search = $session->form->param('query'); + + my $results = $session->db->buildArrayRefOfHashRefs(q|select userId, username from users where username like CONCAT(?, '%') LIMIT 15|, [ $search ]); + + return JSON::to_json({ results => $results }); +} + + 1; diff --git a/lib/WebGUI/Help/Asset_Dashboard.pm b/lib/WebGUI/Help/Asset_Dashboard.pm new file mode 100644 index 000000000..7deb5f819 --- /dev/null +++ b/lib/WebGUI/Help/Asset_Dashboard.pm @@ -0,0 +1,60 @@ +package WebGUI::Help::Asset_Dashboard; +use strict; + +our $HELP = { + + 'dashboard template' => { + title => 'Dashboard Template Variables', + isa => [ + { namespace => "Asset_Dashboard", + tag => "dashboard asset template variables" + }, + { namespace => "Asset", + tag => "asset template" + }, + ], + fields => [], + variables => [ + { name => 'dragger.init' }, + { name => 'fullUrl' }, + { name => 'canEdit' }, + { name => 'positionN_loop', + variables => [ + { 'name' => 'id' }, + { 'name' => 'content' }, + { 'name' => 'dashletTitle' }, + { 'name' => 'shortcutUrl' }, + { 'name' => 'dashletUrl' }, + { 'name' => 'canDelete' }, + { 'name' => 'canMove' }, + { 'name' => 'canPersonalize' }, + { 'name' => 'showReloadIcon' }, + { 'name' => 'canEditUserPrefs' }, + { 'name' => 'editFormUrl' }, + ] + }, + ], + related => [] + }, + + 'dashboard asset template variables' => { + private => 1, + title => 'dashboard asset template variables title', + isa => [ + { namespace => "Asset_Wobject", + tag => "wobject template variables" + }, + ], + fields => [], + variables => [ + { name => 'templateId' }, + { name => 'adminsGroupId' }, + { name => 'usersGroupId' }, + { name => 'isInitialized' }, + ], + related => [] + }, + +}; + +1; diff --git a/lib/WebGUI/Macro/LastUpdatedBy.pm b/lib/WebGUI/Macro/LastUpdatedBy.pm new file mode 100644 index 000000000..cd1b50547 --- /dev/null +++ b/lib/WebGUI/Macro/LastUpdatedBy.pm @@ -0,0 +1,48 @@ +package WebGUI::Macro::LastUpdatedBy; + +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 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 strict; +use WebGUI::Asset; +use WebGUI::User; + +=head1 NAME + +Package WebGUI::Macro::LastUpdatedBy + +=head1 DESCRIPTION + +Macro for displaying the username of the user that made the most recent revision of current Asset. + +=head2 process ( ) + +Display the username, if the user still exists in the system. If not, of if the user does not +have a username, then display an internationalized label for "Unknown". + +=cut + + +#------------------------------------------------------------------- +sub process { + my $session = shift; + return '' unless $session->asset; + + my $userId = $session->asset->getContentLastModifiedBy(); + my $user = WebGUI::User->new($session, $userId); + if ($user && $user->username) { + return $user->username; + } + + my $i18n = WebGUI::International->new($session,'Macro_LastModified'); + return $i18n->get('Unknown'); +} + +1; diff --git a/lib/WebGUI/Operation.pm b/lib/WebGUI/Operation.pm index 7c9a9204b..d3c56f3a3 100644 --- a/lib/WebGUI/Operation.pm +++ b/lib/WebGUI/Operation.pm @@ -130,6 +130,8 @@ sub getOperations { 'listGroups' => 'Group', 'manageGroupsInGroup' => 'Group', 'manageUsersInGroup' => 'Group', + 'manageGroups' => 'Group', + 'updateGroupUsers' => 'Group', 'viewHelp' => 'Help', 'viewHelpIndex' => 'Help', diff --git a/lib/WebGUI/i18n/English/Asset_Dashboard.pm b/lib/WebGUI/i18n/English/Asset_Dashboard.pm index ea923edd6..77e80253e 100644 --- a/lib/WebGUI/i18n/English/Asset_Dashboard.pm +++ b/lib/WebGUI/i18n/English/Asset_Dashboard.pm @@ -30,16 +30,6 @@ our $I18N = { message => q|The group whose users may save their personalizations/preferences to the site. If someone is in the "Who can view?" group but not in this group, they can personalize the arrangement of the Dashlets (whose positions will be saved in cookies), but they will not be able to edit the preferences of any particular Dashlet.|, lastUpdated => 1133619940 }, - 'dashboard template field label' => { - message => q|Dashboard Template|, - lastUpdated => 1133619940 - }, - - 'dashboard template field label' => { - message => q|Dashboard Template|, - lastUpdated => 1133619940 - }, - 'assets to hide' => { message => q|Assets To Hide.|, lastUpdated => 1118942468 @@ -57,6 +47,168 @@ checkbox for any Asset that you do not want displayed in the Page Layout Asset. lastUpdated => 1230356526, }, + 'Edit Dashlet' => { + message => q|Edit Dashlet|, + lastUpdated => 1230356526, + context => q|A dashlet is an asset being displayed by the Dashboard. It may not have a translation.|, + }, + + 'Is static' => { + message => q|Is static|, + lastUpdated => 1230356526, + context => q|Can it be moved, or rearranged?|, + }, + + 'Is static help' => { + message => q|Can this dashlet be moved around on the dashboard by users of the Dashboard?|, + lastUpdated => 1230356526, + context => q|Can it be moved, or rearranged?|, + }, + + 'Is required' => { + message => q|Is required|, + lastUpdated => 1230356526, + context => q|Can it be deleted from a dashboard by a user?|, + }, + + 'Is required help' => { + message => q|Can this dashlet be deleted from the dashboard by users of the Dashboard?|, + lastUpdated => 1230356526, + context => q|Can it be moved, or rearranged?|, + }, + + 'Dashboard Template Variables' => { + message => q|Dashboard Template Variables|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'dragger.init' => { + message => q|Javascript necessary to initialize the Dashboard. It should be placed at the bottom of the template.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'fullUrl' => { + message => q|The full URL to this Dashboard, including sitename and any gateway configuration.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'canEdit' => { + message => q|A boolean which will be true if the current user can edit this Dashboard.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'positionN_loop' => { + message => q|By default, there are four positions, numbered 1, 2, 3 and 4. Each loop contains the list of assets that have been placed into it. Position 1 is special, because it also contains any assets which have not been specifically placed by the user.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'id' => { + message => q|Asset ID of the current dashlet.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'content' => { + message => q|The dashlet's content|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'dashletTitle' => { + message => q|The title of the dashlet, the raw asset title.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'shortcutUrl' => { + message => q|If this dashlet is a shortcut, the URL of the shortcut.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'dashletUrl' => { + message => q|The URL to this dashlet.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'canDelete' => { + message => q|A boolean that is true if the current user is in the group who can personalize the dashboard and if this dashlet is not set to be required.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'canMove' => { + message => q|A boolean that is true if the current user is in the group who can personalize the dashboard and if this dashlet is not set to be static.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'canPersonalize' => { + message => q|A boolean that is true if the current user is in the group who can personalize the dashboard.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'showReloadIcon' => { + message => q|A boolean that is true if this dashlet is a shortcut, and the Show Reload Icon property is set to be true.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'canEditUserPrefs' => { + message => q|A boolean that is true if the current user is in the Registered Users group, and the dashlet is a Shortcut, and the Shortcut has preferences that can be configured.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'dashboard asset template variables title' => { + message => q|Dashboard Asset Template Variables|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'templateId' => { + message => q|The GUID of the template used to display the dashboard|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'adminsGroupId' => { + message => q|The GUID of the group that is allowed to set the default appearance of the dashboard for visitors.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'usersGroupId' => { + message => q|The GUID of the group that is allowed to change the appearance of their own dashboard.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'isInitialized' => { + message => q|A boolean which is true if this Dashboard has been initialized. You really don't need to know more than that.|, + lastUpdated => 1230356526, + context => q|Template variable help|, + }, + + 'Add New Content' => { + message => q|Add New Content|, + lastUpdated => 1230356526, + context => q|i18n phrase for the view template|, + }, + + 'editFormUrl' => { + message => q|The URL to fetch the user overrides form for this dashlet, whether it is Shortcut based or a regular asset with overrides.|, + lastUpdated => 1230356526, + context => q|i18n phrase for the view template|, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_StockData.pm b/lib/WebGUI/i18n/English/Asset_StockData.pm index 53509b3e5..77baed71c 100644 --- a/lib/WebGUI/i18n/English/Asset_StockData.pm +++ b/lib/WebGUI/i18n/English/Asset_StockData.pm @@ -514,6 +514,12 @@ our $I18N = { lastUpdated => 1229493261, }, + 'cache timeout help' => { + message => q|How long should lookups for each stock symbol be cached internally? Note, the default template has javascript that does fetches on the client side.|, + context => q|Template variable help|, + lastUpdated => 1229493261, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_WeatherData.pm b/lib/WebGUI/i18n/English/Asset_WeatherData.pm index 1a2ed5be2..d22c9ed87 100644 --- a/lib/WebGUI/i18n/English/Asset_WeatherData.pm +++ b/lib/WebGUI/i18n/English/Asset_WeatherData.pm @@ -102,6 +102,12 @@ our $I18N = { lastUpdated => 1167972337 }, + 'cache timeout help' => { + message => q|How long should lookups for each location be cached internally?|, + context => q|Template variable help|, + lastUpdated => 1229493261, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Form_Group.pm b/lib/WebGUI/i18n/English/Form_Group.pm new file mode 100644 index 000000000..8b20f7d68 --- /dev/null +++ b/lib/WebGUI/i18n/English/Form_Group.pm @@ -0,0 +1,33 @@ +package WebGUI::i18n::English::Form_Group; + +use strict; ##Required for all good Perl::Critic compliant code + +our $I18N = { + 'Group Manager' => { + message => q|Group Manager|, + lastUpdated => 1131394070, #seconds from the epoch + context => q|A form to add or do minor edits to groups| + }, + + 'Add User...' => { + message => q|Add User...|, + lastUpdated => 1131394070, #seconds from the epoch + context => q|Hint for a text box where you enter in a username| + }, + + 'Add Group...' => { + message => q|Add Group...|, + lastUpdated => 1131394070, #seconds from the epoch + context => q|Hint for a text box where you enter in a groupname| + }, + + 'New Group' => { + message => q|New Group|, + lastUpdated => 1131394070, #seconds from the epoch + context => q|Label for a button to create a new group.| + }, + +}; + +1; +#vim:ft=perl diff --git a/lib/WebGUI/i18n/English/Icon.pm b/lib/WebGUI/i18n/English/Icon.pm index 73ec3c974..7dcae79fe 100644 --- a/lib/WebGUI/i18n/English/Icon.pm +++ b/lib/WebGUI/i18n/English/Icon.pm @@ -92,6 +92,12 @@ our $I18N = { lastUpdated => 1096319562 }, + 'Add' => { + message => q|Add|, + lastUpdated => 1096319562, + context => q|to add something new, to create/acquire|, + }, + 'Edit help' => { message => q|Edit the properties of this Asset. This icon is only available if the asset isn't locked, or if it is locked and you are using the tag it was edited under.|, lastUpdated => 1165448622 diff --git a/lib/WebGUI/i18n/English/Macro_LastModified.pm b/lib/WebGUI/i18n/English/Macro_LastModified.pm index 1c391a900..328e58b4e 100644 --- a/lib/WebGUI/i18n/English/Macro_LastModified.pm +++ b/lib/WebGUI/i18n/English/Macro_LastModified.pm @@ -8,6 +8,12 @@ our $I18N = { lastUpdated => 1134969093 }, + 'Unknown' => { + message => q|Unknown|, + lastUpdated => 1134969093, + context => q|meaning, we do not know who it is, and it is not Visitor|, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/WebGUI.pm b/lib/WebGUI/i18n/English/WebGUI.pm index 45a458b18..440b8138b 100644 --- a/lib/WebGUI/i18n/English/WebGUI.pm +++ b/lib/WebGUI/i18n/English/WebGUI.pm @@ -3049,6 +3049,12 @@ or are under your current version tag.

context => q|Label of the cancel button| }, + 'Cancel' => { + message => q|Cancel|, + lastUpdated =>1092930637, + context => q|Label of the cancel button, with capital C| + }, + 'trash' => { message => q|Trash|, lastUpdated =>1211131614, diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 21c4d915d..d6d2df0bf 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -71,6 +71,7 @@ checkModule("Test::LongString", 0.13, 2 ); checkModule("Test::Exception", 0.27, 2 ); checkModule("Test::Differences", 0.5, 2 ); checkModule("Test::Class", 0.31, 2 ); +checkModule("Test::MockTime", 0.09, 2 ); checkModule("Pod::Coverage", 0.19, 2 ); checkModule("Text::Balanced", 2.00, 2 ); checkModule("Digest::MD5", 2.38 ); diff --git a/t/Asset/Asset.t b/t/Asset/Asset.t index 9fc631575..fee63d6c2 100644 --- a/t/Asset/Asset.t +++ b/t/Asset/Asset.t @@ -172,7 +172,7 @@ sub definition { package main; -plan tests => 134 +plan tests => 137 + scalar(@fixIdTests) + scalar(@fixTitleTests) + 2*scalar(@getTitleTests) #same tests used for getTitle and getMenuTitle @@ -1011,8 +1011,12 @@ $session->http->setRedirectLocation(''); is $clippedAsset->checkView(), 'chunked', 'checkView: returns "chunked" when admin is on for cut asset'; is $session->http->getRedirectLocation, $clippedAsset->getUrl('func=manageClipboard'), '... cut asset sets redirect to manageClipboard'; -#---------------------------------------------------------------------------- -# packed head tags +################################################################ +# +# Packed head tags +# +################################################################ + use HTML::Packer; my $asset = WebGUI::Asset->getImportNode( $session )->addChild({ className => 'WebGUI::Asset::Snippet', @@ -1035,6 +1039,30 @@ is $asset->get('extraHeadTagsPacked'), $packed, 'extraHeadTagsPacked'; $asset->update({ extraHeadTags => '' }); ok !$asset->get('extraHeadTagsPacked'), 'extraHeadTagsPacked cleared'; +################################################################ +# +# getContentLastModifiedBy +# +################################################################ + +{ + my $revised_user1 = WebGUI::User->new($session, 'new'); + my $revised_user2 = WebGUI::User->new($session, 'new'); + WebGUI::Test->addToCleanup($revised_user1, $revised_user2 ); + $session->user({user => $revised_user1}); + my $versionTag = WebGUI::VersionTag->getWorking($session); + my $asset = WebGUI::Asset->getImportNode( $session )->addChild({ + className => 'WebGUI::Asset::Snippet', + }, undef, 12); + $versionTag->commit; + $asset = $asset->cloneFromDb; + WebGUI::Test->addToCleanup($asset, $versionTag); + is $asset->getContentLastModifiedBy, $asset->get('revisedBy'), 'getContentLastModifiedBy returns revisedBy for most assets'; + is $asset->getContentLastModifiedBy, $revised_user1->userId, '... real userId check'; + $session->user({user => $revised_user2}); + $asset = $asset->addRevision({ title => 'titular', }, 14); + is $asset->getContentLastModifiedBy, $revised_user2->userId, '... check that a new revision tracks'; +} ##Return an array of hashrefs. Each hashref describes a test ##for the fixId method. diff --git a/t/Asset/Wobject/Folder.t b/t/Asset/Wobject/Folder.t new file mode 100644 index 000000000..7336595f3 --- /dev/null +++ b/t/Asset/Wobject/Folder.t @@ -0,0 +1,67 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 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"; + +use Test::MockTime qw/:all/; ##Must be loaded before all other code +use WebGUI::Test; +use WebGUI::Session; +use Test::More tests => 3; # increment this value for each test you create +use WebGUI::Asset::Wobject::Folder; + +my $session = WebGUI::Test->session; + +# Do our work in the import node +my $node = WebGUI::Asset->getImportNode($session); + +################################################################ +# +# getContentLastModifiedBy +# +################################################################ + +my $revised_user1 = WebGUI::User->new($session, 'new'); +my $revised_user2 = WebGUI::User->new($session, 'new'); +WebGUI::Test->addToCleanup($revised_user1, $revised_user2 ); +$session->user({userId => 3}); +set_relative_time(-600); +WebGUI::Test->addToCleanup(sub { restore_time(); }); +my $versionTag = WebGUI::VersionTag->getWorking($session); +my $folder = $node->addChild({ + className => 'WebGUI::Asset::Wobject::Folder', +}, undef, 12); +$session->user({user => $revised_user1}); +my $snip1 = $folder->addChild({ + className => 'WebGUI::Asset::Snippet', +}, undef, 14); + +set_relative_time(-500); +$session->user({user => $revised_user2}); +my $snip2 = $folder->addChild({ + className => 'WebGUI::Asset::Snippet', +}, undef, 16); + +$folder = $folder->cloneFromDb; +$snip1 = $snip1->cloneFromDb; +$snip2 = $snip2->cloneFromDb; +WebGUI::Test->addToCleanup($folder); +is $folder->getContentLastModifiedBy, $snip2->get('revisedBy'), 'getContentLastModifiedBy returns revisedBy for most recent child asset'; +is $folder->getContentLastModifiedBy, $revised_user2->userId, '... real userId check'; +$session->user({user => $revised_user1}); + +set_relative_time(-100); + +$snip1 = $snip1->addRevision({ title => 'titular', }, 18); +is $folder->getContentLastModifiedBy, $revised_user1->userId, '... check that a new revision tracks'; + + diff --git a/t/Asset/Wobject/Layout.t b/t/Asset/Wobject/Layout.t new file mode 100644 index 000000000..c69cb794b --- /dev/null +++ b/t/Asset/Wobject/Layout.t @@ -0,0 +1,67 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 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"; + +use Test::MockTime qw/:all/; ##Must be loaded before all other code +use WebGUI::Test; +use WebGUI::Session; +use Test::More tests => 3; # increment this value for each test you create +use WebGUI::Asset::Wobject::Layout; + +my $session = WebGUI::Test->session; + +# Do our work in the import node +my $node = WebGUI::Asset->getImportNode($session); + +################################################################ +# +# getContentLastModifiedBy +# +################################################################ + +my $revised_user1 = WebGUI::User->new($session, 'new'); +my $revised_user2 = WebGUI::User->new($session, 'new'); +WebGUI::Test->addToCleanup($revised_user1, $revised_user2 ); +$session->user({userId => 3}); +set_relative_time(-600); +WebGUI::Test->addToCleanup(sub { restore_time(); }); +my $versionTag = WebGUI::VersionTag->getWorking($session); +my $page = $node->addChild({ + className => 'WebGUI::Asset::Wobject::Layout', +}, undef, 12); +$session->user({user => $revised_user1}); +my $snip1 = $page->addChild({ + className => 'WebGUI::Asset::Snippet', +}, undef, 14); + +set_relative_time(-500); +$session->user({user => $revised_user2}); +my $snip2 = $page->addChild({ + className => 'WebGUI::Asset::Snippet', +}, undef, 16); + +$page = $page->cloneFromDb; +$snip1 = $snip1->cloneFromDb; +$snip2 = $snip2->cloneFromDb; +WebGUI::Test->addToCleanup($page); +is $page->getContentLastModifiedBy, $snip2->get('revisedBy'), 'getContentLastModifiedBy returns revisedBy for most recent child asset'; +is $page->getContentLastModifiedBy, $revised_user2->userId, '... real userId check'; +$session->user({user => $revised_user1}); + +set_relative_time(-100); + +$snip1 = $snip1->addRevision({ title => 'titular', }, 18); +is $page->getContentLastModifiedBy, $revised_user1->userId, '... check that a new revision tracks'; + + diff --git a/t/Asset/Wobject/StockData.t b/t/Asset/Wobject/StockData.t new file mode 100644 index 000000000..e6ab0417d --- /dev/null +++ b/t/Asset/Wobject/StockData.t @@ -0,0 +1,65 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 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 +#------------------------------------------------------------------ + +# This tests the AssetReport asset +# +# + +use Test::MockTime qw/:all/; +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use Test::Deep; +use JSON; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Cache; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode( $session ); + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 6; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# Asset Report creation +my $asset = $node->addChild( { + className => 'WebGUI::Asset::Wobject::StockData', + source => 'usa', + defaultStocks => "GWR", + cacheTimeout => 2000, +} ); +WebGUI::Test->addToCleanup($asset); + +my $now = time(); +set_relative_time(-1000); + +my $stocks = $asset->_getStocks(["GWR"]); +is $stocks->{qw/GWR symbol/}, 'GWR', 'stock fetch successful'; +cmp_ok $stocks->{qw/GWR last_fetch/}, '<', $now-500, 'last_fetch set in the past'; +my $last_fetch = $stocks->{qw/GWR last_fetch/}; + +my $cache = WebGUI::Cache->new($session, [$asset->getId, 'usa', 'GWR']); +is $cache->get()->{qw/GWR symbol/}, 'GWR', 'cache loaded with valid data'; + +restore_time(); + +my $stocks2 = $asset->_getStocks([qw/GWR UNP/]); +is $stocks2->{qw/UNP symbol/}, 'UNP', 'stock fetch successful, new stock'; +is $stocks2->{qw/GWR symbol/}, 'GWR', '... cached stock'; +is $stocks2->{qw/GWR last_fetch/}, $last_fetch, 'GWR stock lookup was cached'; + +#vim:ft=perl diff --git a/t/Asset/Wobject/WeatherData.t b/t/Asset/Wobject/WeatherData.t new file mode 100644 index 000000000..b875ae473 --- /dev/null +++ b/t/Asset/Wobject/WeatherData.t @@ -0,0 +1,111 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 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 +#------------------------------------------------------------------ + +# This tests the AssetReport asset +# +# + +use Test::MockTime qw/:all/; +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use Test::Deep; +use Clone qw/clone/; + +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Cache; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode( $session ); + +#---------------------------------------------------------------------------- +# Tests + +my $can_test = 1; + +my $partnerId = $session->config->get('testing/WeatherData_partnerId'); +if (!$partnerId) { + $partnerId = 'partnerId'; + $can_test = 0; +} + +my $licenseKey = $session->config->get('testing/WeatherData_licenseKey'); +if (!$licenseKey) { + $partnerId = 'licenseKey'; + $can_test = 0; +} + +if ($can_test) { + plan tests => 7; # Increment this number for each test you create +} +else { + plan skip_all => 'Missing credentials for Weather.com'; +} + +#---------------------------------------------------------------------------- +# Asset Report creation + + #1234567890123456789012# +my $templateId = 'FAKE_WEATHER_TEMPLATEq'; + +my $templateMock = Test::MockObject->new({}); +$templateMock->set_isa('WebGUI::Asset::Template'); +$templateMock->set_always('getId', $templateId); +my $templateVars; +$templateMock->mock('process', sub { $templateVars = clone $_[1]; } ); + +my $asset = $node->addChild( { + className => 'WebGUI::Asset::Wobject::WeatherData', + cacheTimeout => 2000, + partnerId => $partnerId, + licenseKey => $licenseKey, + locations => "53715", + templateId => $templateId, +} ); +WebGUI::Test->addToCleanup($asset); + +my $now = time(); +diag $now; +set_relative_time(-1000); +diag time(); + +WebGUI::Test->mockAssetId($templateId, $templateMock); +$asset->prepareView(); +$asset->view(); + +my $weather_data = $templateVars->{'ourLocations.loop'}->[0]; + +is $weather_data->{cityState}, 'Madison, WI (53715)', 'data from weather.com returned'; +my $last_fetch = $weather_data->{last_fetch}; +diag $last_fetch; +cmp_ok $last_fetch, '<', $now-500, 'last_fetch set in the past'; + +my $cache = WebGUI::Cache->new($session, [$asset->getId, '53715']); +is $cache->get()->{'locations'}->[0]->{cityState}, 'Madison, WI (53715)', 'cache loaded with valid data'; + +restore_time(); + +$cache = WebGUI::Cache->new($session, [$asset->getId, '53715']); +is $cache->get()->{'locations'}->[0]->{cityState}, 'Madison, WI (53715)', 'cache loaded with valid data'; + +$asset->update({locations => "53715\n97123"}); + +$asset->view(); +$weather_data = $templateVars->{'ourLocations.loop'}; +is $weather_data->[1]->{cityState}, 'Hillsboro, OR (97123)', 'weather data fetch successful, new location'; +is $weather_data->[0]->{cityState}, 'Madison, WI (53715)', '...cached weather data'; +is $weather_data->[0]->{last_fetch}, $last_fetch, '53715 lookup was cached'; + +#vim:ft=perl diff --git a/t/Form/Group.t b/t/Form/Group.t new file mode 100644 index 000000000..f527edd4e --- /dev/null +++ b/t/Form/Group.t @@ -0,0 +1,178 @@ + +# WebGUI is Copyright 2001-2009 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 lib "$FindBin::Bin/../lib"; + +use WebGUI::Test; +use WebGUI::Form; +use WebGUI::Form::Group; +use WebGUI::Group; +use WebGUI::Session; + +#The goal of this test is to verify that various www_ methods for the Group plugin work. + +use Test::More; +use Test::Deep; +use JSON (); +use Data::Dumper; + +my $session = WebGUI::Test->session; + +# put your tests here + +plan tests => 11; + +my $groupAdminUser = WebGUI::User->new($session, 'new'); +my $groupAdminGroup = WebGUI::Group->new($session, 'new'); +$groupAdminGroup->addUsers([$groupAdminUser->userId]); +$session->setting->set('groupIdAdminGroup', $groupAdminGroup->getId); +WebGUI::Test->addToCleanup($groupAdminUser, $groupAdminGroup); + +my $json; + +$json = WebGUI::Form::Group::www_searchGroups($session); +is $json, '{"results":[]}', 'www_searchGroups: unprivileged user is not allowed to use this'; + +$session->user({user => $groupAdminUser}); +$json = WebGUI::Form::Group::www_searchGroups($session); +is $json, '{"results":[]}', '... without a body parameter, returns valid empty JSON array'; + +$session->request->setup_body({query => 'Registered Users'}); +$json = WebGUI::Form::Group::www_searchGroups($session); +my $group_data = JSON::from_json($json); +cmp_deeply( + $group_data, + { + results => [ + { + groupId => 2, + groupName => 'Registered Users', + }, + ], + }, + '... with an exact match, get one result back' +); + +{ + my @groups = map { my $group = WebGUI::Group->new($session, 'new'); $group->name('Test Group '. $_); $group; } 1..20; + my $cleanup = WebGUI::Test->cleanupGuard(@groups); + $session->request->setup_body({query => 'Test Group'}); + $json = WebGUI::Form::Group::www_searchGroups($session); + my $group_data = JSON::from_json($json); + is scalar @{ $group_data->{results} }, 15, '... results are limited to 15'; +} + +{ + my @groups = map { my $group = WebGUI::Group->new($session, 'new'); $group->name('Test Group '. $_); $group; } 1..5; + $groups[0]->showInForms(0); + my $cleanup = WebGUI::Test->cleanupGuard(@groups); + $session->request->setup_body({query => 'Test Group'}); + $json = WebGUI::Form::Group::www_searchGroups($session); + my $group_data = JSON::from_json($json); + my $has_group0 = grep { $_->{groupName} eq $groups[0]->name } @{ $group_data->{results} }; + ok ! $has_group0, '... group with showInForms set to false does not show up in the results'; +} + +my $test_group = WebGUI::Group->new($session, 'new'); +$test_group->name('Testing Group'); + +my $andy = WebGUI::User->new($session, 'new'); +$andy->username('andy'); + +my $red = WebGUI::User->new($session, 'new'); +$red->username('red'); + +WebGUI::Test->addToCleanup($test_group, $andy, $red); + +$session->request->setup_body({}); +$session->user({userId => 1}); +$json = WebGUI::Form::Group::www_groupMembers($session); +is $json, '{}', 'www_groupMembers: returns empty hashref for an unprivileged user'; + +$session->user({user => $groupAdminUser}); +$json = WebGUI::Form::Group::www_groupMembers($session); +is $json, '{}', '... returns empty hashref if no form variable'; + + #1234567890123456789012 +$session->request->setup_body({groupId => 'neverAWebGUIGroupId001'}); +$json = WebGUI::Form::Group::www_groupMembers($session); +is $json, '{}', 'www_groupMembers: returns empty hashref if no groupId does not exist in the db'; + +$session->request->setup_body({groupId => $test_group->getId}); +$json = WebGUI::Form::Group::www_groupMembers($session); +$group_data = JSON::from_json($json); +cmp_deeply( + $group_data, + { + groupName => 'Testing Group', + users => [ ], + groups => [ + { + groupId => '3', + groupName => 'Admins', + }, + ], + }, + '... with an exact match on an empty group, returns a hashref with arrayrefs' +); + +$test_group->addUsers([$andy->getId]); +$json = WebGUI::Form::Group::www_groupMembers($session); +$group_data = JSON::from_json($json); +cmp_deeply( + $group_data, + { + groupName => 'Testing Group', + users => [ + { + userId => $andy->userId, + username => 'andy', + } + ], + groups => [ + { + groupId => '3', + groupName => 'Admins', + }, + ], + }, + '... with an exact match on a populated group, return users and groups' +); + +$test_group->addGroups(['2']); +$json = WebGUI::Form::Group::www_groupMembers($session); +$group_data = JSON::from_json($json); +cmp_deeply( + $group_data, + { + groupName => 'Testing Group', + users => [ + { + userId => $andy->userId, + username => 'andy', + } + ], + groups => bag( + { + groupId => '2', + groupName => 'Registered Users', + }, + { + groupId => '3', + groupName => 'Admins', + }, + ), + }, + '... users not listed recursively, groups do not show up twice' +); + + diff --git a/t/Form/User.t b/t/Form/User.t new file mode 100644 index 000000000..f321ae722 --- /dev/null +++ b/t/Form/User.t @@ -0,0 +1,74 @@ + +# WebGUI is Copyright 2001-2009 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 lib "$FindBin::Bin/../lib"; + +use WebGUI::Test; +use WebGUI::Form; +use WebGUI::Form::User; +use WebGUI::Group; +use WebGUI::Session; + +#The goal of this test is to verify that various www_ methods for the Group plugin work. + +use Test::More; +use Test::Deep; +use JSON (); +use Data::Dumper; + +my $session = WebGUI::Test->session; + +# put your tests here + +plan tests => 4; + +my $userAdminUser = WebGUI::User->new($session, 'new'); +my $userAdminGroup = WebGUI::Group->new($session, 'new'); +$userAdminGroup->addUsers([$userAdminUser->userId]); +$session->setting->set('groupIdAdminUser', $userAdminGroup->getId); +WebGUI::Test->addToCleanup($userAdminUser, $userAdminGroup); + +my $json; + +$json = WebGUI::Form::User::www_searchUsers($session); +is $json, '{"results":[]}', 'www_searchUsers: unprivileged user is not allowed to use this'; + +$session->user({user => $userAdminUser}); +$json = WebGUI::Form::User::www_searchUsers($session); +is $json, '{"results":[]}', '... without a body parameter, returns valid empty JSON array'; + +$session->request->setup_body({query => 'Visitor'}); +$json = WebGUI::Form::User::www_searchUsers($session); +my $group_data = JSON::from_json($json); +cmp_deeply( + $group_data, + { + results => [ + { + userId => 1, + username => 'Visitor', + }, + ], + }, + '... with an exact match, get one result back' +); + +{ + my @users = map { my $user = WebGUI::User->new($session, 'new'); $user->username('Test User '. $_); $user; } 1..20; + my $cleanup = WebGUI::Test->cleanupGuard(@users); + $session->request->setup_body({query => 'Test User'}); + $json = WebGUI::Form::User::www_searchUsers($session); + my $group_data = JSON::from_json($json); + is scalar @{ $group_data->{results} }, 15, '... results are limited to 15'; +} + + diff --git a/t/Macro/LastUpdatedBy.t b/t/Macro/LastUpdatedBy.t new file mode 100644 index 000000000..60c0f2f15 --- /dev/null +++ b/t/Macro/LastUpdatedBy.t @@ -0,0 +1,71 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 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 lib "$FindBin::Bin/../lib"; + +use WebGUI::Test; +use WebGUI::Session; +use WebGUI::User; +use WebGUI::Macro::LastUpdatedBy; + +use Test::More; # increment this value for each test you create + +my $session = WebGUI::Test->session; +$session->user({userId => 1}); + +my $homeAsset = WebGUI::Asset->getDefault($session); + +my $numTests = 3; + +plan tests => $numTests; + +my $versionTag = WebGUI::VersionTag->getWorking($session); +addToCleanup($versionTag); + +my $output = WebGUI::Macro::LastUpdatedBy::process($session); +is($output, '', "Macro returns '' if no asset is defined"); + +##Make the homeAsset the default asset in the session. +$session->asset($homeAsset); + +$versionTag->set({name=>"Adding asset for LastUpdatedBy macro tests"}); + +my $revised_user = WebGUI::User->new($session, 'new'); +$revised_user->username('Andy'); +WebGUI::Test->addToCleanup($revised_user); +$session->user({user => $revised_user}); + +my $root = WebGUI::Asset->getRoot($session); +my %properties_A = ( + className => 'WebGUI::Asset', + title => 'Asset A', + url => 'asset-a', + ownerUserId => 3, + groupIdView => 7, + groupIdEdit => 3, + id => '1', + # '1234567890123456789012', +); + +my $assetA = $root->addChild(\%properties_A, $properties_A{id}); +$versionTag->commit; + +$session->asset($assetA); + +$output = WebGUI::Macro::LastUpdatedBy::process($session); +is($output, 'Andy', 'Default asset last revised by andy'); + +$revised_user->delete; +#$revised_user->uncache; + +$output = WebGUI::Macro::LastUpdatedBy::process($session); +is($output, 'Unknown', 'macro returns Unknown when the user object cannot be found'); diff --git a/www/extras/toolbar/bullet/add.gif b/www/extras/toolbar/bullet/add.gif new file mode 100644 index 0000000000000000000000000000000000000000..e5fb7df998b3915c58a5fa151fcee18413de35f3 GIT binary patch literal 2497 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 literal 0 HcmV?d00001 diff --git a/www/extras/wobject/Dashboard/dashboard.css b/www/extras/wobject/Dashboard/dashboard.css index 72c1493d0..40c13d415 100644 --- a/www/extras/wobject/Dashboard/dashboard.css +++ b/www/extras/wobject/Dashboard/dashboard.css @@ -1,258 +1,258 @@ -body -{ - margin: 0; - margin-top: 0; - padding: 0; -} - -table.dashboardColumn -{ - width: 100%; - background: none; - background-color:transparent; -} -.dashboardColumn td { - width:100%; -} - -#dashboardContainer -{ - font: 11px Lucida Grande, Verdana, Arial, Helvetica, sans serif; - background-color:white; -} - -#dashboardChooserContainer -{ - margin:0px; - padding:0px; - border:0px; - width:100%; -} - -#position1 { - width:200px; - max-width:200px; - width:expression("200px"); -} - -#position1 td { - margin-top:40px; - padding:5px; -} - -tbody.availableDashlet * div.content { - display: none; -} - -#availableDashlets * td { - width:200px; - max-width:200px; -} - -#availableDashlets * div.content { - width:200px; - max-width:200px; - height:0px; - display:none; - overflow-x:hidden; - overflow-y:hidden; -} - -#columnsContainerDiv -{ - margin:6px; -} - -td -{ - vertical-align: top; -} - -h1 -{ - font: 12px Lucida Grande, Verdana, Arial, Helvetica, sans serif; - font-weight:bold; - margin:0px; - margin-bottom:3px; -} - -h2 -{ - font: 19px Verdana; - margin-bottom:0px; - padding-bottom:0px; -} - -ul -{ - margin-top:2px; - left:-10px; - position:relative; -} - -div.weatherTitle -{ - font-weight:bold; - color:white; -} - -div.content -{ - overflow-x:hidden; - border-bottom:solid orange 1px; - border-top:solid orange 1px; - border-left:solid orange 1px; - border-right:solid orange 1px; - margin-bottom:10px; - padding:5px; - padding-top:10px; - background: #fff url('content_bg.gif') no-repeat top right; -} - -div.content a:link, div.content a:visited -{ - color:#F48117; -} - -table.tableSearch -{ - background-color:#F2F2F2; - border:solid #CCC 1px; - width:100%; -} - -table.tableSearch td select -{ - margin:0px; - padding:0px; -} - -div.dragTitle -{ - overflow-x:visible; - background: url('dragable_bg.gif'); - width:100%; - z-index:998; - border:solid black 0px; - height:22px; - top:0px; - left:0px; - right:30px; - position:absolute; -} - -span.headerTitle -{ - height:22px; - clip:rect(auto,auto,auto,auto); - overflow:hidden; - display:block; - float:left; - line-height:18px; - z-index:999; - margin-right:10px; - padding-left:9px; - padding-top:2px; -} - -span.options -{ - display:block; - position:absolute; - top:0px; - right:-1px; - float:right; - z-index:1000; - padding-right:35px; - background: transparent url('dragtitle_bg.gif') no-repeat top right; -} -span.options img { - opacity:0; - filter:alpha(opacity=0); -} -span.options:hover img { - opacity:100; -} -span.optionsHoverIE img { - filter:alpha(opacity=100); -} -div#availableDashlets * span.options:hover { - opacity:0; - filter:alpha(opacity=0); -} -div#availableDashlets * span.options { - display:none; - opacity:0; - top:500px; - filter:alpha(opacity=0); -} -div#availableDashlets * div.dragTrigger { - background: none; - float:none; - margin-bottom:0px; -/* height:auto;*/ -} -div#availableDashlets * div.dragTitle { - background:none; -/* height:auto;*/ - overflow:hidden; - float:none; - background-color: #eeeeee; - border: solid #bbbbbb 1px; - color: black; -} -div#availableDashlets * span.headerTitle { - display:block; - width:100%; - line-height:auto; -/* height:22px;*/ - background: none; -} - -#hideNewContentButton,#showNewContentButton { -/* position:absolute;*/ - color: #465D94; - font-size: 1.2em; - font-weight: 600; - top: 20px; - cursor: pointer; cursor: hand; - left: 20px; - width:200px; -} -#availableBox { - display:none; -/* width:200px; - max-width:200px; - width:expression("200px");*/ -} -#availableBox2 { - padding-top:40px; - border:solid navy 2px; - height: 600px; -/* width:200px; - max-width:200px; - width:expression("200px");*/ -} - -#availableDashlets { -/* width:200px; - max-width:200px; - width:expression("200px");*/ -} - -.availableDashlet -{ -/* width:200px; - max-width:200px; - width:expression("200px");*/ -} - -#availableDashlets tbody { - height: 0px; -} -#leftBox { - width:210px; -} -#rightBox { - width:100%; +body +{ + margin: 0; + margin-top: 0; + padding: 0; +} + +table.dashboardColumn +{ + width: 100%; + background: none; + background-color:transparent; +} +.dashboardColumn td { + width:100%; +} + +#dashboardContainer +{ + font: 11px Lucida Grande, Verdana, Arial, Helvetica, sans serif; + background-color:white; +} + +#dashboardChooserContainer +{ + margin:0px; + padding:0px; + border:0px; + width:100%; +} + +#position1 { + width:200px; + max-width:200px; + width:expression("200px"); +} + +#position1 td { + margin-top:40px; + padding:5px; +} + +tbody.availableDashlet * div.content { + display: none; +} + +#availableDashlets * td { + width:200px; + max-width:200px; +} + +#availableDashlets * div.content { + width:200px; + max-width:200px; + height:0px; + display:none; + overflow-x:hidden; + overflow-y:hidden; +} + +#columnsContainerDiv +{ + margin:6px; +} + +td +{ + vertical-align: top; +} + +h1 +{ + font: 12px Lucida Grande, Verdana, Arial, Helvetica, sans serif; + font-weight:bold; + margin:0px; + margin-bottom:3px; +} + +h2 +{ + font: 19px Verdana; + margin-bottom:0px; + padding-bottom:0px; +} + +ul +{ + margin-top:2px; + left:-10px; + position:relative; +} + +div.weatherTitle +{ + font-weight:bold; + color:white; +} + +div.content +{ + overflow-x:hidden; + border-bottom:solid orange 1px; + border-top:solid orange 1px; + border-left:solid orange 1px; + border-right:solid orange 1px; + margin-bottom:10px; + padding:5px; + padding-top:10px; + background: #fff url('content_bg.gif') no-repeat top right; +} + +div.content a:link, div.content a:visited +{ + color:#F48117; +} + +table.tableSearch +{ + background-color:#F2F2F2; + border:solid #CCC 1px; + width:100%; +} + +table.tableSearch td select +{ + margin:0px; + padding:0px; +} + +div.dragTitle +{ + overflow-x:visible; + background: url('dragable_bg.gif'); + width:100%; + z-index:998; + border:solid black 0px; + height:22px; + top:0px; + left:0px; + right:30px; + position:absolute; +} + +span.headerTitle +{ + height:22px; + clip:rect(auto,auto,auto,auto); + overflow:hidden; + display:block; + float:left; + line-height:18px; + z-index:999; + margin-right:10px; + padding-left:9px; + padding-top:2px; +} + +span.options +{ + display:block; + position:absolute; + top:0px; + right:-1px; + float:right; + z-index:1000; + padding-right:35px; + background: transparent url('dragtitle_bg.gif') no-repeat top right; +} +span.options img { + opacity:0; + filter:alpha(opacity=0); +} +span.options:hover img { + opacity:100; +} +span.optionsHoverIE img { + filter:alpha(opacity=100); +} +div#availableDashlets * span.options:hover { + opacity:0; + filter:alpha(opacity=0); +} +div#availableDashlets * span.options { + display:none; + opacity:0; + top:500px; + filter:alpha(opacity=0); +} +div#availableDashlets * div.dragTrigger { + background: none; + float:none; + margin-bottom:0px; +/* height:auto;*/ +} +div#availableDashlets * div.dragTitle { + background:none; +/* height:auto;*/ + overflow:hidden; + float:none; + background-color: #eeeeee; + border: solid #bbbbbb 1px; + color: black; +} +div#availableDashlets * span.headerTitle { + display:block; + width:100%; + line-height:auto; +/* height:22px;*/ + background: none; +} + +#hideNewContentButton,#showNewContentButton { +/* position:absolute;*/ + color: #465D94; + font-size: 1.2em; + font-weight: 600; + top: 20px; + cursor: pointer; cursor: hand; + left: 20px; + width:200px; +} +#availableBox { + display:none; +/* width:200px; + max-width:200px; + width:expression("200px");*/ +} +#availableBox2 { + padding-top:40px; + border:solid navy 2px; + height: 600px; +/* width:200px; + max-width:200px; + width:expression("200px");*/ +} + +#availableDashlets { +/* width:200px; + max-width:200px; + width:expression("200px");*/ +} + +.availableDashlet +{ +/* width:200px; + max-width:200px; + width:expression("200px");*/ +} + +#availableDashlets tbody { + height: 0px; +} +#leftBox { + width:210px; +} +#rightBox { + width:100%; } \ No newline at end of file diff --git a/www/extras/wobject/Dashboard/draggable.js b/www/extras/wobject/Dashboard/draggable.js index 84aec708d..35b7066b4 100644 --- a/www/extras/wobject/Dashboard/draggable.js +++ b/www/extras/wobject/Dashboard/draggable.js @@ -20,7 +20,6 @@ var endTD = null; var topelement=dom? "HTML" : "BODY" var currentDiv = null; var clipboard = null; -var contra = ""; var pageHeight=0; var pageWidth=0; var scrollJump=50; @@ -28,36 +27,6 @@ var blankCount=1; // var getScript = s/