diff --git a/docs/upgrades/packages-7.8.5/root_import_ems_ems-badge-listing-default.wgpkg b/docs/upgrades/packages-7.8.5/root_import_ems_ems-badge-listing-default.wgpkg new file mode 100644 index 000000000..64aa307a4 Binary files /dev/null and b/docs/upgrades/packages-7.8.5/root_import_ems_ems-badge-listing-default.wgpkg differ diff --git a/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission-main.wgpkg b/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission-main.wgpkg new file mode 100644 index 000000000..715fadb73 Binary files /dev/null and b/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission-main.wgpkg differ diff --git a/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission-queue.wgpkg b/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission-queue.wgpkg new file mode 100644 index 000000000..717ff6cd2 Binary files /dev/null and b/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission-queue.wgpkg differ diff --git a/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission.wgpkg b/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission.wgpkg new file mode 100644 index 000000000..33b66b03f Binary files /dev/null and b/docs/upgrades/packages-7.8.5/root_import_ems_ems-event-submission.wgpkg differ diff --git a/docs/upgrades/upgrade_7.8.4-7.8.5.pl b/docs/upgrades/upgrade_7.8.4-7.8.5.pl index 1a027df9e..c1f42cd95 100644 --- a/docs/upgrades/upgrade_7.8.4-7.8.5.pl +++ b/docs/upgrades/upgrade_7.8.4-7.8.5.pl @@ -30,6 +30,9 @@ my $quiet; # this line required my $session = start(); # this line required fixPackageFlagOnOlder( $session ); +addEMSSubmissionTables($session); +configEMSActivities($session); + # upgrade functions go here @@ -45,6 +48,119 @@ finish($session); # this line required # print "DONE!\n" unless $quiet; #} +#---------------------------------------------------------------------------- +# Describe what our function does +sub configEMSActivities { + my $session = shift; + print "\tConfigure EMS Activities... " unless $quiet; + my $config = $session->config; + $config->addToArray('workflowActivities/None', 'WebGUI::Workflow::Activity::CleanupEMSSubmissions'); + $config->addToArray('workflowActivities/None', 'WebGUI::Workflow::Activity::ProcessEMSApprovals'); + my $workflow = WebGUI::Workflow->new($session, 'pbworkflow000000000001'); # Daily + BREAK: { foreach my $activity (@{ $workflow->getActivities }) { + last BREAK if $activity->getName() eq 'WebGUI::Workflow::Activity::CleanupEMSSubmissions'; + } + my $activity = $workflow->addActivity('WebGUI::Workflow::Activity::CleanupEMSSubmissions'); + $activity->set('title', 'Purge Denied EMS Submissions'); + $activity->set('description', 'Purges EMS Submissions that were denied and are aged according to parameters.'); + } # end of BREAK block + $workflow = WebGUI::Workflow->new($session, 'pbworkflow000000000004'); # Hourly + BREAK: { foreach my $activity (@{ $workflow->getActivities }) { + last BREAK if $activity->getName() eq 'WebGUI::Workflow::Activity::ProcessEMSApprovals'; + } + my $activity = $workflow->addActivity('WebGUI::Workflow::Activity::ProcessEMSApprovals'); + $activity->set('title', 'Process Approves EMS Submissions'); + $activity->set('description', 'Create EMS Ticket Assets for approved submissions.'); + } # end of BREAK block + print "DONE!\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +# make database changes relevant to EMS Submission system +sub addEMSSubmissionTables { + my $session = shift; + print "\tCreate EMS Submission Tables... " unless $quiet; + my $db = $session->db; + + $db->write(<write(<write(<write(<write(<write(<write(<write(<write(<div({ + contentCallback => sub { $self->getDivContents(shift); } + }); + +=head3 additionalTerms + +The following additional parameters have been added via this sub class. + +=head4 contentCallback + +A code enclosure which returns the html text to insert into the div element. The divId is passed as parameter 0 when it is called. This function MUST return good html text, it is NOT processed here at all. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift || []; + push(@{$definition}, { + contentCallback=>{ + defaultValue=> sub { return '' }, + }, + }); + return $class->SUPER::definition($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 getName ( session ) + +Returns the name of the form control. + +=cut + +sub getName { + my ($class, $session) = @_; + return WebGUI::International->new($session, "Form_Div")->get("topicName"); +} + + +#------------------------------------------------------------------- + +=head2 getValue ( [ value ] ) + +Does some special processing. + +=cut + +sub getValue { + my $self = shift; + return $self->get('contentCallback')->($self->get('id')); +} + +#------------------------------------------------------------------- + +=head2 toHtml ( ) + +Renders an input tag of type text. + +=cut + +sub toHtml { + my $self = shift; + return '
get("extras").'>' . $self->getValue . '
' ; +} + +1; +#vim:ft=perl diff --git a/lib/WebGUI/Workflow/Activity/CloseResolvedTickets.pm b/lib/WebGUI/Workflow/Activity/CloseResolvedTickets.pm new file mode 120000 index 000000000..dcdded4c6 --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/CloseResolvedTickets.pm @@ -0,0 +1 @@ +/data/helpdesk/lib/WebGUI/Workflow/Activity/CloseResolvedTickets.pm \ No newline at end of file diff --git a/lib/WebGUI/Workflow/Activity/ProcessEMSApprovals.pm b/lib/WebGUI/Workflow/Activity/ProcessEMSApprovals.pm new file mode 100644 index 000000000..a670a8ba7 --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/ProcessEMSApprovals.pm @@ -0,0 +1,123 @@ +package WebGUI::Workflow::Activity::ProcessEMSApprovals; + + +=head1 LEGAL + + ------------------------------------------------------------------- + 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 + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Workflow::Activity'; +use WebGUI::Asset; +use WebGUI::International; +use WebGUI::VersionTag; + +=head1 NAME + +Package WebGUI::Workflow::Activity::ProcessEMSApprovals + +=head1 DESCRIPTION + +Uses the settings in the help desk to determine whether the resolved tickets should be closed or not. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + + +#------------------------------------------------------------------- + +=head2 definition ( session, definition ) + +See WebGUI::Workflow::Activity::defintion() for details. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new( $session, "Asset_EMSSubmissionForm" ); + push(@{$definition}, { + name => $i18n->get("activity title approve submissions"), + properties => {} + }); + return $class->SUPER::definition($session,$definition); +} + + +#------------------------------------------------------------------- + +=head2 execute ( ) + +See WebGUI::Workflow::Activity::execute() for details. + +=cut + + +sub execute { + my $self = shift; + my $session = $self->session; + my $root = WebGUI::Asset->getRoot($session); + # keep track of how much time it's taking + my $start = time; + my $limit = 2_500; + my $timeLimit = 60; + + my $list = $root->getLineage( ['descendants'], { returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::EMSSubmissionForm'], + } ); + + for my $emsForm ( @$list ) { + my $whereClause = q{ submissionStatus='approved' }; + my $res = $emsForm->getLineage(['children'],{ returnObjects => 1, + joinClass => 'WebGUI::Asset::EMSSubmission', + includeOnlyClasses => ['WebGUI::Asset::EMSSubmission'], + whereClause => $whereClause, + } ); + for my $submission ( @$res ) { + my $properties = { className => 'WebGUI::Asset::Sku::EMSTicket' }; + for my $name ( qw{title description seatsAvailable price vendorId + synopsis location duration startDate sku relatedRibbons + relatedBadgeGroups eventMetaData shipsSeparately} ) { + $properties->{$name} = $submission->get($name); + } + $properties->{eventNumber} = $self->session->db->quickScalar( + "select max(eventNumber)+1 + from EMSTicket left join asset using (assetId) + where parentId=?",[$emsForm->ems->getId]) || 0; + my $newAsset = $emsForm->ems->addChild( $properties ); + if( $newAsset ) { + # TODO this should be addRevision + $submission->update({ ticketId => $newAsset->getId, submissionStatus => 'created' }); + WebGUI::VersionTag->autoCommitWorkingIfEnabled($session, { override => 1, allowComments => 0 }); + } else { + $submission->addComment($@) if $@; + $submission->update({ submissionStatus => 'failed' }); + } + $limit--; + last if ! $limit or time > $start + $timeLimit; + } + } + return $self->WAITING(1) if ! $limit or time > $start + $timeLimit; + return $self->COMPLETE; +} + +1; + + diff --git a/lib/WebGUI/i18n/English/Asset_HelpDesk.pm b/lib/WebGUI/i18n/English/Asset_HelpDesk.pm new file mode 120000 index 000000000..9790cf495 --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_HelpDesk.pm @@ -0,0 +1 @@ +/data/helpdesk/lib/WebGUI/i18n/English/Asset_HelpDesk.pm \ No newline at end of file diff --git a/lib/WebGUI/i18n/English/Asset_Ticket.pm b/lib/WebGUI/i18n/English/Asset_Ticket.pm new file mode 120000 index 000000000..ad15b6f7c --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_Ticket.pm @@ -0,0 +1 @@ +/data/helpdesk/lib/WebGUI/i18n/English/Asset_Ticket.pm \ No newline at end of file diff --git a/lib/WebGUI/i18n/English/Form_Div.pm b/lib/WebGUI/i18n/English/Form_Div.pm new file mode 100644 index 000000000..f956a4a01 --- /dev/null +++ b/lib/WebGUI/i18n/English/Form_Div.pm @@ -0,0 +1,15 @@ +package WebGUI::i18n::English::Form_Div; + +use strict; ##Required for all good Perl::Critic compliant code + +our $I18N = { + 'topicName' => { + message => q|Form Control Div Element|, + lastUpdated => 1131394070, #seconds from the epoch + context => q|Name of the form control that generates HTML Div elements| + }, + +}; + +1; +#vim:ft=perl diff --git a/t/Asset/EMSSubmissionForm.t b/t/Asset/EMSSubmissionForm.t new file mode 100644 index 000000000..d6a60e7c3 --- /dev/null +++ b/t/Asset/EMSSubmissionForm.t @@ -0,0 +1,516 @@ +# 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 +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# + +use FindBin; +use strict; +use lib "$FindBin::Bin/../lib"; +use Test::More; +use Test::Deep; +use Test::Warn; +use JSON; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Test::Activity; +use WebGUI::Group; +use WebGUI::User; +use WebGUI::Session; +use WebGUI::Asset::Wobject::EventManagementSystem; +use WebGUI::Asset::Sku::EMSBadge; +use WebGUI::Asset::Sku::EMSTicket; +use WebGUI::Asset::Sku::EMSRibbon; +use WebGUI::Asset::Sku::EMSToken; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 52; # Increment this number for each test you create + +(my $submitGroupA = WebGUI::Group->new($session,'new'))->name('groupA'); +(my $submitGroupB = WebGUI::Group->new($session,'new'))->name('groupB'); +(my $registrars = WebGUI::Group->new($session, 'new'))->name('registrars'); +(my $attendees = WebGUI::Group->new($session, 'new'))->name('attendees'); + +(my $registrar = WebGUI::User->new($session,'new'))->update({username=>'registrar'}); +(my $userA = WebGUI::User->new($session,'new'))->update({username=>'userA'}); +(my $userB = WebGUI::User->new($session,'new'))->update({username=>'userB'}); +(my $userC = WebGUI::User->new($session,'new'))->update({username=>'userC'}); + +$registrars->addUsers([$registrar->getId]); +$submitGroupA->addUsers([$userA->userId,$userC->userId]); +$submitGroupB->addUsers([$userB->userId,$userC->userId]); +$attendees->addUsers([$userA->getId, $userB->getId, $userC->getId]); + +WebGUI::Test->groupsToDelete($submitGroupA,$submitGroupB); +WebGUI::Test->groupsToDelete($registrars, $attendees); +WebGUI::Test->usersToDelete($userA,$userB,$userC,$registrar); + +sub loginAdmin { $session->user({userId => 3}); } +sub loginRgstr { $session->user({userId => $registrar->userId}); } +sub loginUserA { $session->user({userId => $userA->userId}); } +sub loginUserB { $session->user({userId => $userB->userId}); } +sub loginUserC { $session->user({userId => $userC->userId}); } +sub logout { $session->user({userId => 1}); } + +#---------------------------------------------------------------------------- +# put your tests here +eval { +my $use = use_ok( 'WebGUI::Asset::EMSSubmissionForm' ) + && use_ok( 'WebGUI::Asset::EMSSubmission' ) + && use_ok( 'WebGUI::Workflow::Activity::CleanupEMSSubmissions' ) + && use_ok( 'WebGUI::Workflow::Activity::ProcessEMSApprovals' ); + +SKIP: { skip 'package compile failed!', 1 unless $use; + +loginAdmin; + +# Create a version tag to work in +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"EventManagementSystem Test"}); +WebGUI::Test->tagsToRollback($versionTag); + +# Do our work in the import node +my $node = WebGUI::Asset->getImportNode($session); + +loginRgstr ; + +# Add an EMS asset +my $ems = $node->addChild({ + className =>'WebGUI::Asset::Wobject::EventManagementSystem', + title => 'Test EMS', + description => 'This is a test ems', + url => '/test-ems', + workflowIdCommit => 'pbworkflow000000000003', # Commit Content Immediately + registrationStaffGroupId => $registrars->getId, + groupIdView => $attendees->getId, + submittedLocationsList => join( "\n", my @submissionLocations = qw'loc1 loc2' ), +}); +# I scooped this out ot WG::Asset::Wobject::EventManagementSystem +# its not pretty, but there is no other way to add a meta field + $ems->setCollateral("EMSEventMetaField", "fieldId",{ + fieldId=> 'new', + label => 'mfRequiredUrl', + dataType => 'url', + visible => 1, + required => 1, + possibleValues => '', + defaultValues => '', + },1,1); + + $ems->setCollateral("EMSEventMetaField", "fieldId",{ + fieldId=> 'new', + label => 'mfDate', + dataType => 'date', + visible => 1, + required => 0, + possibleValues => '', + defaultValues => '', + },1,1); + +my $i18n = $ems->i18n; + +$versionTag->commit; +$versionTag = WebGUI::VersionTag->getWorking($session); +WebGUI::Test->tagsToRollback($versionTag); + +my $id1 = $ems->getNextSubmissionId; +my $id2 = $ems->getNextSubmissionId; +is( $id1 +1, $id2, ' test getNextSubmissionId' ); + +# quick test of addGroupToSubmitList +is($ems->get('eventSubmissionGroups'),'', 'event submission groups is blank'); +$ems->addGroupToSubmitList('joe'); +is($ems->get('eventSubmissionGroups'),'joe', 'event submission groups has one item'); +$ems->addGroupToSubmitList('frank'); +is($ems->get('eventSubmissionGroups'),'frank joe', 'event submission groups has two items'); +$ems->addGroupToSubmitList('joe'); +is($ems->get('eventSubmissionGroups'),'joe frank', 'event submission groups still has two items'); +$ems->update({eventSubmissionGroups => ''}); +is($ems->get('eventSubmissionGroups'),'', 'event submission groups is reset to blank'); + + +is_deeply($ems->getSubmissionLocations, \@submissionLocations, 'test getSubmissionLocations' ); +is_deeply( $ems->getSubmissionStatus, { + map { $_ => $i18n->get($_) } ( qw/pending feedback failed approved created denied/ ) +}, 'test getSubmissionStatus' ); + + +loginRgstr; + +is( $ems->hasSubmissionForms, 0, 'ems currently has no forms' ); + +#print 'press return to continue test' ; <>; + +my $formAdesc = { + _fieldList => [ qw/title description startDate/ ], + title => 1, + description => 1, + duration => 0, + startDate => 1, + seatsAvailable => 0, + location => 0, +}; + + +my $frmA = $ems->addSubmissionForm({ + title => 'test A -- long', + canSubmitGroupId => $submitGroupA->getId, + daysBeforeCleanup => 1, + formDescription => $formAdesc, +}); +isa_ok( $frmA, 'WebGUI::Asset::EMSSubmissionForm' ); +is( $ems->hasSubmissionForms, 1, 'ems now has forms' ); +is_deeply( $frmA->getFormDescription, $formAdesc, 'form description matches' ); +is( $frmA->ems->getId, $ems->getId, 'test ems access function in form' ); + +my $formBdesc = { + _fieldList => [ qw/title description duration mfRequiredUrl/ ], + title => 1, + description => 1, + duration => 1, + startDate => 0, + mfRequiredUrl => 1, + seatsAvailable => 0, + location => 0, +}; +my $frmB = $ems->addSubmissionForm({ + className => 'WebGUI::Asset::EMSSubmissionForm', + title => 'test B -- short', + daysBeforeCleanup => 1, + canSubmitGroupId => $submitGroupB->getId, + formDescription => $formBdesc, +}); +logout; + +ok( !$ems->canSubmit, 'Visitor cannot submit to this ems' ); +ok( !$frmA->canSubmit, 'Visitor cannot submit to form' ); + +loginUserA; + +ok( $ems->canSubmit, 'UserA can submit to this ems' ); +ok( $frmA->canSubmit, 'UserA can submit to formA' ); +ok( !$frmB->canSubmit, 'UserA cannot submit to formB' ); +#print 'press return to complete test' ; <>; +ok( !$ems->hasSubmissions, 'UserA has no submissions' ); + +my $submission = { + title => 'my favorite thing to talk about', + description => 'the description', + startDate => '1255150800', + }; +$session->request->setup_body($submission); +my $sub1 = $frmA->addSubmission; +WebGUI::Test->assetsToPurge( $sub1 ); +print join( "\n", @{$sub1->{errors}} ),"\n" if defined $sub1->{errors}; +my $isa1 = isa_ok( $sub1, 'WebGUI::Asset::EMSSubmission', "userA/formA valid submission succeeded" ); +ok( $ems->hasSubmissions, 'UserA has submissions on this ems' ); +is( $sub1->ems->getId, $ems->getId, 'test ems access function in submission' ); +loginUserB; + +ok( $ems->canSubmit, 'UserB can submit to this ems' ); +ok( !$frmA->canSubmit, 'UserB cannot submit to formA' ); +ok( $frmB->canSubmit, 'UserB can submit to formB' ); + +$submission = { + title => 'why i like to be important', + description => 'the description', + mfRequiredUrl => 'http://google.com', + }; +$session->request->setup_body($submission); +my $sub2 = $frmB->addSubmission; +WebGUI::Test->assetsToPurge( $sub2 ); +my $isa2 = isa_ok( $sub2, 'WebGUI::Asset::EMSSubmission', "userB/FormB valid submission succeeded" ); + +loginUserC; + +ok( $ems->canSubmit, 'UserC can submit to this ems' ); +ok( $frmA->canSubmit, 'UserC can submit to formA' ); +ok( $frmB->canSubmit, 'UserC can submit to formB' ); + +loginUserA; +cmp_deeply( from_json($ems->www_getAllSubmissions), { + sort => undef, + startIndex => 1, + records => [ + { + lastReplyDate => '', + submissionId => '3', + creationDate => ignore(), + createdBy => 'userA', + url => '/test-ems?func=viewSubmissionQueue#3', + submissionStatus => $i18n->get('pending'), + title => 'my favorite thing to talk about', + lastReplyBy => '' + } + ], + totalRecords => '1', + recordsReturned => 25, + dir => 'DESC', +}, 'test getAllSubmissions for UserA' ); + +$session->request->setup_body({submissionId => 3}); +cmp_deeply( from_json($ems->www_getSubmissionById), { + title => 3, + id => 3, + text => ignore(), +}, 'test getSubmissionById'); + +loginUserC; +cmp_deeply( from_json($ems->www_getAllSubmissions), { + sort => undef, + startIndex => 1, + records => [ + ], + totalRecords => '0', + recordsReturned => 25, + dir => 'DESC', +}, 'test getAllSubmissions for UserC' ); + +loginRgstr; +$session->request->setup_body({ orderByColumn => 'submissionId' }); +cmp_deeply( from_json($ems->www_getAllSubmissions), { + sort => 'submissionId', + startIndex => 1, + records => [ + { + lastReplyDate => '', + submissionId => '4', + creationDate => ignore(), + createdBy => 'userB', + url => '/test-ems?func=viewSubmissionQueue#4', + submissionStatus => $i18n->get('pending'), + title => 'why i like to be important', + lastReplyBy => '' + }, + { + lastReplyDate => '', + submissionId => '3', + creationDate => ignore(), + createdBy => 'userA', + url => '/test-ems?func=viewSubmissionQueue#3', + submissionStatus => $i18n->get('pending'), + title => 'my favorite thing to talk about', + lastReplyBy => '' + }, + ], + totalRecords => '2', + recordsReturned => 25, + dir => 'DESC', +}, 'test getAllSubmissions for Registrar' ); + +SKIP: { skip 'create submission failed', 8 unless $isa1 && $isa2; + +loginUserA; + +$sub1->addComment( 'this is a test comment' ); +cmp_deeply($sub1->get('comments')->[0],{ + id => re( qr/.+/ ), + alias => 'userA', + userId => $userA->userId, + comment => 'this is a test comment', + rating => 0, + date => re( qr/\d{10}/ ), + ip => undef, +}, "successfully added comment" ); + +$sub1->update({ + title => 'the new title' +}); + +is( $sub1->get('title'),'the new title','successfully changed the title'); + +loginRgstr; + +$sub1->update({ submissionStatus => 'approved' }); +is($sub1->get('submissionStatus'),'approved','set status to approved'); + +$sub2->update({ submissionStatus => 'denied' }); +is($sub2->get('submissionStatus'),'denied','set status to denied'); + +SKIP: { skip "workflow activities not coded yet", 10 if 0; + +# create the workflows/activities for processing +my $approveSubmissions = WebGUI::Test::Activity->create( $session, + "WebGUI::Workflow::Activity::ProcessEMSApprovals" +); +my $cleanupSubmissions = WebGUI::Test::Activity->create( $session, + "WebGUI::Workflow::Activity::CleanupEMSSubmissions" +); + +is($approveSubmissions->run, 'complete', 'approval complete'); +is($approveSubmissions->run, 'done', 'approval done'); + +$sub1 = $sub1->cloneFromDb; +is( $sub1->get('submissionStatus'),'created','approval successfull'); + +my $ticket = WebGUI::Asset->newByDynamicClass($session, $sub1->get('ticketId')); +isa_ok( $ticket, 'WebGUI::Asset::Sku::EMSTicket', 'approval created a ticket'); +WebGUI::Test->assetsToPurge( $ticket ) if $ticket ; + +my $newDate = time - ( 60 * 60 * 24 * ( $sub2->getParent->get('daysBeforeCleanup') + 1 ) ), +$sub2->update({ + submissionStatus => 'denied', + # lastModified => $newDate, -- update overrides this... +}); +my $sub2Id = $sub2->getId; +$session->db->write('update assetData set lastModified = ' . $newDate . ' where assetId = "' . $sub2Id . '"' ); + +$cleanupSubmissions->rerun; +is($cleanupSubmissions->run, 'complete', 'cleanup complete'); +is($cleanupSubmissions->run, 'done', 'cleanup done'); + +$sub2 = WebGUI::Asset->newByDynamicClass($session, $sub2Id); +is( $sub2, undef, 'submission deleted'); + +} # end of workflow skip + +} # end of create submission skip + +$versionTag->commit; + +SKIP: { skip 'requires HTML::Form', 2 unless use_ok 'HTML::Form'; +# this is not the greatest testm but it does run through the basic create submissionForm code. +loginRgstr; + +my %settings = ( + assetId => 'new', + fieldNames => 'title description startDate duration seatsAvailable location nzymEeuHPQIsgXY0hZxDxA xlvMNwFi1FWwP0PrUAnxSQ', + title => 'Untitled', + menuTitle => 'Untitled', + url => '', + canSubmitGroupId => 2, + daysBeforeCleanup => 7, + deleteCreatedItems => 0, + submissionDeadline => '1991-06-21', + pastDeadlineMessage => 'The deadline for this submission is past, no more submissions will be taken at this time.', + title_yesNo => 1, + description_yesNo => 1, + startDate_yesNo => 1, + duration_yesNo => 1, + seatsAvailable_yesNo => 1, + location_yesNo => 1, + nzymEeuHPQIsgXY0hZxDxA_yesNo => 1, + xlvMNwFi1FWwP0PrUAnxSQ_yesNo => 1, +); + +my $expected = { + 'submissionDeadline' => '1991-06-21', + 'menuTitle' => 'Untitled', + 'pastDeadlineMessage' => 'The deadline for this submission is past, no more submissions will be taken at this time.', + 'formDescription' => { + 'location' => '1', + 'nzymEeuHPQIsgXY0hZxDxA' => 'xlvMNwFi1FWwP0PrUAnxSQ', + 'seatsAvailable' => '1', + 'duration' => '1', + 'title' => '1', + 'startDate' => '1', + 'description' => '1', + 'submissionStatus' => '0', + '_fieldList' => [ + 'title', + 'description', + 'startDate', + 'duration', + 'seatsAvailable', + 'location', + 'nzymEeuHPQIsgXY0hZxDxA' + ] + }, + 'description' => undef, + '_isValid' => 1, + 'deleteCreatedItems' => undef, + 'canSubmitGroupId' => '2', + 'assetId' => 'new', + 'url' => undef, + 'daysBeforeCleanup' => '7', + 'title' => 'Untitled' + } ; + +my $htmlText = $ems->www_addSubmissionForm; +my $form = HTML::Form->parse($htmlText,'http://localhost/'); +for my $input ( $form->inputs ) { + $input->value($settings{$input->name})if exists $settings{$input->name}; +} +$session->request->setup_body( { $form->form } ); +my $result = WebGUI::Asset::EMSSubmissionForm->processForm($ems); +cmp_deeply( $result, $expected , 'test process form' ); +$expected = { + 'errors' => [ + { + 'text' => ignore(), + } + ], + 'submissionDeadline' => undef, + 'menuTitle' => undef, + 'pastDeadlineMessage' => undef, + 'formDescription' => { + '_fieldList' => [], + 'submissionStatus' => 0, + }, + 'description' => undef, + '_isValid' => 0, + 'deleteCreatedItems' => undef, + 'canSubmitGroupId' => undef, + 'assetId' => undef, + 'url' => undef, + 'daysBeforeCleanup' => undef, + 'title' => undef, + }; +$session->request->setup_body( { } ); +$result = WebGUI::Asset::EMSSubmissionForm->processForm($ems); +cmp_deeply( $result, $expected , 'test process form' ); +} # end of skip HTML::Form + +# these run code to see that it runs, but do not check for correctness + +warnings_are { + +$ems->www_viewSubmissionQueue; +$ems->www_addSubmission; +$ems->www_addSubmissionForm; +$ems->www_editSubmissionForm; +$ems->www_editSubmissionFormSave; +$frmA->www_editSubmissionForm; +$frmA->www_addSubmission; +$frmA->www_editSubmission; +$frmA->www_editSubmissionSave; +$frmA->processForm; +$sub1->drawLocationField; +$sub1->drawRelatedBadgeGroupsField; +$sub1->drawRelatedRibbonsField; +$sub1->drawStatusField; +$sub1->www_editSubmission; +$sub1->www_editSubmissionSave; +$sub1->processForm; +# test comments +$sub1->getFormattedComments; + +} [], 'no warnings from calling a bunch of functions'; + +} # end of use packages skip +}; # end of eval +print $@ if $@; + +#done_testing(); +#print 'press return to complete test' ; <>; +#---------------------------------------------------------------------------- +# Cleanup +END { + + +} +#vim:ft=perl diff --git a/t/Form/Div.t b/t/Form/Div.t new file mode 100644 index 000000000..a31f2249b --- /dev/null +++ b/t/Form/Div.t @@ -0,0 +1,66 @@ +#------------------------------------------------------------------- +# 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::Div; +use WebGUI::Session; +use HTML::Form; +use WebGUI::Form_Checking; + +#The goal of this test is to verify that Div form elements work + +use Test::More; + +my $session = WebGUI::Test->session; + +# put your tests here + +plan tests => 3; + +my ($header, $footer) = (WebGUI::Form::formHeader($session), WebGUI::Form::formFooter($session)); + +my $textTag = 'hi this is a piece of text that will get placed in a div'; + +sub myContent { + my $id = shift; + return 'ID = ' . $id . $textTag ; +} + +my $html = join "\n", + $header, + WebGUI::Form::Div->new($session, { + name => 'TestDiv', + contentCallback => sub { myContent(shift); }, + })->toHtml, + $footer; + +my @forms = HTML::Form->parse($html, 'http://www.webgui.org'); + +##Test Form Generation + +is(scalar @forms, 1, '1 form was parsed'); + +my @inputs = $forms[0]->inputs; +is(scalar @inputs, 1, 'The form has 1 inputs'); + +# this is not an input control, we just want to see that we are getting +# the desired output + +ok( $html =~ /$textTag/, 'the tag is in the html, so the div was succeccfully implanted' ); + +# TODO write code to parse the html sing xml or some such + +__END__ + diff --git a/www/extras/wobject/EMS/close12_1.gif b/www/extras/wobject/EMS/close12_1.gif new file mode 100644 index 000000000..e2f67d72e Binary files /dev/null and b/www/extras/wobject/EMS/close12_1.gif differ diff --git a/www/extras/wobject/EMS/indicator.gif b/www/extras/wobject/EMS/indicator.gif new file mode 100644 index 000000000..085ccaeca Binary files /dev/null and b/www/extras/wobject/EMS/indicator.gif differ diff --git a/www/extras/wobject/EMS/submission.css b/www/extras/wobject/EMS/submission.css new file mode 100644 index 000000000..8fa15bbd3 --- /dev/null +++ b/www/extras/wobject/EMS/submission.css @@ -0,0 +1,114 @@ +legend { + font-weight: bold; +} + +form { + margin: 0; + padding: 0 +} +.submission_title { + background: #d8d8d8 url(../../yui/build/assets/skins/sam/sprite.png) repeat-x; + border: solid #a3a3a3; + border-width: 0 1px 0; + font-family: arial; + font-weight: bold; + font-size: 14pt; +} +.submission_body { + background: #FFFFFF; + border: 1px solid #000000; +} +.submission_desc { + background: #d8d8d8; +} +.userArea { + font-family: arial; + font-size: 9pt; + color: black; + background-color: white; + overflow: auto; + height: 310px; +} +.hd_searchBtn { + background-color:silver; + color:white; + border:solid gray 1px; + text-decoration:none; + font-weight:bold; + text-align:center; + cursor:pointer; + font-size:9pt; +} +.inputBox { + border:solid gray 1px; + font-size:9pt; +} +.grayArea { + background-color:#F2F2F2; + border:solid #E8E8E8 1px; + padding:3px; + -moz-box-sizing:border-box; +} +#solutionDialog { + background-color: #FFFFFF; +} +.whiteArea { + font-family: arial; + font-size: 9pt; + color: black; +} +.solutionArea { + font-family: arial; + font-size: 9pt; + color: black; + background-color:#FFFFFF; + overflow: auto; + height: 160px; +} +#userList_div { + overflow: auto; + width: 100%; + height: auto; + font-family: arial; + font-size: 10pt; + border: solid #CACACA 1px; +} +* html #userList_div { + overflow: hidden; + overflow-y: auto; +} +#userList_div img { + padding-right: 10px; + padding-left: 10px; +} +#userList_div td { + background-color: #F2F2F2; + border-top: solid #F9F9F9 1px; + border-bottom: solid #E0E0E0 1px; + text-align: left; + vertical-align: top; +} +#userList_div tr.odd td { + background-color: #EAEAEA; +} +.submission_private { + background-color:#AA0002; + border:solid #E8E8E8 1px; + color:white; + font-size: 9pt; + font-weight: bold; + text-align:center; + -moz-box-sizing:border-box; +} +.submission_public { + background-color:#00FF00; + border:solid #E8E8E8 1px; + color:white; + font-size: 0pt; + font-weight: bold; + text-align:center; + -moz-box-sizing:border-box; +} +.dyn_form_field { + width: 120px; +} diff --git a/www/extras/wobject/EMS/submission.js b/www/extras/wobject/EMS/submission.js new file mode 100644 index 000000000..40423324b --- /dev/null +++ b/www/extras/wobject/EMS/submission.js @@ -0,0 +1,408 @@ + +/*** The WebGUI EMS Submission system + * Requires: YAHOO, Dom, Event, DataSource, DataTable, Paginator, Container + * + */ + +var DataSource = YAHOO.util.DataSource, + DataTable = YAHOO.widget.DataTable, + Paginator = YAHOO.widget.Paginator; + +if ( typeof WebGUI == "undefined" ) { + WebGUI = {}; +} + +/*** WebGUI EMS Object + * + * This object renders the WebGUI EMS Submission datatable + * + * @method WebGUI.EMS.constructor + * @param configs {Object} object containing configuration necessary for creating the datatable. +TODO -- fix this to match what EMS really needs + * datasource {String} Required URL that returns the JSON data structure of data to be displayed. + * container {String} Required id of the HTML Element in which to render both the datatable and the pagination + * dtContainer {String} Required id of the HTML Element in which to render the datatable + * view {String} Required String which is passed to the ticket to properly return uses to the right view [all,my,search]. + * fields {ArrayRef} Required Array Reference of Objects used by the DataSource to configure and store data to be used by the data table + * columns {ArrayRef} Required Array Reference of Objects which define the columns for the datatable to render + * p_containers {ArrayRef} Required Array Reference containing the ids of the HTML Elements in which to render pagination. + * defaultSort {Object} Optional Custom object which defines which column and direction the paginator should sort by + * initRequestString {String} Optional Parameters to append to the end of the url when initializing the datatable + */ + + +WebGUI.EMS = function (configs) { + // Initialize configs + this._configs = {}; + if(configs) { + this._configs = configs; + WebGUI.EMS.url = configs.url; + WebGUI.EMS.tabContent = configs.tabContent; + } + WebGUI.EMS.items = new Object(); + + if(!this._configs.initRequestString) { + this._configs.initRequestString = ';startIndex=0'; + } + + /////////////////////////////////////////////////////////////// + // Internationalization + // this comes first because it is used in other areas... + /////////////////////////////////////////////////////////////// + WebGUI.EMS.i18n = new WebGUI.i18n( { + namespaces : { + 'Asset_EMSSubmission' : [ + '' + ], + 'Asset_EventManagementSystem' : [ + 'close tab', + '' + ] + } +// onpreload : { +// fn : this.initialize, +// obj : this, +// override : true, +// } + } ); + + /////////////////////////////////////////////////////////////// + // Protected Static Methods + /////////////////////////////////////////////////////////////// + + //*********************************************************************************** + // This Method updates the window.location.hash when the user changes tabs + WebGUI.EMS.changeTab = function ( e ) { + alert('tab changed'); + var index = WebGUI.EMS.tabs.getTabIndex( e.newValue ); + if( index == 0 ) { + window.location.hash = ''; + } else { + window.location.hash = WebGUI.EMS.Tabs[index].id; + } + }; + + //*********************************************************************************** + // This method closes the active tab + // + // Parameters: ( integer ) -- if a ticket id is passed in then remove the tab for that ticket + // ( e, object ) -- cancel the event and close the tab associated with the object + // ( ) -- get the current tab from the tabview object and close it + // + WebGUI.EMS.closeTab = function ( e, myTab ) { + var index; + if( typeof(e) == "string" || typeof(e) == "number" ) { + index = e; + myTab = WebGUI.EMS.items[index].tab; + } else { + if( typeof(e) != "undefined" ) { + YAHOO.util.Event.preventDefault(e); + } + if( typeof(myTab) == "undefined" ) { + myTab = WebGUI.EMS.tabs.get('activeTab'); + } + index = WebGUI.EMS.tabs.getTabIndex(myTab); + } + delete WebGUI.EMS.items[index]; + WebGUI.EMS.tabs.removeTab(myTab); + if( WebGUI.EMS.lastTab ) { + WebGUI.EMS.tabs.set('activeTab',WebGUI.EMS.lastTab); + } + }; + + //*********************************************************************************** + // Custom function to handle pagination requests + WebGUI.EMS.handlePagination = function (state,dt) { + var sortedBy = dt.get('sortedBy'); + // Define the new state + var newState = { + startIndex: state.startIndex, + sorting: { + key: sortedBy.key, + dir: ((sortedBy.dir === DataTable.CLASS_ASC) ? "asc" : "desc") + }, + pagination : { // Pagination values + startIndex: state.startIndex, // Go to the proper page offset + rowsPerPage: state.rowsPerPage // Return the proper rows per page + } + }; + + // Create callback object for the request + var oCallback = { + success: dt.onDataReturnSetRows, + failure: dt.onDataReturnSetRows, + scope: dt, + argument: newState // Pass in new state as data payload for callback function to use + }; + + // Send the request + dt.getDataSource().sendRequest(WebGUI.EMS.buildQueryString(newState, dt), oCallback); + }; + + //*********************************************************************************** + //This method is out here so it can be overridden. The datatable uses this method to sort it's columns + WebGUI.EMS.newTab = function(url) { + // the 'loading' 'indicator' + if( typeof(WebGUI.EMS.loadingIndicator) == "undefined" ) { + WebGUI.EMS.loadingIndicator = new YAHOO.widget.Overlay( "loadingIndicator", { + fixedcenter : true, + visible : false + } ); + WebGUI.EMS.loadingIndicator.setBody( "Loading ..." + + "" + ); + WebGUI.EMS.loadingIndicator.render(document.body); + } + WebGUI.EMS.loadingIndicator.show(); + + // Create callback object for the request + var oCallback = { + success: function(o) { + var response = eval('(' + o.responseText + ')'); + var myTab; + if(response.hasError){ + var message = ""; + for(var i = 0; i < response.errors.length; i++) { + message += response.errors[i]; + } + alert(message); + return; + // currently only one tab exists, so instead of checking we just delete it and recreate + // this condition is going to have to search for the id in the list + } else { // if( typeof(WebGUI.EMS.items[response.title]) == "undefined" + // || WebGUI.EMS.items[response.title] == null ) { // } + // if there is a tab .. close it, + // at least until I can get the JS/HTML re-written to handle multiple tabs + // there should only be one + for( var item in WebGUI.EMS.items ) { WebGUI.EMS.closeTab(item) } + var myContent = document.createElement("div"); + myContent.innerHTML = response.text; + myTab = new YAHOO.widget.Tab({ + label: response.title + 'X', + contentEl: myContent + }); + WebGUI.EMS.tabs.addTab( myTab ); + var index = WebGUI.EMS.tabs.getTabIndex(myTab); + YAHOO.util.Event.on(myTab.getElementsByClassName('close')[0], 'click', WebGUI.EMS.closeTab , myTab); + WebGUI.EMS.items[index] = new Object(); + WebGUI.EMS.items[index].tab = myTab; + WebGUI.EMS.items[index].id = response.id; + WebGUI.EMS.items[index].title = response.title; + //} else { + //myTab = WebGUI.EMS.items[response.title].tab; + //myTab.set('content', response.text); + } + // make sure the script on the ticket has run + // if( typeof( WebGUI.ticketJScriptRun ) == "undefined" ) { + // eval( document.getElementById("ticketJScript").innerHTML ); + // } + // delete WebGUI.ticketJScriptRun; + WebGUI.EMS.loadingIndicator.hide(); + WebGUI.EMS.lastTab = WebGUI.EMS.tabs.get('activeTab'); + //initHoverHelp(myTab); + WebGUI.EMS.tabs.set('activeTab',myTab); + }, + failure: function(o) { + WebGUI.EMS.loadingIndicator.hide(); + alert("AJAX call failed"); + } + }; + var request = YAHOO.util.Connect.asyncRequest('GET', url + ';asJson=1' , oCallback); + }; + + //*********************************************************************************** + //This method is out here so it can be overridden. The datatable uses this method to sort it's columns + WebGUI.EMS.sortColumn = function(oColumn,sDir) { + // Default ascending + var sDir = "desc"; + + // If already sorted, sort in opposite direction + if(oColumn.key === this.get("sortedBy").key) { + sDir = (this.get("sortedBy").dir === DataTable.CLASS_ASC) ? "desc" : "asc"; + } + + // Define the new state + var newState = { + startIndex: 0, + sorting: { // Sort values + key: oColumn.key, + dir: (sDir === "asc") ? DataTable.CLASS_ASC : DataTable.CLASS_DESC + }, + pagination : { // Pagination values + startIndex: 0, // Default to first page when sorting + rowsPerPage: this.get("paginator").getRowsPerPage() // Keep current setting + } + }; + + // Create callback object for the request + var oCallback = { + success: this.onDataReturnSetRows, + failure: this.onDataReturnSetRows, + scope: this, + argument: newState // Pass in new state as data payload for callback function to use + }; + + // Send the request + this.getDataSource().sendRequest(WebGUI.EMS.buildQueryString(newState, this), oCallback); + }; + + //*********************************************************************************** + // This method checks for modifier keys pressed during the mouse click + function eventModifiers( e ) { + if( e.event.modifiers ) { + return e.event.modifiers & (Event.ALT_MASK | Event.CONTROL_MASK + | Event.SHIFT_MASK | Event.META_MASK); + } else { + return e.event.altKey | e.event.shiftKey | e.event.ctrlKey; + } + } + + //*********************************************************************************** + // This method does the actual work of loading an item into a tab + // + WebGUI.EMS.loadItem = function ( contentId ) { + var submissionId = parseInt( contentId, 10 ); + var url; + // compare contentId with submissionId incase we get an assetId that starts with numeric chars + if( contentId == submissionId ) { + url = WebGUI.EMS.tabContent['editSubmission'] + ";submissionId=" + submissionId; + } else { + url = WebGUI.EMS.tabContent[contentId]; + } + WebGUI.EMS.newTab(url); + }; + + //*********************************************************************************** + // Load an item when the user clicks on an anchor html element + // + WebGUI.EMS.loadItemFromAnchor = function ( anchorObject ) { + var tabContent = anchorObject.hash.substring(1); + WebGUI.EMS.loadItem(tabContent); + }; + + //*********************************************************************************** + // This method is subscribed to by the DataTable and thus becomes a member of the DataTable + // class even though it is a member of the EMS Class. For this reason, a EMS instance + // is actually passed to the method as it's second parameter. + // + WebGUI.EMS.loadItemFromTable = function ( evt, obj ) { + // if the user pressed a modifier key we want to default + if( eventModifiers( evt ) ) { return } + var target = evt.target; + YAHOO.util.Event.stopEvent(evt.event); + var elCell = this.getTdEl(target); + if(elCell) { + var oRecord = this.getRecord(elCell); + var submissionId = oRecord.getData('submissionId'); + + if( typeof( WebGUI.EMS.items[submissionId] ) != "undefined" ) { + WebGUI.EMS.tabs.set('activeTab',WebGUI.EMS.items[submissionId].tab); + WebGUI.EMS.loadingIndicator.hide(); + } else { + WebGUI.EMS.loadItem( submissionId ); + } + } else { + alert("Could not get table cell for " + target); + } + }; + + + /////////////////////////////////////////////////////////////// + // Public Instance Methods + /////////////////////////////////////////////////////////////// + + //*********************************************************************************** + this.getDataTable = function() { + if(!this.EMSQ) { + return {}; + } + return this.EMSQ; + }; + + //*********************************************************************************** + this.getDefaultSort = function() { + if(this._configs.defaultSort) { + return this._configs.defaultSort; + } + return { + "key" : "creationDate", + "dir" : DataTable.CLASS_DESC + }; + }; + + //*********************************************************************************** + // Override this method if you want pagination to work differently + this.getPaginator = function () { + return new Paginator({ + containers : this._configs.p_containers, + pageLinks : 5, + rowsPerPage : 25, + rowsPerPageOptions : [25,50,100], + template : "{CurrentPageReport} {PreviousPageLink} {PageLinks} {NextPageLink} {RowsPerPageDropdown}" + }); + }; + + //*********************************************************************************** + this.initDataTable = function () { + var datasource = new DataSource(this._configs.datasource); + datasource.responseType = DataSource.TYPE_JSON; + datasource.responseSchema = { + resultsList : 'records', + fields : this._configs.fields, + metaFields : { totalRecords: 'totalRecords' } + }; + + // Initialize the data table + this.EMSQ = new DataTable( + this._configs.dtContainer, + this._configs.columns, + datasource, + { + initialRequest : this._configs.initRequestString, + paginationEventHandler : WebGUI.EMS.handlePagination, + paginator : this.getPaginator(), + dynamicData : true, + sortedBy : this.getDefaultSort() + } + ); + this.EMSQ.subscribe("rowMouseoverEvent", this.EMSQ.onEventHighlightRow); + this.EMSQ.subscribe("rowMouseoutEvent", this.EMSQ.onEventUnhighlightRow); + this.EMSQ.subscribe("cellClickEvent",WebGUI.EMS.loadItemFromTable,this); + // Override function for custom server-side sorting + this.EMSQ.sortColumn = WebGUI.EMS.sortColumn; + this.EMSQ.handleDataReturnPayload = function (oReq, oRes, oPayload ) { + oPayload.totalRecords = parseInt( oRes.meta.totalRecords ); + return oPayload; + }; + this.EMSQ.generateRequest = WebGUI.EMS.buildQueryString; + + //Work around nested scoping for the callback + var myEMSQ = this.EMSQ; + //ensure no memory leaks with the datatable + }; + +}; + +/////////////////////////////////////////////////////////////// +// Public Static Methods +/////////////////////////////////////////////////////////////// + +//*********************************************************************************** +WebGUI.EMS.formatTitle = function ( elCell, oRecord, oColumn, orderNumber ) { + elCell.innerHTML = '