@@ -, +, @@ type "phone") the value "test") phone numbers. An example regular expression is provided below the input field. You can use that while testing this feature. Copy-paste it into the input box and save preferences. (regex also provided below) table including checkboxes for transport types "SMS", "Phone" and "Email". "Primary email", "Primary phone", "SMS number", please clear those fields. be disabled. should appear. appear as you stored them. text into phone number fields, they should appear as invalid, so test like that. to patron's Details tab and you should see multiple "Edit" links on that page. The point is to make sure transport type selections are correctly removed from the patron if the modification screen lets you modify for example primary email, but does not present the messaging preferences at the bottom of the page. So, as an example, first insert patron a valid email and select all email transport types. Then, go to the "Edit" link right above "Patron messaging preferences" title on patron's details page. This page lets you modify "Primary email" but not messaging preferences. Clear the "Primary email" field and save, and observe that all previously selected email transport types are now removed. "Patron messaging preferences title" on the left. You should still be able to select email as transport types. it is no longer possible to select email as a transport type. --- C4/Form/MessagingPreferences.pm | 63 +++++++++- C4/Members.pm | 16 +++ C4/Members/Messaging.pm | 138 ++++++++++++++++++++- Koha/Patron/Modification.pm | 17 +++ Koha/Validation.pm | 4 + .../prog/en/modules/members/memberentrygen.tt | 4 + koha-tmpl/intranet-tmpl/prog/js/members.js | 12 ++ .../intranet-tmpl/prog/js/messaging-preference.js | 93 ++++++++++++++ .../bootstrap/en/modules/opac-messaging.tt | 9 ++ .../opac-tmpl/bootstrap/js/messaging-preference.js | 91 ++++++++++++++ members/memberentry.pl | 6 +- opac/opac-messaging.pl | 2 +- 12 files changed, 446 insertions(+), 9 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/js/messaging-preference.js create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/messaging-preference.js --- a/C4/Form/MessagingPreferences.pm +++ a/C4/Form/MessagingPreferences.pm @@ -77,11 +77,46 @@ sub handle_form_action { # TODO: If a "NONE" box and another are checked somehow (javascript failed), we should pay attention to the "NONE" box my $prefs_set = 0; OPTION: foreach my $option ( @$messaging_options ) { - my $updater = { %{ $target_params }, - message_attribute_id => $option->{'message_attribute_id'} }; - + my $updater = { + message_attribute_id => $option->{'message_attribute_id'} + }; + + $updater->{borrowernumber} = $target_params->{borrowernumber}; + if ($target_params->{categorycode} && !$target_params->{borrowernumber}) { + $updater->{categorycode} = $target_params->{categorycode}; + } + # find the desired transports - @{$updater->{'message_transport_types'}} = $query->multi_param( $option->{'message_attribute_id'} ); + @{$updater->{'message_transport_types'}} = + $query->multi_param( $option->{'message_attribute_id'} ); + + my $email = $query->param('email'); + my $phone = $query->param('phone'); + my $smsnumber = $query->param('SMSnumber'); + + # Messaging preference validation. Make sure there is a valid contact + # information provided for every transport method. Otherwise remove + # the transport method, because the message cannot be delivered! + if (_no_contact_set($email, $target_params, 'email')) { + my $id = _transport_set( + 'email', @{$updater->{'message_transport_types'}} + ); + splice(@{$updater->{'message_transport_types'}}, $id, 1) if $id > -1; + } + if (_no_contact_set($phone, $target_params, 'phone')) { + my $id = _transport_set( + 'phone',@{$updater->{'message_transport_types'}} + ); + splice(@{$updater->{'message_transport_types'}}, $id, 1) if $id > -1; + } + if (_no_contact_set($smsnumber, $target_params, 'smsalertnumber')) { + my $id = _transport_set( + 'sms', + @{$updater->{'message_transport_types'}} + ); + splice(@{$updater->{'message_transport_types'}}, $id, 1) if $id > -1; + } + next OPTION unless $updater->{'message_transport_types'}; if ( $option->{'has_digest'} ) { @@ -149,6 +184,26 @@ sub set_form_values { $template->param(messaging_preferences => $messaging_options); } +sub _no_contact_set { + my ($param, $target_params, $target_param_name) = @_; + + if (defined $param && !$param) { + return 1; + } + elsif (!defined $param && exists $target_params->{$target_param_name} + && !$target_params->{$target_param_name}) { + return 1; + } else { + return 0; + } +} + +sub _transport_set { + my ($name, @transport_methods) = @_; + + return List::MoreUtils::firstidx { $_ eq $name } @transport_methods; +} + =head1 TODO =over 4 --- a/C4/Members.pm +++ a/C4/Members.pm @@ -396,6 +396,22 @@ sub ModMember { } } + # Validate messaging preferences if any of the following field have + # been removed + if (!$data{email} || !$data{phone} || !$data{smsalertnumber}) { + C4::Members::Messaging::DeleteAllMisconfiguredPreferences( + $data{borrowernumber} + ); + } + # ...or if any of the given contact information is invalid + elsif ($data{email} && !Koha::Validation::email($data{email}) || + $data{phone} && !Koha::Validation::phone($data{phone}) || + $data{smsalertnumber} && !Koha::Validation::phone($data{phone})) { + C4::Members::Messaging::DeleteAllMisconfiguredPreferences( + $data{borrowernumber} + ); + } + # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a # cronjob will use for syncing with NL if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { --- a/C4/Members/Messaging.pm +++ a/C4/Members/Messaging.pm @@ -21,7 +21,7 @@ use strict; use warnings; use C4::Context; - +use Koha::Validation; =head1 NAME @@ -253,6 +253,142 @@ sub SetMessagingPreferencesFromDefaults { $default_pref->{borrowernumber} = $params->{borrowernumber}; SetMessagingPreference( $default_pref ); } + # Finally, delete all misconfigured preferences + DeleteAllMisconfiguredPreferences($params->{borrowernumber}); +} + +=head2 DeleteAllMisconfiguredPreferences + + C4::Members::Messaging::DeleteAllMisconfiguredPreferences( [ $borrowernumber ] ); + +Deletes all misconfigured preferences for ALL borrowers that have invalid contact information for +the transport types. Given a borrowernumber, deletes misconfigured preferences only for this borrower. + +return: returns array of arrayrefs to the deleted preferences (borrower_message_preference_id and message_transport_type) + +=cut + +sub DeleteAllMisconfiguredPreferences { + my $borrowernumber = shift; + + my @deleted_prefs; + + push(@deleted_prefs, DeleteMisconfiguredPreference("email", "email", "email", + $borrowernumber)); + push(@deleted_prefs, DeleteMisconfiguredPreference("phone", "phone", "phone", + $borrowernumber)); + push(@deleted_prefs, DeleteMisconfiguredPreference("sms", "smsalertnumber", + "phone", $borrowernumber)); + + return @deleted_prefs; +} + +=head2 DeleteMisconfiguredPreference + + C4::Members::Messaging::DeleteMisconfiguredPreference( + $type, $contact, $validator [, $borrowernumber ] + ); + +Takes a messaging preference type and the primary contact method for it, and +a string to define the Koha::Validation that should be used to determine whether +the preference is misconfigured. + +A messaging preference is misconfigured when it is linked with invalid contact +information. E.g. messaging type email expects the user to have a valid e-mail +address in order to work. + +Deletes misconfigured preferences for ALL borrowers that have invalid contact +information for that transport type. Given a borrowernumber, deletes misconfigured +preferences only for this borrower. + +return: returns array of arrayrefs to the deleted preferences +(borrower_message_preference_id and message_transport_type) + +=cut + +sub DeleteMisconfiguredPreference { + my ($type, $contact, $validator, $borrowernumber) = @_; + + if (not defined $type or not defined $contact or not defined $validator) { + return 0; + } + + my @misconfigured_prefs; + + # Get all messaging preferences and borrower's contact information + my $dbh = C4::Context->dbh(); + my $query = " + + SELECT + borrower_message_preferences.borrower_message_preference_id, + borrower_message_transport_preferences.message_transport_type, + borrowers.$contact + + FROM + borrower_message_preferences, + borrower_message_transport_preferences, + borrowers + + WHERE + borrower_message_preferences.borrower_message_preference_id + = + borrower_message_transport_preferences.borrower_message_preference_id + + AND + borrowers.borrowernumber = borrower_message_preferences.borrowernumber + + AND + borrower_message_transport_preferences.message_transport_type = ? + "; + + $query .= " AND borrowers.borrowernumber = ?" if defined $borrowernumber; + + my $sth = $dbh->prepare($query); + + if (defined $borrowernumber){ + $sth->execute($type, $borrowernumber); + } else { + $sth->execute($type); + } + + while (my $ref = $sth->fetchrow_arrayref) { + if ($$ref[1] eq $type) { + if (not defined $$ref[2] or defined $$ref[2] and $$ref[2] eq "") { + my $valid_contact = 0; # delete prefs if empty contact + unless ($valid_contact && $$ref[2]) { + # push the misconfigured preferences into an array + push(@misconfigured_prefs, $$ref[0]); + } + } + else { + my ($valid_contact, $err, $err_msg) = + Koha::Validation->$validator($$ref[2]); + unless ($valid_contact && $$ref[2]) { + # push the misconfigured preferences into an array + push(@misconfigured_prefs, $$ref[0]); + } + } + } + } + + $sth = $dbh->prepare(" + + DELETE FROM + borrower_message_transport_preferences + + WHERE + borrower_message_preference_id = ? AND message_transport_type = ? + "); + + my @deleted_prefs; + foreach my $id (@misconfigured_prefs){ + # delete the misconfigured pref + $sth->execute($id, $type); + # push it into array that we will return + push (@deleted_prefs, [$id,$type]); + } + + return @deleted_prefs; } =head1 TABLES --- a/Koha/Patron/Modification.pm +++ a/Koha/Patron/Modification.pm @@ -140,6 +140,23 @@ sub approve { && $attr->{value} == 0 ); } + + # Validate messaging preferences if any of the following field + # have been removed + if (!$data->{email} || !$data->{phone} || + !$data->{smsalertnumber}) { + C4::Members::Messaging::DeleteAllMisconfiguredPreferences( + $self->borrowernumber + ); + } + # ...or if any of the given contact information is invalid + elsif (!Koha::Validation::email($data->{email}) || + !Koha::Validation::phone($data->{phone}) || + !Koha::Validation::phone($data->{phone})) { + C4::Members::Messaging::DeleteAllMisconfiguredPreferences( + $self->borrowernumber + ); + } } catch { if ( $_->isa('DBIx::Class::Exception') ) { --- a/Koha/Validation.pm +++ a/Koha/Validation.pm @@ -39,6 +39,7 @@ This module lets you validate given inputs. =head3 email Koha::Validation::email("email@address.com"); +Koha::Validation->email("email@address.com"); Validates given email. @@ -48,6 +49,7 @@ returns: 1 if the given email is valid (or empty), 0 otherwise. sub email { my $address = shift; + $address = shift if $address eq __PACKAGE__; return 1 unless $address; return 0 if $address =~ /(^(\s))|((\s)$)/; @@ -58,6 +60,7 @@ sub email { =head3 phone Koha::Validation::validate_phonenumber(123456789); +Koha::Validation->validate_phonenumber(123456789); Validates given phone number. @@ -67,6 +70,7 @@ returns: 1 if the given phone number is valid (or empty), 0 otherwise. sub phone { my $phonenumber = shift; + $phonenumber = shift if $phonenumber eq __PACKAGE__; return 1 unless $phonenumber; return 0 if $phonenumber =~ /(^(\s))|((\s)$)/; --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt @@ -64,6 +64,9 @@ $(document).ready(function() { $(".toggler").toggle(); }); + [% IF email %] + patron_email = "[% email %]"; + [% END %] var MSG_INCORRECT_PHONE = _("Please enter a valid phone number."); $.validator.addMethod('phone', function(value) { @@ -114,6 +117,7 @@ $(document).ready(function() { //]]> + [% INCLUDE 'header.inc' %] --- a/koha-tmpl/intranet-tmpl/prog/js/members.js +++ a/koha-tmpl/intranet-tmpl/prog/js/members.js @@ -372,6 +372,18 @@ $(document).ready(function(){ $("#phone, #phonepro, #B_phone, #SMSnumber").each(function(){ $(this).val($.trim($(this).val())); }); + disableCheckboxesWithInvalidPreferences($("#email"), "email"); + disableCheckboxesWithInvalidPreferences($("#phone"), "phone"); + disableCheckboxesWithInvalidPreferences($("#SMSnumber"), "sms"); + $("#email").on("input change", function() { + disableCheckboxesWithInvalidPreferences($("#email"), "email"); + }); + $("#phone").on("input change", function() { + disableCheckboxesWithInvalidPreferences($("#phone"), "phone"); + }); + $("#SMSnumber").on("input change", function() { + disableCheckboxesWithInvalidPreferences($("#SMSnumber"), "sms"); + }); var mrform = $("#manual_restriction_form"); var mrlink = $("#add_manual_restriction"); --- a/koha-tmpl/intranet-tmpl/prog/js/messaging-preference.js +++ a/koha-tmpl/intranet-tmpl/prog/js/messaging-preference.js @@ -0,0 +1,93 @@ +/** + * + * Used in: koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt + * + * This component is also used in OPAC: koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-messaging.tt + * For modifications, edit also OPAC version in: koha-tmpl/opac-tmpl/bootstrap/en/js/messaging-preference.js + * + * Disables and clears checkboxes from messaging preferences + * if there is either invalid or nonexistent contact information + * for the message transfer type. + * + * @param {HTMLInputElement} elem + * The contact field + * @param {string} id_attr + * Checkboxes' id attribute so that we can recognize them + */ +// Settings for messaging-preference.js +var patron_messaging_checkbox_preferences = { + email: { + checked_checkboxes: null, + is_enabled: true, + disabled_checkboxes: null + }, + sms: { + checked_checkboxes: null, + is_enabled: true, + disabled_checkboxes: null + }, + phone: { + checked_checkboxes: null, + is_enabled: true, + disabled_checkboxes: null + } +}; + +function disableCheckboxesWithInvalidPreferences(elem, id_attr) { + if (!$(elem).length && typeof patron_email !== "undefined") { + return; + } + // Get checkbox preferences for the element + var checkbox_prefs = eval("patron_messaging_checkbox_preferences." + id_attr); + // Check if element is empty or not valid + if (!$(elem).length || $(elem).val().length == 0 || !$(elem).valid()) { + + if (checkbox_prefs.is_enabled) { + // Save the state of checked checkboxes + checkbox_prefs.checked_checkboxes = $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]:checked"); + + // Save the state of automatically disabled checkboxes + // (We don't want to enable them once the e-mail is valid!) + checkbox_prefs.disabled_checkboxes = $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]:disabled"); + + // Clear patron messaging preferences checkboxes + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").removeAttr("checked"); + + // Then disable checkboxes from patron messaging perferences + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").attr("disabled", "disabled"); + + // Color table cell's background emphasize the disabled state of the checkbox + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").parent().css("background-color", "#E8F0F8"); + + // Show notice about missing contact in messaging preferences box + $("#required-" + id_attr).css("display", "block"); + + checkbox_prefs.is_enabled = false; + } + } else { + + if (!checkbox_prefs.is_enabled) { + // Enable patron messaging preferences checkboxes + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").removeAttr("disabled"); + + // Disable the checkboxes that were disabled by default + checkbox_prefs.disabled_checkboxes.each(function() { + $(this).attr("disabled", "disabled"); + $(this).removeAttr("checked"); + }); + + // Restore the state of checkboxes + checkbox_prefs.checked_checkboxes.each(function() { + $(this).attr("checked", "checked"); + }); + + // Remove the background color from table cell + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").parent().css("background-color", "#FFF"); + + // Remove notice about missing contact from messaging preferences box + $("#required-" + id_attr).css("display", "none"); + + checkbox_prefs.is_enabled = true; + } + } +} --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-messaging.tt +++ a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-messaging.tt @@ -25,6 +25,9 @@ [% IF Koha.Preference( 'EnhancedMessagingPreferencesOPAC' ) %]

Your messaging settings

+

Primary email

is required in order to set Email preferences.
+ [% IF ( TalkingTechItivaPhone AND ValidatePhoneNumber ) %]

Primary phone

is required in order to set Phone preferences.
[% END %] + [% IF ( SMSSendDriver AND ValidatePhoneNumber ) %]

SMS number

is required in order to set SMS preferences.
[% END %] [% IF ( settings_updated ) %]

Settings updated

[% END %] @@ -214,11 +217,17 @@ error.width("auto"); } }); + [% IF SMSSendDriver %] $("#SMSnumber").on("change", function(){ $(this).val($.trim($(this).val())); + [% IF ( ValidatePhoneNumber ) %] + disableCheckboxesWithInvalidPreferences($("#SMSnumber"), "sms"); + [% END %] }); + [% END %] }); //]]> + [% END %] --- a/koha-tmpl/opac-tmpl/bootstrap/js/messaging-preference.js +++ a/koha-tmpl/opac-tmpl/bootstrap/js/messaging-preference.js @@ -0,0 +1,91 @@ +/** + * + * Used in: koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-messaging.tt + * + * This component is also used in Staff client: koha-tmpl/intranet-tmpl/prog/en/modules/memberentrygen.tt + * For modifications, edit also Staff client version in: koha-tmpl/intranet-tmpl/prog/en/js/messaging-preference.js + * + * Disables and clears checkboxes from messaging preferences + * if there is either invalid or nonexistent contact information + * for the message transfer type. + * + * @param {HTMLInputElement} elem + * The contact field + * @param {string} id_attr + * Checkboxes' id attribute so that we can recognize them + */ +// Settings for messaging-preference.js +var patron_messaging_checkbox_preferences = { + email: { + checked_checkboxes: null, + is_enabled: true, + disabled_checkboxes: null + }, + sms: { + checked_checkboxes: null, + is_enabled: true, + disabled_checkboxes: null + }, + phone: { + checked_checkboxes: null, + is_enabled: true, + disabled_checkboxes: null + } +}; + +function disableCheckboxesWithInvalidPreferences(elem, id_attr) { + // Get checkbox preferences for the element + var checkbox_prefs = eval("patron_messaging_checkbox_preferences." + id_attr); + // Check if element is empty or not valid + + if (!$(elem).length || $(elem).val().length == 0 || !$(elem).valid()) { + + if (checkbox_prefs.is_enabled) { + // Save the state of checked checkboxes + checkbox_prefs.checked_checkboxes = $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]:checked"); + + // Save the state of automatically disabled checkboxes + // (We don't want to enable them once the e-mail is valid!) + checkbox_prefs.disabled_checkboxes = $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]:disabled"); + + // Clear patron messaging preferences checkboxes + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").removeAttr("checked"); + + // Then disable checkboxes from patron messaging perferences + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").attr("disabled", "disabled"); + + // Color table cell's background emphasize the disabled state of the checkbox + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").parent().css("background-color", "#E8F0F8"); + + // Show notice about missing contact in messaging preferences box + $("#required-" + id_attr).css("display", "block"); + + checkbox_prefs.is_enabled = false; + } + } else { + + if (!checkbox_prefs.is_enabled) { + // Enable patron messaging preferences checkboxes + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").removeAttr("disabled"); + + // Disable the checkboxes that were disabled by default + checkbox_prefs.disabled_checkboxes.each(function() { + $(this).attr("disabled", "disabled"); + $(this).removeAttr("checked"); + }); + + // Restore the state of checkboxes + checkbox_prefs.checked_checkboxes.each(function() { + $(this).attr("checked", "checked"); + }); + + // Remove the background color from table cell + $("input[type='checkbox'][id^=" + id_attr + "][value=" + id_attr + "]").parent().css("background-color", "#FFF"); + + // Remove notice about missing contact from messaging preferences box + $("#required-" + id_attr).css("display", "none"); + + checkbox_prefs.is_enabled = true; + } + } +} --- a/members/memberentry.pl +++ a/members/memberentry.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/perl -d # Copyright 2006 SAN OUEST PROVENCE et Paul POULAIN # Copyright 2010 BibLibre @@ -438,7 +438,7 @@ if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){ C4::Members::Attributes::SetBorrowerAttributes($borrowernumber, $extended_patron_attributes); } if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) { - C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template, 1, $newdata{'categorycode'}); + C4::Form::MessagingPreferences::handle_form_action($input, \%newdata, $template, 1, $newdata{'categorycode'}); } # Try to do the live sync with the Norwegian national patron database, if it is enabled if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { @@ -499,7 +499,7 @@ if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){ C4::Members::Attributes::SetBorrowerAttributes($borrowernumber, $extended_patron_attributes); } if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) { - C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template); + C4::Form::MessagingPreferences::handle_form_action($input, \%data, $template); } } print scalar ($destination eq "circ") ? --- a/opac/opac-messaging.pl +++ a/opac/opac-messaging.pl @@ -77,7 +77,7 @@ if ( defined $query->param('modify') && $query->param('modify') eq 'yes' ) { $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ); } - C4::Form::MessagingPreferences::handle_form_action($query, { borrowernumber => $borrowernumber }, $template); + C4::Form::MessagingPreferences::handle_form_action($query, $borrower, $template); } C4::Form::MessagingPreferences::set_form_values({ borrowernumber => $borrower->{'borrowernumber'} }, $template); --