diff --git a/lib/WebGUI/Asset/Sku/EMSTicket.pm b/lib/WebGUI/Asset/Sku/EMSTicket.pm index 8da550916..34f3ac892 100644 --- a/lib/WebGUI/Asset/Sku/EMSTicket.pm +++ b/lib/WebGUI/Asset/Sku/EMSTicket.pm @@ -99,6 +99,8 @@ sub definition { noFormPost => 1, fieldType => "hidden", defaultValue => $date->toDatabase, + label => $i18n->get("add/edit event start date"), + hoverHelp => $i18n->get("add/edit event start date help"), autoGenerate => 0, }, duration => { @@ -250,7 +252,7 @@ Extended to support event metadata. sub getEditForm { my $self = shift; my $form = $self->SUPER::getEditForm(@_); - my $metadata = JSON->new->utf8->decode($self->get("eventMetaData") || '{}'); + my $metadata = $self->getEventMetaData; my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem"); my $date = WebGUI::DateTime->new($self->session, time()); @@ -275,6 +277,29 @@ sub getEditForm { return $form; } + +#------------------------------------------------------------------- + +=head2 getEventMetaData + +Decodes and returns metadata properties as a hashref. + +=head3 key + +If specified, returns a single value for the key specified. + +=cut + +sub getEventMetaData { + my $self = shift; + my $key = shift; + my $metadata = JSON->new->utf8->decode($self->get("eventMetaData") || '{}'); + if (defined $key) { + return $metadata->{$key}; + } + return $metadata; +} + #------------------------------------------------------------------- =head2 getMaxAllowedInCart diff --git a/lib/WebGUI/Asset/Wobject/EventManagementSystem.pm b/lib/WebGUI/Asset/Wobject/EventManagementSystem.pm index 16b19a7af..67df3debe 100644 --- a/lib/WebGUI/Asset/Wobject/EventManagementSystem.pm +++ b/lib/WebGUI/Asset/Wobject/EventManagementSystem.pm @@ -193,6 +193,41 @@ sub getEventMetaFields { my $self = shift; return $self->session->db->buildArrayRefOfHashRefs("select * from EMSEventMetaField where assetId=? order by sequenceNumber, assetId",[$self->getId]); } +#------------------------------------------------------------------- + +=head2 getEventFieldsForImport () + +Returns an array reference of hash references containing name, label, required of the fields that are exportable or importable for events. + +=cut + +sub getEventFieldsForImport { + my $self = shift; + my @fields = (); + my $count = 0; + foreach my $definition (@{WebGUI::Asset::Sku::EMSTicket->definition($self->session)}) { + $count++; + foreach my $field (keys %{$definition->{properties}}) { + next if ($count > 1 && !isIn($field, qw(title description))); + next unless ($definition->{properties}{$field}{label} ne ""); + push(@fields, { + name => $field, + label => $definition->{properties}{$field}{label}, + required => ($field eq "eventNumber") ? 1 : 0, + }); + } + } + foreach my $field (@{$self->getEventMetaFields}) { + push(@fields, { + name => $field->{fieldId}, + label => $field->{label}, + required => $field->{required}, + isMeta => 1, + }); + } + return \@fields; +} + #------------------------------------------------------------------- @@ -284,6 +319,34 @@ sub prepareView { $self->{_viewTemplate} = $template; } +#------------------------------------------------------------------ + +sub purge { + my $self = shift; + my $db = $self->session->db; + + # delete registrations + my $deleteTicket = $db->prepare("delete from EMSRegistrantTicket=?"); + my $deleteToken = $db->prepare("delete from EMSRegistrantToken=?"); + my $deleteRibbon = $db->prepare("delete from EMSRegistrantRibbon=?"); + my $sth = $db->read("select badgeId from EMSRegistrant where emsAssetId=?",[$self->getId]); + while (my ($id) = $sth->array) { + $deleteTicket->execute([$id]); + $deleteToken->execute([$id]); + $deleteRibbon->execute([$id]); + } + $deleteTicket->finish; + $deleteToken->finish; + $deleteRibbon->finish; + $db->write("delete from EMSRegistrant where emsAssetId=?",[$self->getId]); + + # delete other data + $db->write("delete from EMSBadgeGroup where emsAssetId=?",[$self->getId]); + $db->write("delete from EMSEventMetaField where assetId=?",[$self->getId]); + + $self->SUPER::purge(@_); +} + #------------------------------------------------------------------- =head2 view @@ -393,8 +456,8 @@ sub www_buildBadge { my %var = ( %{$self->get}, addTicketUrl => $self->getUrl('func=add;class=WebGUI::Asset::Sku::EMSTicket'), - importTicketsUrl => undef, - exportTicketsUrl => undef, + importTicketsUrl => $self->getUrl('func=importEvents'), + exportTicketsUrl => $self->getUrl('func=exportEvents'), getTicketsUrl => $self->getUrl('func=getTicketsAsJson;badgeId='.$badgeId), canEdit => $self->canEdit, hasBadge => ($badgeId ne ""), @@ -653,6 +716,63 @@ sub www_editRegistrantSave { #------------------------------------------------------------------- +=head2 www_exportEvents ( ) + +Method to deliver this EMS's events in CSV format. + +=cut + +sub www_exportEvents { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient unless $self->canEdit; + + my $csv = Text::CSV_XS->new({ eol => "\n", binary => 1 }); # TODO use their newline? + my $fields = $self->getEventFieldsForImport; + my $out = $session->output; + + # set http header + $self->session->http->setFilename($self->getTitle.".csv", 'application/excel'); + + # add file header + my @header = (); + foreach my $field (@{$fields}) { + push @header, $field->{label}; + } + $csv->combine(@header); + $out->print($csv->string,1); + + # process events + foreach my $id (@{$self->getLineage(["children"],{includeOnlyClasses=>["WebGUI::Asset::Sku::EMSTicket"]})}) { + my $event = WebGUI::Asset::Sku::EMSTicket->new($session, $id); + my @export = (); + if (defined $event) { + my $metadata = $event->getEventMetaData; + foreach my $field (@{$fields}) { + if ($field->{isMeta}) { + push(@export, $metadata->{$field->{name}}); + } + else { + push(@export, $event->get($field->{name})); + } + } + } + if ($csv->combine(@export)) { + $out->print($csv->string,1); + } + else { + # $out->print(join('|',@export)."\n",1); + $out->print("Error: ".$csv->error_input,1); + last; + } + } + + # finished + return "chunked"; +} + +#------------------------------------------------------------------- + =head2 www_getBadgesAsJson () Retrieves a list of badges for the www_view() method. @@ -1104,6 +1224,446 @@ sub www_getTokensAsJson { #------------------------------------------------------------------- +=head2 www_importEvents ( [ $errors_aref ] ) + +Show the CSV-file upload form, along with optional errors. + +=cut + +sub www_importEvents { + my ($self) = shift; + my $errors_aref = shift || []; + + return $self->session->privilege->insufficient unless $self->canEdit; + my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); + my $form = $self->session->form; + + # header, with optional errors as unordered list + my $page_header = $i18n->get('import form header'); + if (@$errors_aref) { + $page_header .= ""; + } + + # create the form + my $f = WebGUI::HTMLForm->new( $self->session, action => $self->getUrl("func=doImportEvents"), enctype => 'multipart/form-data' ); + + $f->file( + -label => $i18n->get('choose a file to import'), + -hoverHelp => $i18n->get('import hoverhelp file'), + -name => 'file', + ); + $f->selectBox( + -label => $i18n->get('what about duplicates'), + -name => 'duplicates_how', + -hoverHelp => $i18n->get('import hoverhelp dups'), + -defaultValue => ($form->param('duplicates_how')||'skip'), + -options => { + skip => $i18n->get('skip'), + overwrite => $i18n->get('overwrite'), + }, + ); + $f->yesNo( + -label => $i18n->get('ignore first line'), + -name => 'ignore_first_line', + -hoverHelp => $i18n->get('import hoverhelp first line'), + -defaultValue => $form->param('ignore_first_line'), + ); + $f->fieldSetStart('Fields'); + $f->raw(q[ ]); + + # create the std & meta fields part of the form + foreach my $field (@{$self->getEventFieldsForImport}) { + $f->raw(qq[ ]); + } + + $f->raw(q[
  ]. + q[ File Contains FieldField Is Duplicate Key
$field->{label} ]); + # insert the first checkbox + $f->raw(WebGUI::Form::Checkbox->new( + $self->session,{ + -name => "file_contains-$field->{name}", + -value => 1, + -checked => ($field->{required} || $form->param("file_contains-$field->{name}")), + })->toHtml()); + $f->raw(qq[ ]); + # insert the second checkbox + $f->raw(WebGUI::Form::Checkbox->new( + $self->session,{ + -name => "check_duplicates-$field->{name}", + -value => 1, + -checked => $form->param("check_duplicates-$field->{name}"), + })->toHtml()); + $f->raw(qq[
]); + $f->fieldSetEnd; + $f->submit(-value=>$i18n->get('import events')); + + return $self->processStyle($page_header.'

'.$f->print); +} + + + +#------------------------------------------------------------------- + +=head2 importEventsSave ( ) + +Handle the uploading of a CSV event data file, along with other options. + +=cut + +my $max_errors = 10; # number of errors to collect before showing them, when we're in error-collecting mode. +sub www_importEventsSave { + my $self = shift; + my $session = $self->session; + + return $session->privilege->insufficient unless $self->canEdit; + + # set up + my $start = time; + my $i18n = WebGUI::International->new($session,'Asset_EventManagementSystem'); + my $csv = Text::CSV_XS->new({ binary => 1 }); + my $out = $session->output; + + # get csv data + $out->print('Reading file...',1); + my $storage = WebGUI::Storage->createTemp($session); + my $filename = $storage->addFileFromFormPost("file_file"); + + if (open my $file, "<", $storage->getPath($filename)) { + $out->print('Processing file...',1); + while (my $line = <$file>) { + if ($csv->parse($line)) { + my @row = $csv->fields; + } + else { + $out->print($csv->error_input() . ": ". $line.'
',1); + } + } + } + else { + $out->print($i18n->get("no import took place").'
',1); + } + + # clean up + $storage->delete; + return "chunked"; + + my $start_time = time; + + my $no_action_taken_error = { # on error, always let the user know that we didn't partially import their data + type => 'general', + message => $i18n->get("no import took place"), + }; + + # get input: CSV data + my $storageId = $self->session->form->param("file_file"); + my $storage = WebGUI::Storage->get($self->session, $storageId); + return $self->error([{ type => 'general', message => $i18n->get("enter import file") }], 'www_importEvents') + unless $storage; + my $filename = $storage->addFileFromFormPost("file_file"); + my $csv_data = $storage->getFileContentsAsScalar($filename); + $storage->delete; + + # store the input on disk for processing - TODO can we do this whole thing more easily? + my $fh = tempfile(); + print $fh $csv_data; + seek $fh, 0, 0; + + # organize meta input: sorted fields included and duplicate keys + my $skip_duplicates = $self->session->form->process('duplicates_how') eq 'skip' ? 1 : 0; + my $ignore_first_line = $self->session->form->process('ignore_first_line') eq 'yes' ? 1 : 0; + my @params = $self->session->form->param(); + my @fields_included = grep s/^file_contains-(.+)$/$1/, @params; + my @dup_keys = grep s/^check_duplicates-(.+)$/$1/, @params; + return $self->error([$no_action_taken_error,{ + type => 'general', + message => $i18n->get('import need dup key'), + }], 'www_importEvents') unless @dup_keys; + my @all_data_fields = @{ $self->getEventDataFields() }; # aref of sorted hrefs with name, label, required, type keys + my $sku_is_required = grep { $_ eq 'sku' } @dup_keys; + if (!$sku_is_required) { + for my $field (@all_data_fields) { $field->{required} = 0 if $field->{name} eq 'sku' } # not required here + } + my @sorted_fields_included = (); + for my $field (@all_data_fields) { + if (grep { $_ eq $field->{name} } @fields_included) { + push @sorted_fields_included, $field->{name}; + } + } + my @missing_required_fields = (); + for my $field (grep $_->{required}, @all_data_fields) { + if (!grep { $_ eq $field->{name} } @fields_included) { + push @missing_required_fields, $field->{label}; + } + } + if (@missing_required_fields) { + return $self->error([$no_action_taken_error,{ + type => 'general', + message => $i18n->get('check required fields')."@missing_required_fields", + }], + 'www_importEvents', + ); + } + + # we sanity check all of the input before processing any of it + # check all records for required fields and field count + my $errors = []; + my $prerequisites_href = {}; + if (grep /^prerequisiteId$/, @fields_included) { + $prerequisites_href = $self->session->db->buildHashRef(" SELECT name, prerequisiteId FROM EventManagementSystem_prerequisites"); + } + my $templates_href = {}; + if (grep /^templateId$/, @fields_included) { + $templates_href = $self->session->db->buildHashRef(<<""); + SELECT assetData.title, template.assetId + FROM template + LEFT JOIN assetData + ON assetData.assetId = template.assetId + AND assetData.revisionDate = template.revisionDate + + } + my %approved_values = ( Approved => 1, Denied => 0, Pending => -1, Cancelled => -2); + my $meta_fields_aref = $self->getEventMetaFields; + my %meta_fields = (); + @meta_fields{map {$_->{fieldId}} @$meta_fields_aref} = @$meta_fields_aref; # get them keyed by fieldId + my $first_line = 1; + my $before_check_time = time; + while (my $line = do { local $/ = "\n"; <$fh> }) { + if ($first_line and $ignore_first_line) { + $first_line = 0; + next; + } + + # line is blank - skip it, no error + next if $line =~ /^[\s\r\n]*$/; + + # parse the line + if (!$csv->parse($line)) { + my $error_input = $csv->error_input; + push @$errors,{ + type => 'general', # "There was an error processing this input: '$line'" + message => sprintf $i18n->get('import record parse error'), + $fh->input_line_number - $ignore_first_line, $error_input, + }; + if (@$errors >= $max_errors) { + return $self->error($errors, 'www_importEvents'); + } + next; + } + + my @columns = $csv->fields(); + + # check the field count + if (@columns != @fields_included) { + push @$errors,{ + type => 'general', + message => sprintf $i18n->get('field count mismatch'), $fh->input_line_number-$ignore_first_line, scalar @columns, scalar @fields_included, + }; + if (@$errors >= $max_errors) { + return $self->error($errors, 'www_importEvents'); + } + next; + } + # check the required fields + my %data = map { $sorted_fields_included[$_], $columns[$_] } 0..$#columns; + $data{sku} ||= 'do not check this here - we will create one later if necessary' unless $sku_is_required; + my $new_errors = $self->checkRequiredFields(\%data, $fh->input_line_number-$ignore_first_line); + if (@$new_errors) { + push @$errors, @$new_errors; + return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; + } + #check that approved, if present, is a good value + if (exists $data{approved} && ! grep { lc $_ eq lc $data{approved} } %approved_values) { + push @$errors, { + type => 'general', + message => sprintf $i18n->get('import invalid status'), $fh->input_line_number-$ignore_first_line, $data{approved}, + }; + return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; + } + #check that prerequisiteId, if present, is a good value + if (exists $data{prerequisiteId} && !exists $prerequisites_href->{$data{prerequisiteId}}) { + push @$errors, { + type => 'general', + message => sprintf $i18n->get('import invalid prereq'), $fh->input_line_number-$ignore_first_line, $data{prerequisiteId}, + }; + return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; + } + #check that templateId, if present, is a good value + if (exists $data{templateId} && !exists $templates_href->{$data{templateId}}) { + push @$errors, { + type => 'general', + message => sprintf $i18n->get('import invalid template'), $fh->input_line_number-$ignore_first_line, $data{templateId}, + }; + return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; + } + } + my $after_check_time = time; + + # errors? output them instead of proceeding with the import + return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors; + + # organize our existing events by duplicate keys + my %duplicate_events = (); + for my $event_href (@{ $self->getAllStdEventDetails }) { + my $event_meta_data_href = $self->getEventMetaDataFields($event_href->{productId}); + my $dup_key = join '|', map { /^metadata_(.+)/ ? $event_meta_data_href->{$1}->{fieldData} : $event_href->{$_} } @dup_keys; + $duplicate_events{$dup_key} = $event_href->{productId}; + } + + # input is deemed sane - time to process it + my $total_lines = $fh->input_line_number; + seek $fh, 0, 0; # start of the file, again + my %all_data_fields; + @all_data_fields{map $_->{name}, @all_data_fields} = @all_data_fields; # by name + my $existing_events_aref = $self->getAllStdEventDetails; + $first_line = 1; + my @skipped = (); + my @overwritten = (); + my @blank_lines = (); + my $before_process_time = time; + while (my $line = do { local $/ = "\n"; <$fh> }) { + if ($first_line and $ignore_first_line) { + $first_line = 0; + next; + } + + # line is blank - skip it, no error + if ($line =~ /^[\s\r\n]*$/) { + push @blank_lines, $fh->input_line_number - $ignore_first_line - $total_lines; # the record number + next; + } + + # parse the line + if (!$csv->parse($line)) { + my $error_input = $csv->error_input; + push @$errors,{ + type => 'general', # "There was an error processing this input: '$line'" + message => sprintf $i18n->get('import line parse error'), $error_input, $fh->input_line_number - $ignore_first_line - $total_lines, + }; + # this should "never happen" (TM) + return $self->error($errors, 'www_importEvents'); + } + + my @columns = $csv->fields(); + my %data = map { $sorted_fields_included[$_], $columns[$_] } 0..$#columns; + + # get the data in the form in which it will be compared to existing events + # to find dups, as well as how we'll store it in the db + for my $key (keys %data) { + if ($key eq 'approved') { + $data{$key} = $approved_values{$data{$key}}; + } + else { + my $method = $all_data_fields{$key}->{type}; + if ($method =~ /^(?:hidden|check|select)List$/i) { + $data{$key} = $self->session->form->$method(undef, split /;/, $data{$key}); + } + else { + $data{$key} = $self->session->form->$method(undef, $data{$key}); + } + } + } + + # store it or skip it + my $is_new = 1; + my $this_dup_key = join '|', map $data{$_}, @dup_keys; + my $product_id = "new"; + if (exists $duplicate_events{$this_dup_key}) { + if ($skip_duplicates) { + push @skipped, $fh->input_line_number - $ignore_first_line - $total_lines; # the record number + next; + } + push @overwritten, $fh->input_line_number - $ignore_first_line - $total_lines; # the record number + $is_new = 0; + $product_id = $duplicate_events{$this_dup_key}; # overwrite this product_id + # TODO load everything for this product_id so that we only overwrite the fields that are in the CSV file + } + + # reasonable defaults? + $data{sku} = $self->session->id->generate unless exists $data{sku}; + $data{approved} = $approved_values{Pending} unless exists $data{approved}; + + # store data in the EMS_products table + my $ems_products_href = { + productId => $product_id, + startDate => $data{startDate}, + endDate => $data{endDate}, + maximumAttendees => $data{maximumAttendees}, + approved => $data{approved}, + prerequisiteId => $prerequisites_href->{$data{prerequisiteId}}, + #passId => '', # NULL for these - there's no way to import them + #imageId => '', + #passType => '', + }; + my $pid = $self->setCollateral("EventManagementSystem_products", "productId",$ems_products_href,1,1); + + # store data in EMS_metaData + my @meta_fields = grep { $_->{name} =~ /^metadata_/ } @all_data_fields; + foreach my $field (@meta_fields) { + $field->{name} =~ /^metadata_(.+)$/; + my $field_id = $1; + my $data = $data{$field->{name}}; + my $sql = "insert into EMSEventMetaData values (". + $self->session->db->quoteAndJoin([$field_id, $pid, $data]). + ") on duplicate key update fieldData=". + $self->session->db->quote($data); + $self->session->db->write($sql); + } + + # store data in products + my $data_href = { + productId => $pid, + title => $data{title}, + description => $data{description}, + price => $data{price}, + useSalesTax => ($data{useSalesTax}||0), + weight => ($data{weight}||0), + sku => $data{sku}, + skuTemplate => ($data{skuTemplate}||''), + templateId => ($templates_href->{$data{templateId}}||''), + }; + if ($is_new) { + $self->session->db->setRow("products", "productId", $data_href, $pid); + $duplicate_events{$this_dup_key} = $product_id; + } + else { + $self->session->db->setRow("products", "productId", $data_href); + } + } + my $after_process_time = time; + + # acknowledge success - &error will work fine - with the number of records processed/imported/skipped/overwritten + my $processed_count = $fh->input_line_number - $ignore_first_line - $total_lines; + my $other_count = $skip_duplicates ? @skipped : @overwritten; + my $imported_count = $processed_count - $other_count - @blank_lines; + my $what_done = $i18n->get($skip_duplicates ? 'skipped' : 'overwritten'); + my $message = sprintf $i18n->get('import ok'), $processed_count, $imported_count, scalar(@blank_lines), $other_count, $what_done; + my $html = "

  • $message
  • "; + $html .= join '', map "
  • ".sprintf($i18n->get('import blank line'),$_)."
  • ", @blank_lines; + $html .= join '', map "
  • ".sprintf($i18n->get('import other line'),$_,$what_done)."
  • ", ($skip_duplicates ? @skipped : @overwritten); + + my $total_time = $after_process_time - $start_time; + my $prep_time = $before_check_time - $start_time; + my $check_time = $after_check_time - $before_check_time; + my $prep_time2 = $before_process_time - $after_check_time; + my $process_time = $after_process_time - $before_process_time; + my $time_block = <<""; +
    + total time: $total_time
    + prep time: $prep_time
    + check time: $check_time
    + prep2 time: $prep_time2
    + process time: $process_time
    +
    + + return $self->processStyle("$time_block"); +} + +#------------------------------------------------------------------- + =head2 www_lookupRegistrant () Displays the badges purchased by the current user, or all users if the user is part of the registration staff. @@ -1520,1000 +2080,4 @@ sub www_toggleRegistrantCheckedIn { } - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#------------------------------------------------------------------- -sub _acWrapper { - my $self = shift; - my $html = shift; - my $title = shift; - my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); - my $ac = $self->getAdminConsole; - $ac->addSubmenuItem($self->getUrl('func=search'),$i18n->get("manage events")); - $ac->addSubmenuItem($self->getUrl('func=manageEventMetadata'), $i18n->get('manage event metadata')); - $ac->addSubmenuItem($self->getUrl('func=managePrereqSets'), $i18n->get('manage prerequisite sets')); - $ac->addSubmenuItem($self->getUrl('func=searchBadges'), "Search Badges"); - $ac->addSubmenuItem($self->getUrl('func=manageDiscountPasses'), $i18n->get('manage discount passes')); - $ac->addSubmenuItem($self->getUrl('func=importEvents'), $i18n->get('import events')); - $ac->addSubmenuItem($self->getUrl('func=exportEvents'), $i18n->get('export events')); - return $ac->render($html,$title); -} - -#------------------------------------------------------------------- - -=head2 checkRequiredFields ( [ dataHref ] [, recNum ] ) - -Check for null form fields. - -Returns an array reference containing error messages - -=head3 dataHref - -An href with the data for the event that we want to check. If absent, then the current form submission is used. - -=head3 recNum - -The number of the record, for error reporting - -=cut - -sub checkRequiredFields { - my $self = shift; - my $event_data_href = shift || $self->session->form->paramsHashRef(); - my $rec_num = shift || ''; - - my $requiredFields = $self->getRequiredFields(); - my @errors; - - foreach my $requiredField (keys %{$requiredFields}) { - if ($event_data_href->{$requiredField} eq "") { - push(@errors, { - type => "nullField$rec_num", - fieldName => $requiredFields->{"$requiredField"} - } - ); - } - - } - - return \@errors; -} - - -#------------------------------------------------------------------- - -=head2 getEventMetaDataFields ( productId ) - -Returns a hash reference containing all metadata field properties. - -=head3 productId - -Which product to get metadata for. - -=cut - -sub getEventMetaDataFields { - my $self = shift; - my $productId = shift; - my $useGlobalMetadata = shift; - my $globalWhere = ($useGlobalMetadata == 0 || $useGlobalMetadata == 'false')?" where f.assetId='".$self->getId."'":''; - my $sql = "select f.*, d.fieldData - from EMSEventMetaField f - left join EMSEventMetaData d on f.fieldId=d.fieldId - and d.productId=? $globalWhere - order by f.sequenceNumber"; - tie my %hash, 'Tie::IxHash'; - my $sth = $self->session->db->read($sql,[$productId]); - while( my $h = $sth->hashRef) { - foreach(keys %$h) { - $hash{$h->{fieldId}}{$_} = $h->{$_}; - } - } - $sth->finish; - return \%hash; -} - -#------------------------------------------------------------------ - -sub purge { - my $self = shift; - my $db = $self->session->db; - # delete meta fields - my $sth = $db->read("select fieldId from EMSEventMetaField where assetId=?",[$self->getId]); - while (my ($id) = $sth->array) { - $self->deleteMetaField($id); - } - # delete events - $sth = $db->read("select productId from EventManagementSystem_products where assetId=?",[$self->getId]); - while (my ($id) = $sth->array) { - $self->deleteEvent($id); - } - # delete prereqs - $sth = $db->read("select prerequisiteId from EventManagementSystem_prerequisites where assetId=?",[$self->getId]); - while (my ($id) = $sth->array) { - $self->deletePrereqSet($id); - } - # delete badges - $sth = $db->read("select badgeId from EventManagementSystem_badges where assetId=?",[$self->getId]); - while (my ($id) = $sth->array) { - $self->deleteBadge($id); - } - $self->SUPER::purge(@_); -} - -#------------------------------------------------------------------ - -=head2 getRequiredFields ( ) - -Returns the required fiends as ordered hash of fieldname => $readable pairs. MetaFields are paired like so: metadata_[fieldId] => $readable. - -=cut - -sub getRequiredFields { - my $self = shift; - - my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); - - my %requiredFields; - tie %requiredFields, 'Tie::IxHash'; - - #-----Form name--------------User Friendly Name----# - %requiredFields = ( - "title" => $i18n->get("add/edit event title"), - "description" => $i18n->get("add/edit event description"), - "price" => $i18n->get("price"), - "maximumAttendees" => $i18n->get("add/edit event maximum attendees"), - "sku" => $i18n->get("sku"), - ); - - my $mdFields = $self->getEventMetaDataFields; - foreach my $mdField (keys %{$mdFields}) { - next unless $mdFields->{$mdField}->{required}; - $requiredFields{'metadata_'.$mdField} = $mdFields->{$mdField}->{name}; - } - - return \%requiredFields; -} - -#------------------------------------------------------------------- - -=head2 www_test ( ) - -Test WebGUI::Form::*::getValueFromPost methods used in www_doImportEvents - -=cut - -sub www_test { - my $self = shift; - # if there's no input, output a form - my @param = $self->session->form->param(); - if (@param <= 1) { - my $f = WebGUI::HTMLForm->new($self->session, action => $self->getUrl("func=test"), ); - - #$f->Asset(); - #$f->Button(); - #$f->Captcha(); - $f->CheckList( - -name => "checkList", - -label => 'Checklist', - -options => { 1, one => 2, two => 3, 'three' }, - -value => [1,2], - ); - $f->Checkbox( - -name => 'checkbox', - -label => 'Checkbox', - -value => 42, - -checked=> 1, - ); - #$f->ClassName(); - #$f->Codearea(); - #$f->Color(); - #$f->Combo(); - #$f->ContentType(); - #$f->Control(); - #$f->Country(); - #$f->DatabaseLink(); - $f->Date( - -name => 'date', - -label => 'Date', - -value => 770000000, # ? - ); - $f->DateTime( - -name => 'dateTime', - -label => 'DateTime', - -value => 770000045, # ? - ); - #$f->DynamicField(); - #$f->Email(); - #$f->FieldType(); - #$f->File(); - #$f->FilterContent(); - $f->Float( - -name => 'float', - -label => 'Float', - -value => 42.42, - ); - #$f->Group(); - #$f->HTMLArea(); - #$f->HexSlider(); - #$f->Hexadecimal(); - #$f->Hidden(); - $f->HiddenList( - -name => "hiddenList", - -label => 'Hiddenlist', - -options => { 1, one => 2, two => 3, 'three' }, - -value => [1,2], - ); - #$f->Image(); - #$f->IntSlider(); - $f->Integer( - -name => 'integer', - -label => 'Integer', - -value => 42, - ); - $f->Interval( - -name => 'interval', - -label => 'Interval', - -value => '86400', - ); - #$f->LdapLink(); - #$f->List(); - #$f->MimeType(); - #$f->Password(); - #$f->Phone(); - #$f->Radio(); - #$f->RadioList(); - #$f->ReadOnly(); - #$f->SelectBox(); - $f->SelectList( - -name => "selectList", - -label => 'Selectlist', - -options => { 1, one => 2, two => 3, 'three' }, - -value => [1,2], - ); - #$f->SelectSlider(); - #$f->Slider(); - #$f->Submit(); - #$f->Template(); - $f->Text( - -name => 'text', - -label => 'Text', - -value => 'text', - ); - $f->Text( - -name => 'text-empty', - -label => 'Text', - -value => '', - ); - $f->Textarea( - -name => 'textarea', - -label => 'Textarea', - -value => "text\n\narea", - ); - $f->TimeField( - -name => 'timeField', - -label => 'TimeField', - -value => '12:12:12', - ); - $f->TimeField( - -name => 'timeField-nosecs', - -label => 'TimeField', - -value => '12:12', - ); - #$f->TimeZone(); - #$f->Url(); - #$f->User(); - #$f->WhatNext(); - #$f->Workflow(); - $f->YesNo( - -name => 'yesNo', - -label => 'YesNo', - -value => 1, - ); - $f->YesNo( - -name => 'yesNo-no', - -label => 'YesNo', - -value => 0, - ); - #$f->Zipcode(); - - $f->submit(-value=>'Run tests'); - return $self->_acWrapper($f->print, "test"); - } - # there's input, so test the input - else { - my $form = $self->session->form; - - my $html = ''; - for my $param (sort @param) { - next if $param eq 'func' || $param =~ /interval/i; - - # this lets us have multiple fields with the same type: text, text-empty, etc - $param =~ /^(\w+)/ or return $self->_acWrapper("There was an error with param '$param'","test"); - my $type = $1; - my @raw = $form->process($param); - my $std = $form->process($param, $type); - my $offline = $form->$type(undef, @raw); - - if ($std eq $offline and $std == $offline) { - $html .= "'$param': raw: '@raw'; std: '$std'; offline: '$offline'

    "; - } - else { - $html .= "'$param': raw: '@raw'; std: '$std'; offline: '$offline'

    "; - } - } - - # do the interval test - my $num = $form->process('interval_interval'); - my $units = $form->process('interval_units'); - my $std_interval = $form->process('interval', 'interval'); - my $offline_interval = $form->interval(undef, "$num $units"); # pass in as it would be in an import file - - if ($std_interval eq $offline_interval and $std_interval == $offline_interval) { - $html .= "'interval': raw: '$num $units'; std: '$std_interval'; offline: '$offline_interval'

    "; - } - else { - $html .= "'interval': raw: '$num $units'; std: '$std_interval'; offline: '$offline_interval'

    "; - } - - - # canned tests for things like all empty checkbox groups and yesNo fields - $html .= "

    Other tests

    "; - - # empty checkList - my $cl = $form->checkList(undef, ''); # passing an empty list here is not going to work - if ($cl eq '') { - $html .= "empty CheckList is: ''

    "; - } - else { - $html .= "empty CheckList is: '$cl'

    "; - } - - # empty date - my $date = $form->date(undef, ''); - if ($date eq '') { - $html .= "empty Date is: ''

    "; - } - else { - $html .= "empty Date is: '$date'

    "; - } - - # empty datetime - my $dt = $form->dateTime(undef, ''); - if ($dt eq '') { - $html .= "empty DateTime is: ''

    "; - } - else { - $html .= "empty DateTime is: '$dt'

    "; - } - - # empty hiddenList - my $hl = $form->hiddenList(undef, ''); # passing an empty list here is not going to work - if ($hl eq '') { - $html .= "empty HiddenList is: ''

    "; - } - else { - $html .= "empty HiddenList is: '$hl'

    "; - } - - # empty selectList - my $sl = $form->selectList(undef, ''); # passing an empty list here is not going to work - if ($sl eq '') { - $html .= "empty SelectList is: ''

    "; - } - else { - $html .= "empty SelectList is: '$sl'

    "; - } - - # empty timeField - my $std_tf = $form->process('doesnotexist', 'timeField'); - my $tf = $form->timeField(undef, ''); # passing an empty list here is not going to work - if ($tf eq $std_tf) { - $html .= "empty TimeField is: '$std_tf'

    "; - } - else { - $html .= "empty TimeField is: '$tf' (not '$std_tf')

    "; - } - - # yesNo - my $yn = $form->yesNo(undef, ''); - if ($yn eq '') { - $html .= "empty yesNo is: ''

    "; - } - else { - $html .= "empty yesNo is: '$yn'

    "; - } - - - return $self->_acWrapper("

    $html
    ", "test"); - } -} - -#------------------------------------------------------------------- - -=head2 www_exportEvents ( ) - -Method to deliver this EMS's events in CSV format. - -=cut - -sub www_exportEvents { - my $self = shift; - - return $self->session->privilege->insufficient unless $self->canEdit; - - my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); - my $csv = Text::CSV_XS->new({ eol => "\n", binary => 1 }); # TODO use their newline? - - # if we need to punt - my @error_args = ($i18n->get('export error'), $i18n->get('export events')); - - # get standard & metaField labels TODO - refactor this - my @std_labels = map $i18n->get($_), ( 'status', 'add/edit event title', 'add/edit event description', - 'add/edit event image', 'add/edit useSalesTax', 'price', 'add/edit event template', 'weight', 'sku', - 'sku template', 'add/edit event start date', 'add/edit event end date', 'add/edit event maximum attendees', - 'prereq set name field label' ); - - my $meta_fields_aref = $self->getEventMetaFields; - my @meta_labels = map $_->{label}, @$meta_fields_aref; - my @all_labels = (@std_labels, @meta_labels); - - # combine field labels for first line of CSV output - return $self->_acWrapper(@error_args) - unless $csv->combine(@all_labels); - my $csvdata = $csv->string(); - - # get events of this EMS - my $events_std_data_aref = $self->getAllStdEventDetails; - - # get the prereqs - my $prereqs_href = $self->session->db->buildHashRef(<<""); - SELECT prerequisiteId, name - FROM EventManagementSystem_prerequisites - - my $templates_href = $self->session->db->buildHashRef(<<""); - SELECT template.assetId, assetData.title - FROM template - LEFT JOIN assetData - ON assetData.assetId = template.assetId - AND assetData.revisionDate = template.revisionDate - - # format useSalesTax(yes/no), startDate, endDate, and approved values - my $dt = $self->session->datetime; - for my $event (@$events_std_data_aref) { - $event->{approved} = $self->getEventStateLabel($event->{approved}); - $event->{startDate} = $dt->epochToSet($event->{startDate},1); - $event->{endDate} = $dt->epochToSet($event->{endDate},1); - $event->{useSalesTax} = $event->{useSalesTax} ? 'Y' : 'N'; - $event->{prerequisiteId} = $prereqs_href->{$event->{prerequisiteId}}; - $event->{templateId} = $templates_href->{$event->{templateId}}; - } - - # standard field names in the same order as the web forms - my @std_fields = qw( approved title description imageId useSalesTax price templateId weight sku - skuTemplate startDate endDate maximumAttendees prerequisiteId); - - # for each event, gather std & meta data & create CSV record - for my $std_data_href (@$events_std_data_aref) { - # get the std data - my @std_data = map $std_data_href->{$_}, @std_fields; - - # get the meta data - my ($meta_data_href) = $self->getEventMetaDataFields($std_data_href->{productId}, $self->get("globalMetadata")); - my @meta_data = (); - for my $meta_field_config_href (@$meta_fields_aref) { - # get the value for this field, depending on the dataType - my $value = $meta_data_href->{$meta_field_config_href->{fieldId}}->{fieldData}; - - # format some field types - # if the type is, then the value is - my $readable = $meta_field_config_href->{dataType} eq 'Date' ? $dt->epochToSet($value) - : $meta_field_config_href->{dataType} eq 'DateTime' ? $dt->epochToSet($value,1) - : $meta_field_config_href->{dataType} eq 'YesNo' ? ( $value ? 'Y' : length($value) ? 'N' : '' ) - : $meta_field_config_href->{dataType} eq 'TimeField' ? $dt->secondsToTime($value) - : $value - ; - - $readable =~ s/\n/;/g; - push @meta_data, $readable; - } - - # create CSV record for this event or display error message - if ($csv->combine(@std_data, @meta_data)) { - $csvdata .= $csv->string(); - } - else { - return $self->_acWrapper(@error_args); - } - } - - # no errors, output file as attachment - my $filename = $self->session->db->quickScalar(" SELECT title FROM assetData WHERE assetId = ? ", [ $self->getId ]); - $filename =~ s/[^-\w.]//g; # old-school - $self->session->http->setFilename("$filename.csv", 'application/excel'); - return $csvdata; -} - -#------------------------------------------------------------------- - -=head2 getEventDataFields ( ) - -Returns a form-field ordered aref of hrefs of event data fields (standard and meta) with these keys: name, label, type, and required. - -=cut - -sub getEventDataFields { - my $self = shift; - - my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); - - my $custom_rows_aref = $self->getEventMetaFields; - my @custom_rows = map { - label => $_->{label}, - name => "metadata_$_->{fieldId}", - required => $_->{required}, - type => $_->{dataType}, - }, @$custom_rows_aref; - - return [ - # std fields - { label => $i18n->get('status'), name => 'approved', required => 0, type => 'text' }, - { label => $i18n->get('add/edit event title'), name => 'title', required => 1, type => 'text' }, - { label => $i18n->get('add/edit event description'), name => 'description', required => 1, type => 'HTMLArea' }, - { label => $i18n->get('add/edit event image'), name => 'imageId', required => 0, type => 'text' }, - { label => $i18n->get('add/edit useSalesTax'), name => 'useSalesTax', required => 0, type => 'yesNo' }, - { label => $i18n->get('price'), name => 'price', required => 1, type => 'float' }, - { label => $i18n->get('add/edit event template'), name => 'templateId', required => 0, type => 'template' }, - { label => $i18n->get('weight'), name => 'weight', required => 0, type => 'float' }, - { label => $i18n->get('sku'), name => 'sku', required => 1, type => 'text' }, - { label => $i18n->get('sku template'), name => 'skuTemplate', required => 0, type => 'text' }, - { label => $i18n->get('add/edit event start date'), name => 'startDate', required => 0, type => 'dateTime' }, - { label => $i18n->get('add/edit event end date'), name => 'endDate', required => 0, type => 'dateTime' }, - { label => $i18n->get('add/edit event maximum attendees'), name => 'maximumAttendees', required => 1, type => 'integer' }, - { label => $i18n->get('prereq set name field label'), name => 'prerequisiteId', required => 0, type => 'text' }, - - @custom_rows, - ]; -} - -#------------------------------------------------------------------- - -=head2 www_importEvents ( [ $errors_aref ] ) - -Show the CSV-file upload form, along with optional errors. - -=cut - -sub www_importEvents { - my ($self) = shift; - my $errors_aref = shift || []; - - return $self->session->privilege->insufficient unless $self->canEdit; - my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); - my $form = $self->session->form; - - # header, with optional errors as unordered list - my $page_header = $i18n->get('import form header'); - if (@$errors_aref) { - $page_header .= ""; - } - - # create the form - my $f = WebGUI::HTMLForm->new( $self->session, action => $self->getUrl("func=doImportEvents"), enctype => 'multipart/form-data' ); - - $f->file( - -label => $i18n->get('choose a file to import'), - -hoverHelp => $i18n->get('import hoverhelp file'), - -name => 'file', - ); - $f->selectBox( - -label => $i18n->get('what about duplicates'), - -name => 'duplicates_how', - -hoverHelp => $i18n->get('import hoverhelp dups'), - -value => ($form->param('duplicates_how')||'skip'), - -options => { - skip => $i18n->get('skip'), - overwrite => $i18n->get('overwrite'), - }, - ); - $f->radioList( - -label => $i18n->get('ignore first line'), - -name => 'ignore_first_line', - -hoverHelp => $i18n->get('import hoverhelp first line'), - -value => ($form->param('ignore_first_line')||'no'), - -options => { - yes => $i18n->get('yes'), - no => $i18n->get('no'), - }, - ); - $f->fieldSetStart('Fields'); - $f->raw(q[ ]); - - # create the std & meta fields part of the form - my $rows_aref = $self->getEventDataFields; # form-field ordered aref of hrefs with these keys: name, label, required, type - my %row_data = map { $_->{name}, $_ } @$rows_aref; - for my $row (@$rows_aref) { - $f->raw(qq[ ]); - } - - $f->raw(q[
      ]. - q[ File Contains FieldField Is Duplicate Key
    $row->{label} ]); - # insert the first checkbox - $f->raw(WebGUI::Form::Checkbox->new( - $self->session,{ - -name => "file_contains-$row->{name}", - -value => 1, - -checked => ($row_data{$row->{name}}->{required} || $form->param("file_contains-$row->{name}")), - })->toHtml()); - $f->raw(qq[ ]); - # insert the second checkbox - $f->raw(WebGUI::Form::Checkbox->new( - $self->session,{ - -name => "check_duplicates-$row->{name}", - -value => 1, - -checked => $form->param("check_duplicates-$row->{name}"), - })->toHtml()); - $f->raw(qq[
    ]); - $f->fieldSetEnd; - $f->submit(-value=>$i18n->get('import events')); - - return $self->_acWrapper($page_header.'

    '.$f->print, $i18n->get('import events')); -} - -#------------------------------------------------------------------- - -=head2 doImportEvents ( ) - -Handle the uploading of a CSV event data file, along with other options. - -=cut - -my $max_errors = 10; # number of errors to collect before showing them, when we're in error-collecting mode. -sub www_doImportEvents { - my $start_time = time; - my $self = shift; - - return $self->session->privilege->insufficient unless $self->canEdit; - my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem'); - my $csv = Text::CSV_XS->new({ binary => 1 }); - my $no_action_taken_error = { # on error, always let the user know that we didn't partially import their data - type => 'general', - message => $i18n->get("no import took place"), - }; - - # get input: CSV data - my $storageId = $self->session->form->param("file_file"); - my $storage = WebGUI::Storage->get($self->session, $storageId); - return $self->error([{ type => 'general', message => $i18n->get("enter import file") }], 'www_importEvents') - unless $storage; - my $filename = $storage->addFileFromFormPost("file_file"); - my $csv_data = $storage->getFileContentsAsScalar($filename); - $storage->delete; - - # store the input on disk for processing - TODO can we do this whole thing more easily? - my $fh = tempfile(); - print $fh $csv_data; - seek $fh, 0, 0; - - # organize meta input: sorted fields included and duplicate keys - my $skip_duplicates = $self->session->form->process('duplicates_how') eq 'skip' ? 1 : 0; - my $ignore_first_line = $self->session->form->process('ignore_first_line') eq 'yes' ? 1 : 0; - my @params = $self->session->form->param(); - my @fields_included = grep s/^file_contains-(.+)$/$1/, @params; - my @dup_keys = grep s/^check_duplicates-(.+)$/$1/, @params; - return $self->error([$no_action_taken_error,{ - type => 'general', - message => $i18n->get('import need dup key'), - }], 'www_importEvents') unless @dup_keys; - my @all_data_fields = @{ $self->getEventDataFields() }; # aref of sorted hrefs with name, label, required, type keys - my $sku_is_required = grep { $_ eq 'sku' } @dup_keys; - if (!$sku_is_required) { - for my $field (@all_data_fields) { $field->{required} = 0 if $field->{name} eq 'sku' } # not required here - } - my @sorted_fields_included = (); - for my $field (@all_data_fields) { - if (grep { $_ eq $field->{name} } @fields_included) { - push @sorted_fields_included, $field->{name}; - } - } - my @missing_required_fields = (); - for my $field (grep $_->{required}, @all_data_fields) { - if (!grep { $_ eq $field->{name} } @fields_included) { - push @missing_required_fields, $field->{label}; - } - } - if (@missing_required_fields) { - return $self->error([$no_action_taken_error,{ - type => 'general', - message => $i18n->get('check required fields')."@missing_required_fields", - }], - 'www_importEvents', - ); - } - - # we sanity check all of the input before processing any of it - # check all records for required fields and field count - my $errors = []; - my $prerequisites_href = {}; - if (grep /^prerequisiteId$/, @fields_included) { - $prerequisites_href = $self->session->db->buildHashRef(" SELECT name, prerequisiteId FROM EventManagementSystem_prerequisites"); - } - my $templates_href = {}; - if (grep /^templateId$/, @fields_included) { - $templates_href = $self->session->db->buildHashRef(<<""); - SELECT assetData.title, template.assetId - FROM template - LEFT JOIN assetData - ON assetData.assetId = template.assetId - AND assetData.revisionDate = template.revisionDate - - } - my %approved_values = ( Approved => 1, Denied => 0, Pending => -1, Cancelled => -2); - my $meta_fields_aref = $self->getEventMetaFields; - my %meta_fields = (); - @meta_fields{map {$_->{fieldId}} @$meta_fields_aref} = @$meta_fields_aref; # get them keyed by fieldId - my $first_line = 1; - my $before_check_time = time; - while (my $line = do { local $/ = "\n"; <$fh> }) { - if ($first_line and $ignore_first_line) { - $first_line = 0; - next; - } - - # line is blank - skip it, no error - next if $line =~ /^[\s\r\n]*$/; - - # parse the line - if (!$csv->parse($line)) { - my $error_input = $csv->error_input; - push @$errors,{ - type => 'general', # "There was an error processing this input: '$line'" - message => sprintf $i18n->get('import record parse error'), - $fh->input_line_number - $ignore_first_line, $error_input, - }; - if (@$errors >= $max_errors) { - return $self->error($errors, 'www_importEvents'); - } - next; - } - - my @columns = $csv->fields(); - - # check the field count - if (@columns != @fields_included) { - push @$errors,{ - type => 'general', - message => sprintf $i18n->get('field count mismatch'), $fh->input_line_number-$ignore_first_line, scalar @columns, scalar @fields_included, - }; - if (@$errors >= $max_errors) { - return $self->error($errors, 'www_importEvents'); - } - next; - } - # check the required fields - my %data = map { $sorted_fields_included[$_], $columns[$_] } 0..$#columns; - $data{sku} ||= 'do not check this here - we will create one later if necessary' unless $sku_is_required; - my $new_errors = $self->checkRequiredFields(\%data, $fh->input_line_number-$ignore_first_line); - if (@$new_errors) { - push @$errors, @$new_errors; - return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; - } - #check that approved, if present, is a good value - if (exists $data{approved} && ! grep { lc $_ eq lc $data{approved} } %approved_values) { - push @$errors, { - type => 'general', - message => sprintf $i18n->get('import invalid status'), $fh->input_line_number-$ignore_first_line, $data{approved}, - }; - return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; - } - #check that prerequisiteId, if present, is a good value - if (exists $data{prerequisiteId} && !exists $prerequisites_href->{$data{prerequisiteId}}) { - push @$errors, { - type => 'general', - message => sprintf $i18n->get('import invalid prereq'), $fh->input_line_number-$ignore_first_line, $data{prerequisiteId}, - }; - return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; - } - #check that templateId, if present, is a good value - if (exists $data{templateId} && !exists $templates_href->{$data{templateId}}) { - push @$errors, { - type => 'general', - message => sprintf $i18n->get('import invalid template'), $fh->input_line_number-$ignore_first_line, $data{templateId}, - }; - return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors >= $max_errors; - } - } - my $after_check_time = time; - - # errors? output them instead of proceeding with the import - return $self->error([$no_action_taken_error,@$errors], 'www_importEvents') if @$errors; - - # organize our existing events by duplicate keys - my %duplicate_events = (); - for my $event_href (@{ $self->getAllStdEventDetails }) { - my $event_meta_data_href = $self->getEventMetaDataFields($event_href->{productId}); - my $dup_key = join '|', map { /^metadata_(.+)/ ? $event_meta_data_href->{$1}->{fieldData} : $event_href->{$_} } @dup_keys; - $duplicate_events{$dup_key} = $event_href->{productId}; - } - - # input is deemed sane - time to process it - my $total_lines = $fh->input_line_number; - seek $fh, 0, 0; # start of the file, again - my %all_data_fields; - @all_data_fields{map $_->{name}, @all_data_fields} = @all_data_fields; # by name - my $existing_events_aref = $self->getAllStdEventDetails; - $first_line = 1; - my @skipped = (); - my @overwritten = (); - my @blank_lines = (); - my $before_process_time = time; - while (my $line = do { local $/ = "\n"; <$fh> }) { - if ($first_line and $ignore_first_line) { - $first_line = 0; - next; - } - - # line is blank - skip it, no error - if ($line =~ /^[\s\r\n]*$/) { - push @blank_lines, $fh->input_line_number - $ignore_first_line - $total_lines; # the record number - next; - } - - # parse the line - if (!$csv->parse($line)) { - my $error_input = $csv->error_input; - push @$errors,{ - type => 'general', # "There was an error processing this input: '$line'" - message => sprintf $i18n->get('import line parse error'), $error_input, $fh->input_line_number - $ignore_first_line - $total_lines, - }; - # this should "never happen" (TM) - return $self->error($errors, 'www_importEvents'); - } - - my @columns = $csv->fields(); - my %data = map { $sorted_fields_included[$_], $columns[$_] } 0..$#columns; - - # get the data in the form in which it will be compared to existing events - # to find dups, as well as how we'll store it in the db - for my $key (keys %data) { - if ($key eq 'approved') { - $data{$key} = $approved_values{$data{$key}}; - } - else { - my $method = $all_data_fields{$key}->{type}; - if ($method =~ /^(?:hidden|check|select)List$/i) { - $data{$key} = $self->session->form->$method(undef, split /;/, $data{$key}); - } - else { - $data{$key} = $self->session->form->$method(undef, $data{$key}); - } - } - } - - # store it or skip it - my $is_new = 1; - my $this_dup_key = join '|', map $data{$_}, @dup_keys; - my $product_id = "new"; - if (exists $duplicate_events{$this_dup_key}) { - if ($skip_duplicates) { - push @skipped, $fh->input_line_number - $ignore_first_line - $total_lines; # the record number - next; - } - push @overwritten, $fh->input_line_number - $ignore_first_line - $total_lines; # the record number - $is_new = 0; - $product_id = $duplicate_events{$this_dup_key}; # overwrite this product_id - # TODO load everything for this product_id so that we only overwrite the fields that are in the CSV file - } - - # reasonable defaults? - $data{sku} = $self->session->id->generate unless exists $data{sku}; - $data{approved} = $approved_values{Pending} unless exists $data{approved}; - - # store data in the EMS_products table - my $ems_products_href = { - productId => $product_id, - startDate => $data{startDate}, - endDate => $data{endDate}, - maximumAttendees => $data{maximumAttendees}, - approved => $data{approved}, - prerequisiteId => $prerequisites_href->{$data{prerequisiteId}}, - #passId => '', # NULL for these - there's no way to import them - #imageId => '', - #passType => '', - }; - my $pid = $self->setCollateral("EventManagementSystem_products", "productId",$ems_products_href,1,1); - - # store data in EMS_metaData - my @meta_fields = grep { $_->{name} =~ /^metadata_/ } @all_data_fields; - foreach my $field (@meta_fields) { - $field->{name} =~ /^metadata_(.+)$/; - my $field_id = $1; - my $data = $data{$field->{name}}; - my $sql = "insert into EMSEventMetaData values (". - $self->session->db->quoteAndJoin([$field_id, $pid, $data]). - ") on duplicate key update fieldData=". - $self->session->db->quote($data); - $self->session->db->write($sql); - } - - # store data in products - my $data_href = { - productId => $pid, - title => $data{title}, - description => $data{description}, - price => $data{price}, - useSalesTax => ($data{useSalesTax}||0), - weight => ($data{weight}||0), - sku => $data{sku}, - skuTemplate => ($data{skuTemplate}||''), - templateId => ($templates_href->{$data{templateId}}||''), - }; - if ($is_new) { - $self->session->db->setRow("products", "productId", $data_href, $pid); - $duplicate_events{$this_dup_key} = $product_id; - } - else { - $self->session->db->setRow("products", "productId", $data_href); - } - } - my $after_process_time = time; - - # acknowledge success - &error will work fine - with the number of records processed/imported/skipped/overwritten - my $processed_count = $fh->input_line_number - $ignore_first_line - $total_lines; - my $other_count = $skip_duplicates ? @skipped : @overwritten; - my $imported_count = $processed_count - $other_count - @blank_lines; - my $what_done = $i18n->get($skip_duplicates ? 'skipped' : 'overwritten'); - my $message = sprintf $i18n->get('import ok'), $processed_count, $imported_count, scalar(@blank_lines), $other_count, $what_done; - my $html = "

  • $message
  • "; - $html .= join '', map "
  • ".sprintf($i18n->get('import blank line'),$_)."
  • ", @blank_lines; - $html .= join '', map "
  • ".sprintf($i18n->get('import other line'),$_,$what_done)."
  • ", ($skip_duplicates ? @skipped : @overwritten); - - my $total_time = $after_process_time - $start_time; - my $prep_time = $before_check_time - $start_time; - my $check_time = $after_check_time - $before_check_time; - my $prep_time2 = $before_process_time - $after_check_time; - my $process_time = $after_process_time - $before_process_time; - my $time_block = <<""; -
    - total time: $total_time
    - prep time: $prep_time
    - check time: $check_time
    - prep2 time: $prep_time2
    - process time: $process_time
    -
    - - return $self->_acWrapper("$time_block", $i18n->get('import events')); -} - - 1; diff --git a/lib/WebGUI/Shop/Admin.pm b/lib/WebGUI/Shop/Admin.pm index 9c4aa5a72..b6590b5f7 100644 --- a/lib/WebGUI/Shop/Admin.pm +++ b/lib/WebGUI/Shop/Admin.pm @@ -69,7 +69,6 @@ sub getAdminConsole { $ac->addSubmenuItem($url->page("shop=transaction;method=manage"), $i18n->get("transactions")); $ac->addSubmenuItem($url->page("shop=vendor;method=manage"), $i18n->get("vendors")); $ac->addSubmenuItem($url->page("shop=credit;method=manage"), $i18n->get("in shop credit")); - $ac->addSubmenuItem($url->page("shop=products;method=manage"), $i18n->get("products")); return $ac; }