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, @borrowernumbers);
380 @cardnumbers = @$cardnumbers if ( ref($cardnumbers) eq 'ARRAY' );
381 @borrowernumbers = @$borrowernumbers
382 if ( ref($borrowernumbers) eq 'ARRAY' );
384 my $field = (@cardnumbers) ? 'cardnumber' : 'borrowernumber';
385 my @fields = (@cardnumbers) ? @cardnumbers : @borrowernumbers;
386 my $placeholders = join( ',', ('?') x scalar @fields );
388 my $dbh = C4::Context->dbh;
390 $dbh->do( "DELETE FROM course_instructors WHERE course_id = ?",
392 if ( $mode eq 'replace' );
396 if ( $mode eq 'add' || $mode eq 'replace' ) {
398 INSERT INTO course_instructors ( course_id, borrowernumber )
399 SELECT ?, borrowernumber
401 WHERE $field IN ( $placeholders )
406 DELETE FROM course_instructors
408 AND borrowernumber IN (
409 SELECT borrowernumber FROM borrowers WHERE $field IN ( $placeholders )
414 my $sth = $dbh->prepare($query);
416 $sth->execute( $course_id, @fields ) if (@fields);
419 =head2 GetCourseItem {
421 $course_item = GetCourseItem( itemnumber => $itemnumber [, ci_id => $ci_id );
427 warn identify_myself(%params) if $DEBUG;
429 my $ci_id = $params{'ci_id'};
430 my $itemnumber = $params{'itemnumber'};
432 return unless ( $itemnumber || $ci_id );
434 my $field = ($itemnumber) ? 'itemnumber' : 'ci_id';
435 my $value = ($itemnumber) ? $itemnumber : $ci_id;
437 my $query = "SELECT * FROM course_items WHERE $field = ?";
438 my $dbh = C4::Context->dbh;
439 my $sth = $dbh->prepare($query);
440 $sth->execute($value);
442 my $course_item = $sth->fetchrow_hashref();
445 $query = "SELECT * FROM course_reserves WHERE ci_id = ?";
446 $sth = $dbh->prepare($query);
447 $sth->execute( $course_item->{'ci_id'} );
448 my $course_reserves = $sth->fetchall_arrayref( {} );
450 $course_item->{'course_reserves'} = $course_reserves
451 if ($course_reserves);
456 =head2 ModCourseItem {
458 ModCourseItem( %params );
460 Creates or modifies an existing course item.
466 warn identify_myself(%params) if $DEBUG;
468 my $itemnumber = $params{'itemnumber'};
469 my $itype = $params{'itype'};
470 my $ccode = $params{'ccode'};
471 my $holdingbranch = $params{'holdingbranch'};
472 my $location = $params{'location'};
474 return unless ($itemnumber);
476 my $course_item = GetCourseItem( itemnumber => $itemnumber );
481 $ci_id = $course_item->{'ci_id'};
485 course_item => $course_item,
490 $ci_id = _AddCourseItem(%params);
497 =head2 _AddCourseItem
499 my $ci_id = _AddCourseItem( %params );
505 warn identify_myself(%params) if $DEBUG;
507 my ( @fields, @values );
509 push( @fields, 'itemnumber = ?' );
510 push( @values, $params{'itemnumber'} );
514 push( @fields, "$_ = ?" );
515 push( @values, $params{$_} );
519 my $query = "INSERT INTO course_items SET " . join( ',', @fields );
520 my $dbh = C4::Context->dbh;
521 $dbh->do( $query, undef, @values );
523 my $ci_id = $dbh->last_insert_id( undef, undef, 'course_items', 'ci_id' );
528 =head2 _UpdateCourseItem
530 _UpdateCourseItem( %params );
534 sub _UpdateCourseItem {
536 warn identify_myself(%params) if $DEBUG;
538 my $ci_id = $params{'ci_id'};
539 my $course_item = $params{'course_item'};
540 my $itype = $params{'itype'};
541 my $ccode = $params{'ccode'};
542 my $holdingbranch = $params{'holdingbranch'};
543 my $location = $params{'location'};
545 return unless ( $ci_id || $course_item );
547 $course_item = GetCourseItem( ci_id => $ci_id )
548 unless ($course_item);
549 $ci_id = $course_item->{'ci_id'} unless ($ci_id);
551 ## Revert fields that had an 'original' value, but now don't
552 ## Update the item fields to the stored values from course_items
553 ## and then set those fields in course_items to NULL
554 my @fields_to_revert;
556 if ( !$params{$_} && $course_item->{$_} ) {
557 push( @fields_to_revert, $_ );
562 fields => \@fields_to_revert,
563 course_item => $course_item
564 ) if (@fields_to_revert);
566 ## Update fields that still have an original value, but it has changed
567 ## This necessitates only changing the current item values, as we still
568 ## have the original values stored in course_items
572 && $course_item->{$_}
573 && $params{$_} ne $course_item->{$_} )
575 $mod_params{$_} = $params{$_};
578 ModItem( \%mod_params, undef, $course_item->{'itemnumber'} );
580 ## Update fields that didn't have an original value, but now do
581 ## We must save the original value in course_items, and also
582 ## update the item fields to the new value
583 my $item = GetItem( $course_item->{'itemnumber'} );
587 if ( $params{$_} && !$course_item->{$_} ) {
588 $mod_params_new{$_} = $params{$_};
589 $mod_params_old{$_} = $item->{$_};
592 _ModStoredFields( 'ci_id' => $params{'ci_id'}, %mod_params_old );
593 ModItem( \%mod_params_new, undef, $course_item->{'itemnumber'} );
597 =head2 _ModStoredFields
599 _ModStoredFields( %params );
601 Updates the values for the 'original' fields in course_items
606 sub _ModStoredFields {
608 warn identify_myself(%params) if $DEBUG;
610 return unless ( $params{'ci_id'} );
612 my ( @fields_to_update, @values_to_update );
616 push( @fields_to_update, $_ );
617 push( @values_to_update, $params{$_} );
622 "UPDATE course_items SET "
623 . join( ',', map { "$_=?" } @fields_to_update )
624 . " WHERE ci_id = ?";
626 C4::Context->dbh->do( $query, undef, @values_to_update, $params{'ci_id'} )
627 if (@values_to_update);
633 _RevertFields( ci_id => $ci_id, fields => \@fields_to_revert );
639 warn identify_myself(%params) if $DEBUG;
641 my $ci_id = $params{'ci_id'};
642 my $course_item = $params{'course_item'};
643 my $fields = $params{'fields'};
644 my @fields = @$fields;
646 return unless ($ci_id);
648 $course_item = GetCourseItem( ci_id => $params{'ci_id'} )
649 unless ($course_item);
653 foreach my $field (@fields) {
655 if ( $field eq $_ && $course_item->{$_} ) {
656 $mod_item_params->{$_} = $course_item->{$_};
657 push( @fields_to_null, $_ );
661 ModItem( $mod_item_params, undef, $course_item->{'itemnumber'} );
664 "UPDATE course_items SET "
665 . join( ',', map { "$_=NULL" } @fields_to_null )
666 . " WHERE ci_id = ?";
668 C4::Context->dbh->do( $query, undef, $ci_id ) if (@fields_to_null);
671 =head2 _SwapAllFields
673 _SwapAllFields( $ci_id );
679 warn "C4::CourseReserves::_SwapFields( $ci_id )" if $DEBUG;
681 my $course_item = GetCourseItem( ci_id => $ci_id );
682 my $item = GetItem( $course_item->{'itemnumber'} );
684 my %course_item_fields;
687 if ( $course_item->{$_} ) {
688 $course_item_fields{$_} = $course_item->{$_};
689 $item_fields{$_} = $item->{$_};
693 ModItem( \%course_item_fields, undef, $course_item->{'itemnumber'} );
694 _ModStoredFields( %item_fields, ci_id => $ci_id );
697 =head2 GetCourseItems {
699 $course_items = GetCourseItems(
700 [course_id => $course_id]
701 [, itemnumber => $itemnumber ]
708 warn identify_myself(%params) if $DEBUG;
710 my $course_id = $params{'course_id'};
711 my $itemnumber = $params{'itemnumber'};
713 return unless ($course_id);
718 my $query = "SELECT * FROM course_items";
720 if ( keys %params ) {
724 foreach my $key ( keys %params ) {
725 push( @query_keys, " $key LIKE ? " );
726 push( @query_values, $params{$key} );
729 $query .= join( ' AND ', @query_keys );
732 my $dbh = C4::Context->dbh;
733 my $sth = $dbh->prepare($query);
734 $sth->execute(@query_values);
736 return $sth->fetchall_arrayref( {} );
739 =head2 DelCourseItem {
741 DelCourseItem( ci_id => $cr_id );
747 warn identify_myself(%params) if $DEBUG;
749 my $ci_id = $params{'ci_id'};
751 return unless ($ci_id);
753 _RevertFields( ci_id => $ci_id, fields => \@FIELDS );
756 DELETE FROM course_items
759 C4::Context->dbh->do( $query, undef, $ci_id );
762 =head2 GetCourseReserve {
764 $course_item = GetCourseReserve( %params );
768 sub GetCourseReserve {
770 warn identify_myself(%params) if $DEBUG;
772 my $cr_id = $params{'cr_id'};
773 my $course_id = $params{'course_id'};
774 my $ci_id = $params{'ci_id'};
776 return unless ( $cr_id || ( $course_id && $ci_id ) );
778 my $dbh = C4::Context->dbh;
783 SELECT * FROM course_reserves
786 $sth = $dbh->prepare($query);
787 $sth->execute($cr_id);
791 SELECT * FROM course_reserves
792 WHERE course_id = ? AND ci_id = ?
794 $sth = $dbh->prepare($query);
795 $sth->execute( $course_id, $ci_id );
798 my $course_reserve = $sth->fetchrow_hashref();
799 return $course_reserve;
802 =head2 ModCourseReserve
804 $id = ModCourseReserve( %params );
808 sub ModCourseReserve {
810 warn identify_myself(%params) if $DEBUG;
812 my $course_id = $params{'course_id'};
813 my $ci_id = $params{'ci_id'};
814 my $staff_note = $params{'staff_note'};
815 my $public_note = $params{'public_note'};
817 return unless ( $course_id && $ci_id );
820 GetCourseReserve( course_id => $course_id, ci_id => $ci_id );
823 my $dbh = C4::Context->dbh;
825 if ($course_reserve) {
826 $cr_id = $course_reserve->{'cr_id'};
829 UPDATE course_reserves
830 SET staff_note = ?, public_note = ?
833 $dbh->do( $query, undef, $staff_note, $public_note, $cr_id );
837 INSERT INTO course_reserves SET
843 $dbh->do( $query, undef, $course_id, $ci_id, $staff_note,
846 $dbh->last_insert_id( undef, undef, 'course_reserves', 'cr_id' );
849 my $course = GetCourse($course_id);
850 EnableOrDisableCourseItem(
851 ci_id => $params{'ci_id'},
852 enabled => $course->{'enabled'}
858 =head2 GetCourseReserves {
860 $course_reserves = GetCourseReserves( %params );
867 include_courses => 1,
871 sub GetCourseReserves {
873 warn identify_myself(%params) if $DEBUG;
875 my $course_id = $params{'course_id'};
876 my $ci_id = $params{'ci_id'};
877 my $include_items = $params{'include_items'};
878 my $include_count = $params{'include_count'};
879 my $include_courses = $params{'include_courses'};
881 return unless ( $course_id || $ci_id );
883 my $field = ($course_id) ? 'course_id' : 'ci_id';
884 my $value = ($course_id) ? $course_id : $ci_id;
887 SELECT cr.*, ci.itemnumber
888 FROM course_reserves cr, course_items ci
890 AND cr.ci_id = ci.ci_id
892 my $dbh = C4::Context->dbh;
893 my $sth = $dbh->prepare($query);
894 $sth->execute($value);
896 my $course_reserves = $sth->fetchall_arrayref( {} );
898 if ($include_items) {
899 foreach my $cr (@$course_reserves) {
900 $cr->{'course_item'} = GetCourseItem( ci_id => $cr->{'ci_id'} );
901 $cr->{'item'} = GetBiblioFromItemNumber( $cr->{'itemnumber'} );
902 $cr->{'issue'} = GetOpenIssue( $cr->{'itemnumber'} );
906 if ($include_count) {
907 foreach my $cr (@$course_reserves) {
908 $cr->{'reserves_count'} =
909 CountCourseReservesForItem( ci_id => $cr->{'ci_id'} );
913 if ($include_courses) {
914 foreach my $cr (@$course_reserves) {
916 GetCourses( itemnumber => $cr->{'itemnumber'} );
920 return $course_reserves;
923 =head2 DelCourseReserve {
925 DelCourseReserve( cr_id => $cr_id );
929 sub DelCourseReserve {
931 warn identify_myself(%params) if $DEBUG;
933 my $cr_id = $params{'cr_id'};
935 return unless ($cr_id);
937 my $dbh = C4::Context->dbh;
939 my $course_reserve = GetCourseReserve( cr_id => $cr_id );
942 DELETE FROM course_reserves
945 $dbh->do( $query, undef, $cr_id );
947 ## If there are no other course reserves for this item
948 ## delete the course_item as well
949 unless ( CountCourseReservesForItem( ci_id => $course_reserve->{'ci_id'} ) )
951 DelCourseItem( ci_id => $course_reserve->{'ci_id'} );
956 =head2 GetReservesInfo
958 my $arrayref = GetItemReservesInfo( itemnumber => $itemnumber );
960 For a given item, returns an arrayref of reserves hashrefs,
961 with a course hashref under the key 'course'
965 sub GetItemReservesInfo {
967 warn identify_myself(%params) if $DEBUG;
969 my $itemnumber = $params{'itemnumber'};
971 return unless ($itemnumber);
973 my $course_item = GetCourseItem( itemnumber => $itemnumber );
975 return unless ( keys %$course_item );
977 my $course_reserves = GetCourseReserves( ci_id => $course_item->{'ci_id'} );
979 foreach my $cr (@$course_reserves) {
980 $cr->{'course'} = GetCourse( $cr->{'course_id'} );
983 return $course_reserves;
986 =head2 CountCourseReservesForItem
988 $bool = CountCourseReservesForItem( %params );
990 ci_id - course_item id
992 itemnumber - course_item itemnumber
994 enabled = 'yes' or 'no'
995 Optional, if not supplied, counts reserves
996 for both enabled and disabled courses
1000 sub CountCourseReservesForItem {
1002 warn identify_myself(%params) if $DEBUG;
1004 my $ci_id = $params{'ci_id'};
1005 my $itemnumber = $params{'itemnumber'};
1006 my $enabled = $params{'enabled'};
1008 return unless ( $ci_id || $itemnumber );
1011 GetCourseItem( ci_id => $ci_id, itemnumber => $itemnumber );
1013 my @params = ( $course_item->{'ci_id'} );
1014 push( @params, $enabled ) if ($enabled);
1017 SELECT COUNT(*) AS count
1018 FROM course_reserves cr
1019 LEFT JOIN courses c ON ( c.course_id = cr.course_id )
1022 $query .= "AND c.enabled = ?" if ($enabled);
1024 my $dbh = C4::Context->dbh;
1025 my $sth = $dbh->prepare($query);
1026 $sth->execute(@params);
1028 my $row = $sth->fetchrow_hashref();
1030 return $row->{'count'};
1033 =head2 SearchCourses
1035 my $courses = SearchCourses( term => $search_term, enabled => 'yes' );
1041 warn identify_myself(%params) if $DEBUG;
1043 my $term = $params{'term'};
1045 my $enabled = $params{'enabled'} || '%';
1048 my $query = "SELECT c.* FROM courses c";
1051 LEFT JOIN course_instructors ci
1052 ON ( c.course_id = ci.course_id )
1053 LEFT JOIN borrowers b
1054 ON ( ci.borrowernumber = b.borrowernumber )
1055 LEFT JOIN authorised_values av
1056 ON ( c.department = av.authorised_value )
1058 ( av.category = 'DEPARTMENT' OR av.category = 'TERM' )
1061 department LIKE ? OR
1062 course_number LIKE ? OR
1064 course_name LIKE ? OR
1066 public_note LIKE ? OR
1067 CONCAT(surname,' ',firstname) LIKE ? OR
1068 CONCAT(firstname,' ',surname) LIKE ? OR
1074 GROUP BY c.course_id
1078 @params = ($term) x 10;
1080 $query .= " ORDER BY department, course_number, section, term, course_name ";
1082 my $dbh = C4::Context->dbh;
1083 my $sth = $dbh->prepare($query);
1085 $sth->execute(@params, $enabled);
1087 my $courses = $sth->fetchall_arrayref( {} );
1089 foreach my $c (@$courses) {
1090 $c->{'instructors'} = GetCourseInstructors( $c->{'course_id'} );
1096 sub whoami { ( caller(1) )[3] }
1097 sub whowasi { ( caller(2) )[3] }
1099 sub stringify_params {
1104 foreach my $key ( keys %params ) {
1105 $string .= " $key => " . $params{$key} . "\n";
1108 return "( $string )";
1111 sub identify_myself {
1114 return whowasi() . stringify_params(%params);
1121 Kyle M Hall <kyle@bywatersolutions.com>