1 package C4::CourseReserves;
3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16 # Suite 330, Boston, MA 02111-1307 USA
23 use C4::Items qw(GetItem ModItem);
24 use C4::Biblio qw(GetBiblioFromItemNumber);
25 use C4::Circulation qw(GetOpenIssue);
27 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG @FIELDS);
54 @FIELDS = ( 'itype', 'ccode', 'holdingbranch', 'location' );
59 C4::CourseReserves - Koha course reserves module
63 use C4::CourseReserves;
67 This module deals with course reserves.
73 $course = GetCourse( $course_id );
79 warn whoami() . "( $course_id )" if $DEBUG;
81 my $query = "SELECT * FROM courses WHERE course_id = ?";
82 my $dbh = C4::Context->dbh;
83 my $sth = $dbh->prepare($query);
84 $sth->execute($course_id);
86 my $course = $sth->fetchrow_hashref();
89 SELECT b.* FROM course_instructors ci
90 LEFT JOIN borrowers b ON ( ci.borrowernumber = b.borrowernumber )
93 $sth = $dbh->prepare($query);
94 $sth->execute($course_id);
95 $course->{'instructors'} = $sth->fetchall_arrayref( {} );
102 ModCourse( [ course_id => $id ] [, course_name => $course_name ] [etc...] );
108 warn identify_myself(%params) if $DEBUG;
110 my $dbh = C4::Context->dbh;
113 if ( defined $params{'course_id'} ) {
114 $course_id = $params{'course_id'};
115 delete $params{'course_id'};
123 $query .= ($course_id) ? ' UPDATE ' : ' INSERT ';
124 $query .= ' courses SET ';
126 foreach my $key ( keys %params ) {
127 push( @query_keys, "$key=?" );
128 push( @query_values, $params{$key} );
130 $query .= join( ',', @query_keys );
133 $query .= " WHERE course_id = ?";
134 push( @query_values, $course_id );
137 $dbh->do( $query, undef, @query_values );
139 $course_id = $course_id
140 || $dbh->last_insert_id( undef, undef, 'courses', 'course_id' );
142 EnableOrDisableCourseItems(
143 course_id => $course_id,
144 enabled => $params{'enabled'}
152 @courses = GetCourses( [ fieldname => $value ] [, fieldname2 => $value2 ] [etc...] );
158 warn identify_myself(%params) if $DEBUG;
166 LEFT JOIN course_reserves ON course_reserves.course_id = courses.course_id
167 LEFT JOIN course_items ON course_items.ci_id = course_reserves.ci_id
170 if ( keys %params ) {
174 foreach my $key ( keys %params ) {
175 push( @query_keys, " $key LIKE ? " );
176 push( @query_values, $params{$key} );
179 $query .= join( ' AND ', @query_keys );
182 $query .= " GROUP BY courses.course_id ";
184 my $dbh = C4::Context->dbh;
185 my $sth = $dbh->prepare($query);
186 $sth->execute(@query_values);
188 my $courses = $sth->fetchall_arrayref( {} );
190 foreach my $c (@$courses) {
191 $c->{'instructors'} = GetCourseInstructors( $c->{'course_id'} );
199 DelCourse( $course_id );
204 my ($course_id) = @_;
206 my $course_reserves = GetCourseReserves( course_id => $course_id );
208 foreach my $res (@$course_reserves) {
209 DelCourseReserve( cr_id => $res->{'cr_id'} );
213 DELETE FROM course_instructors
216 C4::Context->dbh->do( $query, undef, $course_id );
222 C4::Context->dbh->do( $query, undef, $course_id );
225 =head2 EnableOrDisableCourseItems
227 EnableOrDisableCourseItems( course_id => $course_id, enabled => $enabled );
229 For each item on reserve for this course,
230 if the course item has no active course reserves,
231 swap the fields for the item to make it 'normal'
234 enabled => 'yes' to enable course items
235 enabled => 'no' to disable course items
239 sub EnableOrDisableCourseItems {
241 warn identify_myself(%params) if $DEBUG;
243 my $course_id = $params{'course_id'};
244 my $enabled = $params{'enabled'} || 0;
246 my $lookfor = ( $enabled eq 'yes' ) ? 'no' : 'yes';
248 return unless ( $course_id && $enabled );
249 return unless ( $enabled eq 'yes' || $enabled eq 'no' );
251 my $course_reserves = GetCourseReserves( course_id => $course_id );
253 if ( $enabled eq 'yes' ) {
254 foreach my $course_reserve (@$course_reserves) {
256 CountCourseReservesForItem(
257 ci_id => $course_reserve->{'ci_id'},
262 EnableOrDisableCourseItem(
263 ci_id => $course_reserve->{'ci_id'},
269 if ( $enabled eq 'no' ) {
270 foreach my $course_reserve (@$course_reserves) {
272 CountCourseReservesForItem(
273 ci_id => $course_reserve->{'ci_id'},
278 EnableOrDisableCourseItem(
279 ci_id => $course_reserve->{'ci_id'},
287 =head2 EnableOrDisableCourseItem
289 EnableOrDisableCourseItem( ci_id => $ci_id, enabled => $enabled );
291 enabled => 'yes' to enable course items
292 enabled => 'no' to disable course items
296 sub EnableOrDisableCourseItem {
298 warn identify_myself(%params) if $DEBUG;
300 my $ci_id = $params{'ci_id'};
301 my $enabled = $params{'enabled'};
303 return unless ( $ci_id && $enabled );
304 return unless ( $enabled eq 'yes' || $enabled eq 'no' );
306 my $course_item = GetCourseItem( ci_id => $ci_id );
308 ## We don't want to 'enable' an already enabled item,
309 ## or disable and already disabled item,
310 ## as that would cause the fields to swap
311 if ( $course_item->{'enabled'} ne $enabled ) {
312 _SwapAllFields($ci_id);
320 C4::Context->dbh->do( $query, undef, $enabled, $ci_id );
326 =head2 GetCourseInstructors
328 @$borrowers = GetCourseInstructors( $course_id );
332 sub GetCourseInstructors {
333 my ($course_id) = @_;
334 warn "C4::CourseReserves::GetCourseInstructors( $course_id )"
338 SELECT * FROM borrowers
339 RIGHT JOIN course_instructors ON ( course_instructors.borrowernumber = borrowers.borrowernumber )
340 WHERE course_instructors.course_id = ?
343 my $dbh = C4::Context->dbh;
344 my $sth = $dbh->prepare($query);
345 $sth->execute($course_id);
347 return $sth->fetchall_arrayref( {} );
350 =head2 ModCourseInstructors
352 ModCourseInstructors( mode => $mode, course_id => $course_id, [ cardnumbers => $cardnumbers ] OR [ borrowernumbers => $borrowernumbers );
354 $mode can be 'replace', 'add', or 'delete'
356 $cardnumbers and $borrowernumbers are both references to arrays
358 Use either cardnumbers or borrowernumber, but not both.
362 sub ModCourseInstructors {
364 warn identify_myself(%params) if $DEBUG;
366 my $course_id = $params{'course_id'};
367 my $mode = $params{'mode'};
368 my $cardnumbers = $params{'cardnumbers'};
369 my $borrowernumbers = $params{'borrowernumbers'};
371 return unless ($course_id);
373 unless ( $mode eq 'replace'
375 || $mode eq 'delete' );
376 return unless ( $cardnumbers || $borrowernumbers );
377 return if ( $cardnumbers && $borrowernumbers );
379 my @cardnumbers = @$cardnumbers if ( ref($cardnumbers) eq 'ARRAY' );
380 my @borrowernumbers = @$borrowernumbers
381 if ( ref($borrowernumbers) eq 'ARRAY' );
383 my $field = (@cardnumbers) ? 'cardnumber' : 'borrowernumber';
384 my @fields = (@cardnumbers) ? @cardnumbers : @borrowernumbers;
385 my $placeholders = join( ',', ('?') x scalar @fields );
387 my $dbh = C4::Context->dbh;
389 $dbh->do( "DELETE FROM course_instructors WHERE course_id = ?",
391 if ( $mode eq 'replace' );
395 if ( $mode eq 'add' || $mode eq 'replace' ) {
397 INSERT INTO course_instructors ( course_id, borrowernumber )
398 SELECT ?, borrowernumber
400 WHERE $field IN ( $placeholders )
405 DELETE FROM course_instructors
407 AND borrowernumber IN (
408 SELECT borrowernumber FROM borrowers WHERE $field IN ( $placeholders )
413 my $sth = $dbh->prepare($query);
415 $sth->execute( $course_id, @fields ) if (@fields);
418 =head2 GetCourseItem {
420 $course_item = GetCourseItem( itemnumber => $itemnumber [, ci_id => $ci_id );
426 warn identify_myself(%params) if $DEBUG;
428 my $ci_id = $params{'ci_id'};
429 my $itemnumber = $params{'itemnumber'};
431 return unless ( $itemnumber || $ci_id );
433 my $field = ($itemnumber) ? 'itemnumber' : 'ci_id';
434 my $value = ($itemnumber) ? $itemnumber : $ci_id;
436 my $query = "SELECT * FROM course_items WHERE $field = ?";
437 my $dbh = C4::Context->dbh;
438 my $sth = $dbh->prepare($query);
439 $sth->execute($value);
441 my $course_item = $sth->fetchrow_hashref();
444 $query = "SELECT * FROM course_reserves WHERE ci_id = ?";
445 $sth = $dbh->prepare($query);
446 $sth->execute( $course_item->{'ci_id'} );
447 my $course_reserves = $sth->fetchall_arrayref( {} );
449 $course_item->{'course_reserves'} = $course_reserves
450 if ($course_reserves);
455 =head2 ModCourseItem {
457 ModCourseItem( %params );
459 Creates or modifies an existing course item.
465 warn identify_myself(%params) if $DEBUG;
467 my $itemnumber = $params{'itemnumber'};
468 my $itype = $params{'itype'};
469 my $ccode = $params{'ccode'};
470 my $holdingbranch = $params{'holdingbranch'};
471 my $location = $params{'location'};
473 return unless ($itemnumber);
475 my $course_item = GetCourseItem( itemnumber => $itemnumber );
480 $ci_id = $course_item->{'ci_id'};
484 course_item => $course_item,
489 $ci_id = _AddCourseItem(%params);
496 =head2 _AddCourseItem
498 my $ci_id = _AddCourseItem( %params );
504 warn identify_myself(%params) if $DEBUG;
506 my ( @fields, @values );
508 push( @fields, 'itemnumber = ?' );
509 push( @values, $params{'itemnumber'} );
513 push( @fields, "$_ = ?" );
514 push( @values, $params{$_} );
518 my $query = "INSERT INTO course_items SET " . join( ',', @fields );
519 my $dbh = C4::Context->dbh;
520 $dbh->do( $query, undef, @values );
522 my $ci_id = $dbh->last_insert_id( undef, undef, 'course_items', 'ci_id' );
527 =head2 _UpdateCourseItem
529 _UpdateCourseItem( %params );
533 sub _UpdateCourseItem {
535 warn identify_myself(%params) if $DEBUG;
537 my $ci_id = $params{'ci_id'};
538 my $course_item = $params{'course_item'};
539 my $itype = $params{'itype'};
540 my $ccode = $params{'ccode'};
541 my $holdingbranch = $params{'holdingbranch'};
542 my $location = $params{'location'};
544 return unless ( $ci_id || $course_item );
546 $course_item = GetCourseItem( ci_id => $ci_id )
547 unless ($course_item);
548 $ci_id = $course_item->{'ci_id'} unless ($ci_id);
550 ## Revert fields that had an 'original' value, but now don't
551 ## Update the item fields to the stored values from course_items
552 ## and then set those fields in course_items to NULL
553 my @fields_to_revert;
555 if ( !$params{$_} && $course_item->{$_} ) {
556 push( @fields_to_revert, $_ );
561 fields => \@fields_to_revert,
562 course_item => $course_item
563 ) if (@fields_to_revert);
565 ## Update fields that still have an original value, but it has changed
566 ## This necessitates only changing the current item values, as we still
567 ## have the original values stored in course_items
571 && $course_item->{$_}
572 && $params{$_} ne $course_item->{$_} )
574 $mod_params{$_} = $params{$_};
577 ModItem( \%mod_params, undef, $course_item->{'itemnumber'} );
579 ## Update fields that didn't have an original value, but now do
580 ## We must save the original value in course_items, and also
581 ## update the item fields to the new value
582 my $item = GetItem( $course_item->{'itemnumber'} );
586 if ( $params{$_} && !$course_item->{$_} ) {
587 $mod_params_new{$_} = $params{$_};
588 $mod_params_old{$_} = $item->{$_};
591 _ModStoredFields( 'ci_id' => $params{'ci_id'}, %mod_params_old );
592 ModItem( \%mod_params_new, undef, $course_item->{'itemnumber'} );
596 =head2 _ModStoredFields
598 _ModStoredFields( %params );
600 Updates the values for the 'original' fields in course_items
605 sub _ModStoredFields {
607 warn identify_myself(%params) if $DEBUG;
609 return unless ( $params{'ci_id'} );
611 my ( @fields_to_update, @values_to_update );
615 push( @fields_to_update, $_ );
616 push( @values_to_update, $params{$_} );
621 "UPDATE course_items SET "
622 . join( ',', map { "$_=?" } @fields_to_update )
623 . " WHERE ci_id = ?";
625 C4::Context->dbh->do( $query, undef, @values_to_update, $params{'ci_id'} )
626 if (@values_to_update);
632 _RevertFields( ci_id => $ci_id, fields => \@fields_to_revert );
638 warn identify_myself(%params) if $DEBUG;
640 my $ci_id = $params{'ci_id'};
641 my $course_item = $params{'course_item'};
642 my $fields = $params{'fields'};
643 my @fields = @$fields;
645 return unless ($ci_id);
647 $course_item = GetCourseItem( ci_id => $params{'ci_id'} )
648 unless ($course_item);
652 foreach my $field (@fields) {
654 if ( $field eq $_ && $course_item->{$_} ) {
655 $mod_item_params->{$_} = $course_item->{$_};
656 push( @fields_to_null, $_ );
660 ModItem( $mod_item_params, undef, $course_item->{'itemnumber'} );
663 "UPDATE course_items SET "
664 . join( ',', map { "$_=NULL" } @fields_to_null )
665 . " WHERE ci_id = ?";
667 C4::Context->dbh->do( $query, undef, $ci_id ) if (@fields_to_null);
670 =head2 _SwapAllFields
672 _SwapAllFields( $ci_id );
678 warn "C4::CourseReserves::_SwapFields( $ci_id )" if $DEBUG;
680 my $course_item = GetCourseItem( ci_id => $ci_id );
681 my $item = GetItem( $course_item->{'itemnumber'} );
683 my %course_item_fields;
686 if ( $course_item->{$_} ) {
687 $course_item_fields{$_} = $course_item->{$_};
688 $item_fields{$_} = $item->{$_};
692 ModItem( \%course_item_fields, undef, $course_item->{'itemnumber'} );
693 _ModStoredFields( %item_fields, ci_id => $ci_id );
696 =head2 GetCourseItems {
698 $course_items = GetCourseItems(
699 [course_id => $course_id]
700 [, itemnumber => $itemnumber ]
707 warn identify_myself(%params) if $DEBUG;
709 my $course_id = $params{'course_id'};
710 my $itemnumber = $params{'itemnumber'};
712 return unless ($course_id);
717 my $query = "SELECT * FROM course_items";
719 if ( keys %params ) {
723 foreach my $key ( keys %params ) {
724 push( @query_keys, " $key LIKE ? " );
725 push( @query_values, $params{$key} );
728 $query .= join( ' AND ', @query_keys );
731 my $dbh = C4::Context->dbh;
732 my $sth = $dbh->prepare($query);
733 $sth->execute(@query_values);
735 return $sth->fetchall_arrayref( {} );
738 =head2 DelCourseItem {
740 DelCourseItem( ci_id => $cr_id );
746 warn identify_myself(%params) if $DEBUG;
748 my $ci_id = $params{'ci_id'};
750 return unless ($ci_id);
752 _RevertFields( ci_id => $ci_id, fields => \@FIELDS );
755 DELETE FROM course_items
758 C4::Context->dbh->do( $query, undef, $ci_id );
761 =head2 GetCourseReserve {
763 $course_item = GetCourseReserve( %params );
767 sub GetCourseReserve {
769 warn identify_myself(%params) if $DEBUG;
771 my $cr_id = $params{'cr_id'};
772 my $course_id = $params{'course_id'};
773 my $ci_id = $params{'ci_id'};
775 return unless ( $cr_id || ( $course_id && $ci_id ) );
777 my $dbh = C4::Context->dbh;
782 SELECT * FROM course_reserves
785 $sth = $dbh->prepare($query);
786 $sth->execute($cr_id);
790 SELECT * FROM course_reserves
791 WHERE course_id = ? AND ci_id = ?
793 $sth = $dbh->prepare($query);
794 $sth->execute( $course_id, $ci_id );
797 my $course_reserve = $sth->fetchrow_hashref();
798 return $course_reserve;
801 =head2 ModCourseReserve
803 $id = ModCourseReserve( %params );
807 sub ModCourseReserve {
809 warn identify_myself(%params) if $DEBUG;
811 my $course_id = $params{'course_id'};
812 my $ci_id = $params{'ci_id'};
813 my $staff_note = $params{'staff_note'};
814 my $public_note = $params{'public_note'};
816 return unless ( $course_id && $ci_id );
819 GetCourseReserve( course_id => $course_id, ci_id => $ci_id );
822 my $dbh = C4::Context->dbh;
824 if ($course_reserve) {
825 $cr_id = $course_reserve->{'cr_id'};
828 UPDATE course_reserves
829 SET staff_note = ?, public_note = ?
832 $dbh->do( $query, undef, $staff_note, $public_note, $cr_id );
836 INSERT INTO course_reserves SET
842 $dbh->do( $query, undef, $course_id, $ci_id, $staff_note,
845 $dbh->last_insert_id( undef, undef, 'course_reserves', 'cr_id' );
848 my $course = GetCourse($course_id);
849 EnableOrDisableCourseItem(
850 ci_id => $params{'ci_id'},
851 enabled => $course->{'enabled'}
857 =head2 GetCourseReserves {
859 $course_reserves = GetCourseReserves( %params );
866 include_courses => 1,
870 sub GetCourseReserves {
872 warn identify_myself(%params) if $DEBUG;
874 my $course_id = $params{'course_id'};
875 my $ci_id = $params{'ci_id'};
876 my $include_items = $params{'include_items'};
877 my $include_count = $params{'include_count'};
878 my $include_courses = $params{'include_courses'};
880 return unless ( $course_id || $ci_id );
882 my $field = ($course_id) ? 'course_id' : 'ci_id';
883 my $value = ($course_id) ? $course_id : $ci_id;
886 SELECT cr.*, ci.itemnumber
887 FROM course_reserves cr, course_items ci
889 AND cr.ci_id = ci.ci_id
891 my $dbh = C4::Context->dbh;
892 my $sth = $dbh->prepare($query);
893 $sth->execute($value);
895 my $course_reserves = $sth->fetchall_arrayref( {} );
897 if ($include_items) {
898 foreach my $cr (@$course_reserves) {
899 $cr->{'course_item'} = GetCourseItem( ci_id => $cr->{'ci_id'} );
900 $cr->{'item'} = GetBiblioFromItemNumber( $cr->{'itemnumber'} );
901 $cr->{'issue'} = GetOpenIssue( $cr->{'itemnumber'} );
905 if ($include_count) {
906 foreach my $cr (@$course_reserves) {
907 $cr->{'reserves_count'} =
908 CountCourseReservesForItem( ci_id => $cr->{'ci_id'} );
912 if ($include_courses) {
913 foreach my $cr (@$course_reserves) {
915 GetCourses( itemnumber => $cr->{'itemnumber'} );
919 return $course_reserves;
922 =head2 DelCourseReserve {
924 DelCourseReserve( cr_id => $cr_id );
928 sub DelCourseReserve {
930 warn identify_myself(%params) if $DEBUG;
932 my $cr_id = $params{'cr_id'};
934 return unless ($cr_id);
936 my $dbh = C4::Context->dbh;
938 my $course_reserve = GetCourseReserve( cr_id => $cr_id );
941 DELETE FROM course_reserves
944 $dbh->do( $query, undef, $cr_id );
946 ## If there are no other course reserves for this item
947 ## delete the course_item as well
948 unless ( CountCourseReservesForItem( ci_id => $course_reserve->{'ci_id'} ) )
950 DelCourseItem( ci_id => $course_reserve->{'ci_id'} );
955 =head2 GetReservesInfo
957 my $arrayref = GetItemReservesInfo( itemnumber => $itemnumber );
959 For a given item, returns an arrayref of reserves hashrefs,
960 with a course hashref under the key 'course'
964 sub GetItemReservesInfo {
966 warn identify_myself(%params) if $DEBUG;
968 my $itemnumber = $params{'itemnumber'};
970 return unless ($itemnumber);
972 my $course_item = GetCourseItem( itemnumber => $itemnumber );
974 return unless ( keys %$course_item );
976 my $course_reserves = GetCourseReserves( ci_id => $course_item->{'ci_id'} );
978 foreach my $cr (@$course_reserves) {
979 $cr->{'course'} = GetCourse( $cr->{'course_id'} );
982 return $course_reserves;
985 =head2 CountCourseReservesForItem
987 $bool = CountCourseReservesForItem( %params );
989 ci_id - course_item id
991 itemnumber - course_item itemnumber
993 enabled = 'yes' or 'no'
994 Optional, if not supplied, counts reserves
995 for both enabled and disabled courses
999 sub CountCourseReservesForItem {
1001 warn identify_myself(%params) if $DEBUG;
1003 my $ci_id = $params{'ci_id'};
1004 my $itemnumber = $params{'itemnumber'};
1005 my $enabled = $params{'enabled'};
1007 return unless ( $ci_id || $itemnumber );
1010 GetCourseItem( ci_id => $ci_id, itemnumber => $itemnumber );
1012 my @params = ( $course_item->{'ci_id'} );
1013 push( @params, $enabled ) if ($enabled);
1016 SELECT COUNT(*) AS count
1017 FROM course_reserves cr
1018 LEFT JOIN courses c ON ( c.course_id = cr.course_id )
1021 $query .= "AND c.enabled = ?" if ($enabled);
1023 my $dbh = C4::Context->dbh;
1024 my $sth = $dbh->prepare($query);
1025 $sth->execute(@params);
1027 my $row = $sth->fetchrow_hashref();
1029 return $row->{'count'};
1032 =head2 SearchCourses
1034 my $courses = SearchCourses( term => $search_term, enabled => 'yes' );
1040 warn identify_myself(%params) if $DEBUG;
1042 my $term = $params{'term'};
1044 my $enabled = $params{'enabled'} || '%';
1047 my $query = "SELECT c.* FROM courses c";
1050 LEFT JOIN course_instructors ci
1051 ON ( c.course_id = ci.course_id )
1052 LEFT JOIN borrowers b
1053 ON ( ci.borrowernumber = b.borrowernumber )
1054 LEFT JOIN authorised_values av
1055 ON ( c.department = av.authorised_value )
1057 ( av.category = 'DEPARTMENT' OR av.category = 'TERM' )
1060 department LIKE ? OR
1061 course_number LIKE ? OR
1063 course_name LIKE ? OR
1065 public_note LIKE ? OR
1066 CONCAT(surname,' ',firstname) LIKE ? OR
1067 CONCAT(firstname,' ',surname) LIKE ? OR
1073 GROUP BY c.course_id
1077 @params = ($term) x 10;
1079 $query .= " ORDER BY department, course_number, section, term, course_name ";
1081 my $dbh = C4::Context->dbh;
1082 my $sth = $dbh->prepare($query);
1084 $sth->execute(@params, $enabled);
1086 my $courses = $sth->fetchall_arrayref( {} );
1088 foreach my $c (@$courses) {
1089 $c->{'instructors'} = GetCourseInstructors( $c->{'course_id'} );
1095 sub whoami { ( caller(1) )[3] }
1096 sub whowasi { ( caller(2) )[3] }
1098 sub stringify_params {
1103 foreach my $key ( keys %params ) {
1104 $string .= " $key => " . $params{$key} . "\n";
1107 return "( $string )";
1110 sub identify_myself {
1113 return whowasi() . stringify_params(%params);
1120 Kyle M Hall <kyle@bywatersolutions.com>