# # ARSperl - An ARS v2-v4 / Perl5 Integration Kit # # Copyright (C) 1995-1999 Joel Murphy, jmurphy@acsu.buffalo.edu # Jeff Murphy, jcmurphy@acsu.buffalo.edu # # This program is free software; you can redistribute it and/or modify # it under the terms as Perl itself. # # Refer to the file called "Artistic" that accompanies the source distribution # of ARSperl (or the one that accompanies the source distribution of Perl # itself) for a full description. # # Official Home Page: # http://www.arsperl.org/ # # Mailing List (must be subscribed to post): # See URL above. # package ARS::form; require Carp; # new ARS::form(-form => name, -vui => view, -connection => connection) sub new { my ($class, $self) = (shift, {}); my ($b) = bless($self, $class); my ($form, $vui, $connection) = ARS::rearrange([FORM,VUI,CONNECTION],@_); $connection->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: new ARS::form(-form => name, -vui => vui, -connection => connection)\nform and connection parameters are required." ) if(!defined($form) || !defined($connection)); $vui = "Default Admin View" unless defined $vui; $self->{'form'} = $form; $self->{'connection'} = $connection; $self->{'vui'} = $vui; my %f = ARS::ars_GetFieldTable($connection->{'ctrl'}, $form); $connection->tryCatch(); $self->{'fields'} = \%f; my %rev = reverse %f; # convenient $self->{'fields_rev'} = \%rev; my(%t, %enums); foreach (keys %f) { print "caching field: $_\n" if $self->{'connection'}->{'.debug'}; my $fv = ARS::ars_GetField($self->{'connection'}->{'ctrl'}, $self->{'form'}, $f{$_}); $connection->tryCatch(); $t{$_} = $fv->{'dataType'}; print "\tdatatype: $t{$_}\n" if $self->{'connection'}->{'.debug'}; if ($fv->{'dataType'} eq "enum") { if (ref($fv->{'limit'}->{'enumLimits'}) eq "ARRAY") { $enums{$_} = [@{$fv->{'limit'}->{'enumLimits'}}]; } elsif (exists $fv->{'limit'}->{'enumLimits'}->{'regularList'}) { $enums{$_} = [@{$fv->{'limit'}->{'enumLimits'}->{'regularList'}}]; } else { print "Sorry. I'm not sure what to do with non-regularLists of enums.\n"; print "(this enum is type \"", keys %{$fv->{'limit'}->{'enumLimits'}}, "\")\n"; print "listStyle = ", $fv->{'limit'}->{'enumLimits'}->{'listStyle'}, "\n"; die; } } } $self->{'fieldtypes'} = \%t; $self->{'fieldEnumValues'} = \%enums; return $b; } sub DESTROY { } # getEnumValues(-field => "fieldname") sub getEnumValues { my ($this) = shift; my ($field) = ARS::rearrange([FIELD], @_); if(ref($this->{'fieldEnumValues'}->{$field}) eq "ARRAY") { return @{$this->{'fieldEnumValues'}->{$field}}; } $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81006, "field $field is not an enumeration field."); $this->{'connection'}->tryCatch(); return undef; } # query(-query => "qualifier", -maxhits => 100, -firstretrieve => 0) sub query { my ($this) = shift; my ($query, $maxhits, $firstretr) = ARS::rearrange([QUERY,MAXHITS,FIRSTRETRIEVE], @_); $query = "(1 = 1)" unless defined($query); $maxhits = 0 unless defined($maxhits); $firstretr = 0 unless defined($firstretr); if($this->{'connection'}->{'.debug'}) { print "form->query(".$this->{'form'}.", $query, ".$this->{'vui'}.")\n"; } $this->{'qualifier'} = ARS::ars_LoadQualifier($this->{'connection'}->{'ctrl'}, $this->{'form'}, $query, $this->{'vui'}); $this->{'connection'}->tryCatch(); my @sortOrder = (); if(defined($this->{'sortOrder'}) && ref($this->{'sortOrder'}) eq "ARRAY") { @sortOrder = @{$this->{'sortOrder'}}; } my @matches = ARS::ars_GetListEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $this->{'qualifier'}, $maxhits, $firstretr, @sortOrder); my(@mids, @mdescs); for(my $i = 0; $i <= $#matches ; $i += 2) { push @mids, $matches[$i]; push @mdescs, $matches[$i+1]; } $this->{'matches'} = \@mids; $this->{'querylist'} = \@mdescs; return @mids; } # getFieldID(-field => name) sub getFieldID { my $this = shift; my ($name) = ARS::rearrange([FIELD], @_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->getFieldID(-field => name)\nname parameter is required.") unless defined($name); if(!defined($this->{'fields'}->{$name})) { $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81001, "field '$name' not in view: ".$this->{'vui'}."\n" ); } return $this->{'fields'}->{$name} if(defined($name)); } # getFieldName(-id => id) sub getFieldName { my $this = shift; my ($id) = ARS::rearrange([ID], @_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->getFieldName(-id => id)\nid parameter required." ) unless defined($id); return $this->{'fields_rev'}->{$id} if defined($this->{'fields_rev'}->{$id}); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81002, "field id '$id' not available on form: ".$this->{'form'}."" ); } # getFieldType(-field => name, -id => id) sub getFieldType { my $this = shift; my ($name, $id) = ARS::rearrange([FIELD,ID], @_); if(!defined($name) && !defined($id)) { $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->getFieldType(-field => name, -id => id)\none of the parameters must be specified."); } if(defined($name) && !defined($this->{'fieldtypes'}->{$name})) { $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81001, "field '$name' not in view: ".$this->{'vui'}."\n" ); } #print "getFieldType($name, $id)\n" if $this->{'connection'}->{'.debug'}; return $this->{'fieldtypes'}->{$name} if defined($name); # they didnt give us a name, but instead gave us an id. look up the # name and return the type. if(defined($id)) { my $n = $this->getFieldName(-id => $id); return $this->{'fieldtypes'}->{$n}; } $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81003, "couldn't determine dataType for field."); } # delete(-entry => id) sub delete { my $this = shift; my ($id) = ARS::rearrange([ENTRY],@_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->delete(-entry => id)\nentry parameter is required.") unless defined($id); my (@d); # allow the user to delete multiple entries in one shot if(ref($id) eq "ARRAY") { @d = @{$id}; } else { push @d, $id; } foreach (@d) { ARS::ars_DeleteEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $_); $this->{'connection'}->tryCatch(); } } # merge(-type => mergeType, -values => { field1 => value1, ... }) sub merge { my ($this) = shift; my ($type, $vals) = ARS::rearrange([TYPE,[VALUE,VALUES]],@_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->merge(-type => mergeType, -values => { field1 => value1, ... })\ntype and values parameters are required.") unless(defined($type) && defined($vals)); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->merge(-type => mergeType, -values => { field1 => value1, ... })\nvalues parameter must be HASH ref.") unless ref($vals) eq "HASH"; my (%realmap); # as we work thru each value, we need to perform translations for # enum fields. foreach (keys %{$vals}) { my ($rv) = $this->value2internal(-field => $_, -value => $vals->{$_}); #print "[form->merge] realval for $_ = $rv\n"; $realmap{$this->getFieldID($_)} = $rv; } print "merge/type=$type\n" if $this->{'connection'}->{'.debug'}; my ($rv) = ARS::ars_MergeEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $type, %realmap); $this->{'connection'}->tryCatch(); # if ($rv is "") and there are no FATAL or ERRORs and # an entry id was in our vals realmap hash, then this was # a successful "OVERWRITE" or "MERGE" operation. lets return # the entry-id. if $rv is no "", then whatever operation this # was - it was successful. if it's "" and we had no entry-id # specified - or we did have one specified and there are FATALs # or ERRORs then something is wrong. complicated, but that's how # the C API works. we try to make the OO layer a little nicer for # the end user. if(($rv eq "") && defined($realmap{1})) { if(!$this->{'connection'}->hasFatals() && !$this->{'connection'}->hasErrors()) { $rv = $realmap{1}; } } return $rv; } # set(-entry => id, -gettime => tstamp, -values => { field1 => value1, ... }) sub set { my ($this) = shift; my ($entry,$gettime,$vals) = ARS::rearrange([ENTRY,GETTIME,[VALUE,VALUES]],@_); $gettime = 0 unless defined($gettime); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->set(-entry => id, -gettime => tstamp, -values => { field1 => value1, ... })\nentry and values parameters are required." ) unless (defined($vals) && defined($entry)); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->set(-entry => id, -values => { field1 => value1, ... })\nvalues parameter must be HASH ref.") unless ref($vals) eq "HASH"; my (%realmap); # as we work thru each value, we need to perform translations for # enum fields. foreach (keys %{$vals}) { my ($rv) = $this->value2internal(-field => $_, -value => $vals->{$_}); #print "realval for $_ = $rv\n"; $realmap{$this->getFieldID($_)} = $rv; } my ($rv) = ARS::ars_SetEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $entry, $gettime, %realmap); $this->{'connection'}->tryCatch(); return $rv; } # value2internal(-field => name, -value => value) sub value2internal { my ($this) = shift; my ($f, $v) = ARS::rearrange([FIELD,VALUE], @_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->value2internal(-field => name, -value => value)\nfield parameter is required.") unless (defined($f)); return $v unless defined $v; my ($t) = $this->getFieldType($f); print "value2internal($f, $v) type=$t\n" if $this->{'connection'}->{'.debug'}; # translate an text value into an enumeration number if this # field is an enumeration field and we havent been passed a number # to begin with. if(($t eq "enum") && ($v !~ /^\d+$/)) { if(!defined($this->{'fieldEnumValues'}->{$f})) { $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81004, "[1] unable to translate enumeration value for field '$f'"); } for($i = 0 ; $i <= $#{$this->{'fieldEnumValues'}->{$f}} ; $i++) { return $i if $this->{'fieldEnumValues'}->{$f}->[$i] eq $v; } $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81004, "[2] unable to translate enumeration value for field '$f'"); } # we don't need translation.. return $v; } # internal2value(-field => name, -id => id, -value => value) sub internal2value { my ($this) = shift; my ($f, $id, $v) = ARS::rearrange([FIELD,ID,VALUE], @_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->internal2value(-field => name, -id => id, -value => value)\nid or field parameter are required.") unless (defined($f) || defined($id)); $f = $this->getFieldName(-id => $id) unless defined($f); my ($t) = $this->getFieldType($f); print "internal2value($f, $v) type=$t\n" if $this->{'connection'}->{'.debug'}; # translate an enumeration value into a text value if($t eq "enum") { # if the field doesnt exist in our cache, or if the # enumeration value exceeds the known list of enumerations, # barf. return undef unless defined $v; if(!defined($this->{'fieldEnumValues'}->{$f}) || ($#{$this->{'fieldEnumValues'}->{$f}} < $v) ) { $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81004, "[1] unable to translate enumeration value for field '$f'" ); } return $this->{'fieldEnumValues'}->{$f}->[$v] } # we don't need translation.. return $v; } # create(-values => { field1 => value1, ... }) sub create { my ($this) = shift; my ($vals) = ARS::rearrange([[VALUES,VALUE]],@_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->create(-values => { field1 => value1, ... })\nvalues parameter is required.") unless defined($vals); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->create(-values => { field1 => value1, ... })\nvalues parameter must be HASH ref.") unless ref($vals) eq "HASH"; my (%realmap); print "Mapping field information.\n" if $self->{'connection'}->{'.debug'}; foreach (keys %{$vals}) { my ($rv) = $this->value2internal(-field => $_, -value => $vals->{$_}); #print "realval for $_ = $rv\n"; $realmap{$this->getFieldID($_)} = $rv; } print "calling ars_CreateEntry..\n" if $self->{'connection'}->{'.debug'}; my ($id) = ARS::ars_CreateEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, %realmap); print "calling tryCatch()..\n" if $self->{'connection'}->{'.debug'}; $this->{'connection'}->tryCatch(); return $id; } # get(-entry => entryid, -fields => [ field1, field2 ]) sub get { my $this = shift; my ($eid, $fields) = ARS::rearrange([ENTRY,[FIELD,FIELDS]],@_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->get(-entry => entryid, -fields => [ field1, field2, ... ])\nentry parameter is required.") unless defined($eid); my (@fieldlist) = (); my ($allfields) = 1; if(defined($fields)) { $allfields = 0; foreach (@{$fields}) { push @fieldlist, $this->getFieldID($_); } } # what we want to do is: retrieve all of the values, but for # certain datatypes (attachments) we want to insert # an object instead of the field value. for enum types, # we want to decode the value. #print "("; print $this->{'form'}; print ", $eid, @fieldlist)\n"; my @v; if($allfields == 0) { @v = ARS::ars_GetEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $eid, @fieldlist); } else { @v = ARS::ars_GetEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $eid); } my @rv; for(my $i = 0 ; $i <= $#v ; $i += 2) { if($this->getFieldType(-id => $v[$i]) eq "attach") { push @rv, $v[$i+1]; # "attach"; } elsif($this->getFieldType(-id => $v[$i]) eq "enum") { push @rv, $this->internal2value(-id => $v[$i], -value => $v[$i+1]); } else { push @rv, $v[$i+1]; } } return @rv unless ($#rv == 0); return $rv[0]; } # getAsHash(-entry => entryid, -fields => [field1, field2, ...]) sub getAsHash { my $this = shift; my ($eid, $fields) = ARS::rearrange([ENTRY,[FIELD,FIELDS]],@_); $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: form->getAsHash(-entry => entryid, -fields => [ field1, field2, ... ])\nentry parameter is required.") unless defined($eid); my (@fieldlist) = (); my ($allfields) = 1; if(defined($fields)) { $allfields = 0; foreach (@{$fields}) { push @fieldlist, $this->getFieldID($_); } } my @v; if($allfields == 0) { @v = ARS::ars_GetEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $eid, @fieldlist); } else { @v = ARS::ars_GetEntry($this->{'connection'}->{'ctrl'}, $this->{'form'}, $eid); } for(my $i = 0 ; $i <= $#v ; $i += 2) { if($this->getFieldType(-id => $v[$i]) eq "attach") { #$v[$i+1] = "attach"; } elsif($this->getFieldType(-id => $v[$i]) eq "enum") { $v[$i+1] = $this->internal2value(-id => $v[$i], -value => $v[$i+1]); } $v[$i] = $this->getFieldName(-id => $v[$i]); } return @v; } # getAttachment(-entry => eid, -field => fieldname, -file => filename) # if file isnt specified, the attachment is returned "in core" sub getAttachment { my $this = shift; my ($eid, $field, $file) = ARS::rearrange([ENTRY,FIELD,FILE],@_); if(!defined($eid) && !defined($field)) { $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: getAttachment(-entry => eid, -field => fieldname, -file => filename)\nentry and field parameters are required."); } if(defined($file)) { my $rv = ARS::ars_GetEntryBLOB($this->{'connection'}->{'ctrl'}, $this->{'form'}, $eid, $this->getFieldID($field), ARS::AR_LOC_FILENAME, $file); $this->{'connection'}->tryCatch(); return $rv; } return ARS::ars_GetEntryBLOB($this->{'connection'}->{'ctrl'}, $this->{'form'}, $eid, $this->getFieldID($field), ARS::AR_LOC_BUFFER); } #setSort(... ) sub setSort { my $this = shift; if(($#_+1) % 2 == 1){ $this->{'connection'}->pushMessage(&ARS::AR_RETURN_ERROR, 81000, "usage: setSort(...)\nMust have an even number of parameters. (nparm = $#_)"); } my (@t) = @_; for(my $i = 0 ; $i <= $#t ; $i+=2) { $t[$i] = $this->getFieldID($t[$i]); } $this->{'sortOrder'} = \@t; } 1;