[open-ils-commits] [GIT] Evergreen ILS branch master updated. 4bca5949ed01f29c1a1bd6de6e9f139b3dba391f

Evergreen Git git at git.evergreen-ils.org
Tue Jan 10 14:14:47 EST 2012


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  4bca5949ed01f29c1a1bd6de6e9f139b3dba391f (commit)
       via  5b1ece4196f9885e2227ad7eee46def68c93906e (commit)
       via  b0e97273b24a2deca3d5cbd73b66b2fca558c8b1 (commit)
       via  fb103148f8d93ea04671cf13156a8abd22e50344 (commit)
       via  764544d0cbad934d6fc0c673a032755fd34eb2e5 (commit)
      from  9be1f5e4e48cd9e9d0e670988b640a409aa76c3f (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 4bca5949ed01f29c1a1bd6de6e9f139b3dba391f
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Jan 10 14:14:38 2012 -0500

    Stamping upgrade script for SMS notification branch
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 32bc358..542d0ad 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -86,7 +86,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0665', :eg_version); -- phasefx/miker
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0666', :eg_version); -- phasefx/miker
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql b/Open-ILS/src/sql/Pg/upgrade/0666.schema.sms_carriers.sql
similarity index 99%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql
rename to Open-ILS/src/sql/Pg/upgrade/0666.schema.sms_carriers.sql
index fc134d6..f1497b7 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0666.schema.sms_carriers.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
-SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0666', :eg_version);
 
 -- 950.data.seed-values.sql
 INSERT INTO config.settings_group (name, label) VALUES

commit 5b1ece4196f9885e2227ad7eee46def68c93906e
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Oct 31 05:40:15 2011 -0400

    fix existing Notification Type typo
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>

diff --git a/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2 b/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
index 98cb03c..7d23021 100644
--- a/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
+++ b/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
@@ -79,7 +79,7 @@
  
     <table>
         <thead><tr>
-            <th>[% l('Notifation Type') %]</th>
+            <th>[% l('Notification Type') %]</th>
             <th>[% l('Enabled') %]</th>
         </tr></thead>
         <tbody class='data_grid'>

commit b0e97273b24a2deca3d5cbd73b66b2fca558c8b1
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Jan 9 10:46:52 2012 -0500

    Prevent unshared warnings in hold placement closure
    
    sub data_filler {} becomes $data_filler = sub {}
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
index 28fe7c8..5c95809 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
@@ -561,6 +561,8 @@ sub load_myopac_holds {
     return defined($hold_handle_result) ? $hold_handle_result : Apache2::Const::OK;
 }
 
+my $data_filler;
+
 sub load_place_hold {
     my $self = shift;
     my $ctx = $self->ctx;
@@ -637,14 +639,14 @@ sub load_place_hold {
     my @hold_data;
     $ctx->{hold_data} = \@hold_data;
 
-    sub data_filler {
+    $data_filler = sub {
         my $hdata = shift;
         if ($ctx->{email_notify}) { $hdata->{email_notify} = $ctx->{email_notify}; }
         if ($ctx->{phone_notify}) { $hdata->{phone_notify} = $ctx->{phone_notify}; }
         if ($ctx->{sms_notify}) { $hdata->{sms_notify} = $ctx->{sms_notify}; }
         if ($ctx->{sms_carrier}) { $hdata->{sms_carrier} = $ctx->{sms_carrier}; }
         return $hdata;
-    }
+    };
 
     my $type_dispatch = {
         T => sub {
@@ -679,7 +681,7 @@ sub load_place_hold {
                     $part_required = 1 if $np_copies->[0]->{count} == 0;
                 }
 
-                push(@hold_data, data_filler({
+                push(@hold_data, $data_filler->({
                     target => $rec,
                     record => $rec,
                     parts => $parts,
@@ -697,7 +699,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($vol) = grep {$_->id eq $id} @$vols;
-                push(@hold_data, data_filler({target => $vol, record => $vol->record}));
+                push(@hold_data, $data_filler->({target => $vol, record => $vol->record}));
             }
         },
         C => sub {
@@ -713,7 +715,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($copy) = grep {$_->id eq $id} @$copies;
-                push(@hold_data, data_filler({target => $copy, record => $copy->call_number->record}));
+                push(@hold_data, $data_filler->({target => $copy, record => $copy->call_number->record}));
             }
         },
         I => sub {
@@ -728,7 +730,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($iss) = grep {$_->id eq $id} @$isses;
-                push(@hold_data, data_filler({target => $iss, record => $iss->subscription->record_entry}));
+                push(@hold_data, $data_filler->({target => $iss, record => $iss->subscription->record_entry}));
             }
         }
         # ...
@@ -848,7 +850,7 @@ sub attempt_hold_placement {
         my $breq = $bses->request( 
             $method, 
             $e->authtoken, 
-            data_filler({   patronid => $usr,
+            $data_filler->({   patronid => $usr,
                 pickup_lib => $pickup_lib, 
                 hold_type => $hold_type
             }),

commit fb103148f8d93ea04671cf13156a8abd22e50344
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Sep 22 09:17:50 2011 -0400

    SMS texting
    
    For SMS functionality we're basically converting a carrier and a phone number
    into an email address and sending via SMTP like other email notifications.
    There is administrative UI for configuring these mappings:
    
        Admin -> Server Settings -> SMS Carriers
    
    The permission ADMIN_SMS_CARRIER is needed to use this interface. In a stock
    Evergreen installation, this is given to the Global Administrator group.
    
    In this interface, any occurance of "$number" in the Email Gateway field will
    change into the pertinent SMS phone number when the mapping gets used. We're not
    doing anything fancy here for verifying or munging phone numbers, but you could
    do munging in the pertinent SMS Action/Trigger templates, or better, contribute
    code for the OPAC templates and/or the utility method get_sms_gateway_email in
    Trigger/Reactor.pm.
    
    Our set of stock mappings comes from
    http://en.wikipedia.org/wiki/List_of_SMS_gateways
    
    There's no automated process for keeping this up-to-date, though some volunteer
    with a Wikipedia account could add the page to their watchlist for changes and
    let us know if something needs changing.
    
    Set the org unit setting "SMS: Enable features that send SMS text messages."
    (database name "sms.enable") to True to expose the following:
    
      * SMS notification widgets in the TT-OPAC hold placement interface (to notify
        when a hold is ready for pickup)
    
      * "(SMS)" links next to call numbers on the TT-OPAC record details page. The
        interface spawned by these links require user authentication by default, but
        this can be changed by setting the org unit setting "SMS: Disable auth
        requirement for texting call numbers." to True. The database name for that
        setting is "sms.disable_authentication_requirement.callnumbers".
    
      * New options in TT-OPAC My Account -> Account Preferences -> Notification
        Preferences (we also add in some missing prefs for holds outside of SMS)
    
    There are two pertinent Action/Trigger templates under Admin -> Local
    Administration -> Notifications / Action Triggers:
    
      * SMS Call Number
      * Hold Ready for Pickup SMS Notification
    
    Don't disable these. We rely on the sms.enable YAOUS as the master on/off
    switch.
    
    Also, it's probably best to have just one sms.enable for the whole consortium.
    If you mix and match on/off settings here, then user preferences for SMS can get
    blown away if the user updates their settings in the TT-OPAC at an org where SMS
    is disabled.  Hrmm, the same would probably happen if the user jumps between the
    TT-OPAC and the original JS-PAC.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 07b9815..e9c6f1a 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -725,6 +725,25 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
         </permacrud>
 	</class>
 
+	<class id="csc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::sms_carrier" oils_persist:tablename="config.sms_carrier" reporter:label="SMS Carrier" oils_persist:field_safe="true">
+		<fields oils_persist:primary="id" oils_persist:sequence="config.sms_carrier_id_seq">
+			<field reporter:label="ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Region" name="region" reporter:datatype="text" oils_persist:i18n="true"/>
+			<field reporter:label="Name" name="name" reporter:datatype="text" oils_persist:i18n="true"/>
+            <field reporter:label="Active" name="active" reporter:datatype="bool" oils_persist:i18n="true"/>
+			<field reporter:label="Email Gateway" name="email_gateway" reporter:datatype="text" oils_persist:i18n="true"/>
+		</fields>
+		<links/>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="ADMIN_SMS_CARRIER" global_required="true"/>
+                <retrieve/>
+                <update permission="ADMIN_SMS_CARRIER" global_required="true"/>
+                <delete permission="ADMIN_SMS_CARRIER" global_required="true"/>
+            </actions>
+        </permacrud>
+	</class>
+
 	<class id="mra" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="metabib::record_attr" oils_persist:tablename="metabib.record_attr" reporter:label="SVF Record Attribute" oils_persist:field_safe="true">
 		<fields oils_persist:primary="id">
 			<field reporter:label="Record ID" name="id" reporter:datatype="id" oils_obj:required="true"/>
@@ -4510,6 +4529,8 @@ SELECT  usr,
 			<field reporter:label="Holdable Formats (for M-type hold)" name="holdable_formats" reporter:datatype="text"/>
 			<field reporter:label="Hold ID" name="id" reporter:datatype="id" />
 			<field reporter:label="Notifications Phone Number" name="phone_notify" reporter:datatype="text"/>
+			<field reporter:label="Notifications SMS Number" name="sms_notify" reporter:datatype="text"/>
+			<field reporter:label="Notifications SMS Carrier" name="sms_carrier" reporter:datatype="text"/>
 			<field reporter:label="Pickup Library" name="pickup_lib" reporter:datatype="org_unit"/>
 			<field reporter:label="Last Targeting Date/Time" name="prev_check_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Requesting Library" name="request_lib" reporter:datatype="org_unit"/>
@@ -4552,6 +4573,7 @@ SELECT  usr,
 			<link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
 			<link field="notes" reltype="has_many" key="hold" map="" class="ahrn"/>
 			<link field="current_shelf_lib" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="sms_carrier" reltype="might_have" key="code" map="" class="csc"/>
 		</links>
 	</class>
 	<class id="alhr" controller="open-ils.cstore" oils_obj:fieldmapper="action::last_hold_request" reporter:label="Last Captured Hold Request" oils_persist:readonly="true">
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
index a018e3f..e1d2c82 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
@@ -1241,6 +1241,75 @@ sub create_update_asset_copy_template {
     $e->commit and return $retval;
 }
 
+__PACKAGE__->register_method(
+    method      => "acn_sms_msg",
+    api_name    => "open-ils.cat.acn.send_sms_text",
+    signature   => q^
+        Send an SMS text from an A/T template for specified call numbers.
+
+        First parameter is null or an auth token (whether a null is allowed
+        depends on the sms.disable_authentication_requirement.callnumbers OU
+        setting).
+
+        Second parameter is the id of the context org.
+
+        Third parameter is the code of the SMS carrier from the
+        config.sms_carrier table.
+
+        Fourth parameter is the SMS number.
+
+        Fifth parameter is the ACN id's to target, though currently only the
+        first ACN is used by the template (and the UI is only sending one).
+    ^
+);
+
+sub acn_sms_msg {
+    my($self, $conn, $auth, $org_id, $carrier, $number, $target_ids) = @_;
+
+    my $sms_enable = $U->ou_ancestor_setting_value(
+        $org_id || $U->fetch_org_tree->id,
+        'sms.enable'
+    );
+    # We could maybe make a Validator for this on the templates
+    if (! $U->is_true($sms_enable)) {
+        return -1;
+    }
+
+    my $disable_auth = $U->ou_ancestor_setting_value(
+        $org_id || $U->fetch_org_tree->id,
+        'sms.disable_authentication_requirement.callnumbers'
+    );
+
+    my $e = new_editor(
+        (defined $auth)
+        ? (authtoken => $auth, xact => 1)
+        : (xact => 1)
+    );
+    return $e->event unless $disable_auth || $e->checkauth;
+
+    my $targets = $e->batch_retrieve_asset_call_number($target_ids);
+
+    $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
+                  # simply making this method authoritative because of weirdness
+                  # with transaction handling in A/T code that causes rollback
+                  # failure down the line if handling many targets
+
+    return undef unless @$targets;
+    return $U->fire_object_event(
+        undef,                    # event_def
+        'acn.format.sms_text',    # hook
+        $targets,
+        $org_id,
+        undef,                    # granularity
+        {                         # user_data
+            sms_carrier => $carrier,
+            sms_notify => $number
+        }
+    );
+}
+
+
+
 1;
 
 # vi:et:ts=4:sw=4
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
index 1bd0e43..a56a2cd 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
@@ -125,6 +125,40 @@ $_TT_helpers = {
         }
     },
 
+    # given a call number, returns the copy location with the most copies
+    get_most_populous_location => sub {
+        my $acn_id = shift;
+
+        # FIXME - there's probably a more efficient way to do this with json_query/SQL
+        my $call_number = new_editor(xact=>1)->retrieve_asset_call_number([
+            $acn_id,
+            {
+                flesh => 1,
+                flesh_fields => {
+                    acn => ['copies']
+                }
+            }
+        ]);
+        my %location_count = (); my $winning_location; my $winning_total;
+        use Data::Dumper;
+        foreach my $copy (@{$call_number->copies()}) {
+            if (! defined $location_count{ $copy->location() }) {
+                $location_count{ $copy->location() } = 1;
+            } else {
+                $location_count{ $copy->location() } += 1;
+            }
+            if ($location_count{ $copy->location() } > $winning_total) {
+                $winning_total = $location_count{ $copy->location() };
+                $winning_location = $copy->location();
+            }
+        }
+
+        my $location = new_editor(xact=>1)->retrieve_asset_copy_location([
+            $winning_location, {}
+        ]);
+        return $location;
+    },
+
     # returns the org unit setting value
     get_org_setting => sub {
         my($org_id, $setting) = @_;
@@ -273,6 +307,32 @@ $_TT_helpers = {
         return $str ? (new XML::LibXML)->parse_string($str) : undef;
     },
 
+    # returns an email addresses derived from sms_carrier and sms_notify
+    get_sms_gateway_email => sub {
+        my $sms_carrier = shift;
+        my $sms_notify = shift;
+
+        if (! defined $sms_notify || $sms_notify eq '') {
+            return '';
+        }
+
+        my $query = {
+            select => {'csc' => ['id','name','email_gateway']},
+            from => 'csc',
+            where => {id => $sms_carrier}
+        };
+        my $carriers = new_editor()->json_query($query);
+
+        my @addresses = ();
+        foreach my $carrier ( @{ $carriers } ) {
+            my $address = $carrier->{email_gateway};
+            $address =~ s/\$number/$sms_notify/g;
+            push @addresses, $address;
+        }
+
+        return join(',', at addresses);
+    },
+
     unapi_bre => sub {
         my ($bre_id, $unapi_args) = @_;
         $unapi_args ||= {};
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm
new file mode 100644
index 0000000..de775ac
--- /dev/null
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm
@@ -0,0 +1,21 @@
+package OpenILS::Application::Trigger::Reactor::SendSMS;
+use strict; use warnings;
+use Error qw/:try/;
+use Data::Dumper;
+use Email::Send;
+use Email::Simple;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Application::Trigger::Reactor;
+use OpenSRF::Utils::Logger qw/:logger/;
+use Encode;
+$Data::Dumper::Indent = 0;
+
+use base 'OpenILS::Application::Trigger::Reactor::SendEmail';
+
+# This module is just another name for SendEmail, as a way to get around the
+# "ev_def_owner_hook_val_react_clean_delay_once" index/constraint on the table
+# action.event_definition.  The template fed to SendSMS is responsible for
+# using helpers.get_sms_email_gateway to, for example,  convert .sms_carrier
+# and .sms_notify off of a hold into an email address.
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
index 5091c50..b19b8bc 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
@@ -21,6 +21,7 @@ use OpenILS::WWW::EGCatLoader::Account;
 use OpenILS::WWW::EGCatLoader::Search;
 use OpenILS::WWW::EGCatLoader::Record;
 use OpenILS::WWW::EGCatLoader::Container;
+use OpenILS::WWW::EGCatLoader::SMS;
 
 my $U = 'OpenILS::Application::AppUtils';
 
@@ -127,6 +128,12 @@ sub load {
         );
     }
 
+    my $org_unit = $self->cgi->param('loc') || $self->ctx->{aou_tree}->()->id;
+    my $skip_sms_auth = $self->ctx->{get_org_setting}->($org_unit, 'sms.disable_authentication_requirement.callnumbers');
+    if ($skip_sms_auth) {
+        return $self->load_sms_cn if $path =~ m|opac/sms_cn|;
+    }
+
     # ----------------------------------------------------------------
     #  Everything below here requires authentication
     # ----------------------------------------------------------------
@@ -152,6 +159,7 @@ sub load {
     return $self->load_myopac_prefs_notify if $path =~ m|opac/myopac/prefs_notify|;
     return $self->load_myopac_prefs_settings if $path =~ m|opac/myopac/prefs_settings|;
     return $self->load_myopac_prefs if $path =~ m|opac/myopac/prefs|;
+    return $self->load_sms_cn if $path =~ m|opac/sms_cn|;
 
     return Apache2::Const::OK;
 }
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
index 116f8c5..28fe7c8 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
@@ -174,9 +174,46 @@ sub load_myopac_prefs_notify {
     $user_prefs = $self->update_optin_prefs($user_prefs)
         if $self->cgi->request_method eq 'POST';
 
-    $self->ctx->{opt_in_settings} = $user_prefs; 
+    $self->ctx->{opt_in_settings} = $user_prefs;
 
-    return Apache2::Const::OK;
+    my %settings;
+    my $set_map = $self->ctx->{user_setting_map};
+ 
+    foreach my $key (qw/
+        opac.default_phone
+        opac.default_sms_notify
+    /) {
+        my $val = $self->cgi->param($key);
+        $settings{$key}= $val unless $$set_map{$key} eq $val;
+    }
+
+    my $key = 'opac.default_sms_carrier';
+    my $val = $self->cgi->param('sms_carrier');
+    $settings{$key}= $val unless $$set_map{$key} eq $val;
+
+    $key = 'opac.hold_notify';
+    my @notify_methods = ();
+    if ($self->cgi->param($key . ".email") eq 'on') {
+        push @notify_methods, "email";
+    }
+    if ($self->cgi->param($key . ".phone") eq 'on') {
+        push @notify_methods, "phone";
+    }
+    if ($self->cgi->param($key . ".sms") eq 'on') {
+        push @notify_methods, "sms";
+    }
+    $val = join("|", at notify_methods);
+    $settings{$key}= $val unless $$set_map{$key} eq $val;
+
+    # Send the modified settings off to be saved
+    $U->simplereq(
+        'open-ils.actor', 
+        'open-ils.actor.patron.settings.update',
+        $self->editor->authtoken, undef, \%settings);
+
+    # re-fetch user prefs 
+    $self->ctx->{updated_user_settings} = \%settings;
+    return $self->_load_user_with_prefs || Apache2::Const::OK;
 }
 
 sub fetch_optin_prefs {
@@ -331,7 +368,7 @@ sub load_myopac_prefs_settings {
             $settings{$key} = undef if $$set_map{$key};
         }
     }
-    
+
     # Send the modified settings off to be saved
     $U->simplereq(
         'open-ils.actor', 
@@ -537,6 +574,14 @@ sub load_place_hold {
 
     $ctx->{hold_type} = $cgi->param('hold_type');
     $ctx->{default_pickup_lib} = $e->requestor->home_ou; # unless changed below
+    $ctx->{email_notify} = $cgi->param('email_notify');
+    if ($cgi->param('phone_notify_checkbox')) {
+        $ctx->{phone_notify} = $cgi->param('phone_notify');
+    }
+    if ($cgi->param('sms_notify_checkbox')) {
+        $ctx->{sms_notify} = $cgi->param('sms_notify');
+        $ctx->{sms_carrier} = $cgi->param('sms_carrier');
+    }
 
     return $self->generic_redirect unless @targets;
 
@@ -550,12 +595,57 @@ sub load_place_hold {
         ) or return Apache2::Const::HTTP_BAD_REQUEST;
 
         $ctx->{default_pickup_lib} = $ctx->{patron_recipient}->home_ou;
+    } else {
+        $ctx->{staff_recipient} = $self->editor->retrieve_actor_user([
+            $e->requestor->id,
+            {
+                flesh => 1,
+                flesh_fields => {
+                    au => ['settings']
+                }
+            }
+        ]) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+    my $user_setting_map = {
+        map { $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) }
+            @{
+                $ctx->{patron_recipient}
+                ? $ctx->{patron_recipient}->settings
+                : $ctx->{staff_recipient}->settings
+            }
+    };
+    $ctx->{user_setting_map} = $user_setting_map;
+
+    my $default_notify = $$user_setting_map{'opac.hold_notify'} || '';
+    if ($default_notify =~ /email/) {
+        $ctx->{default_email_notify} = 'checked';
+    } else {
+        $ctx->{default_email_notify} = '';
+    }
+    if ($default_notify =~ /phone/) {
+        $ctx->{default_phone_notify} = 'checked';
+    } else {
+        $ctx->{default_phone_notify} = '';
+    }
+    if ($default_notify =~ /sms/) {
+        $ctx->{default_sms_notify} = 'checked';
+    } else {
+        $ctx->{default_sms_notify} = '';
     }
 
     my $request_lib = $e->requestor->ws_ou;
     my @hold_data;
     $ctx->{hold_data} = \@hold_data;
 
+    sub data_filler {
+        my $hdata = shift;
+        if ($ctx->{email_notify}) { $hdata->{email_notify} = $ctx->{email_notify}; }
+        if ($ctx->{phone_notify}) { $hdata->{phone_notify} = $ctx->{phone_notify}; }
+        if ($ctx->{sms_notify}) { $hdata->{sms_notify} = $ctx->{sms_notify}; }
+        if ($ctx->{sms_carrier}) { $hdata->{sms_carrier} = $ctx->{sms_carrier}; }
+        return $hdata;
+    }
+
     my $type_dispatch = {
         T => sub {
             my $recs = $e->batch_retrieve_biblio_record_entry(\@targets, {substream => 1});
@@ -589,12 +679,12 @@ sub load_place_hold {
                     $part_required = 1 if $np_copies->[0]->{count} == 0;
                 }
 
-                push(@hold_data, {
-                    target => $rec, 
+                push(@hold_data, data_filler({
+                    target => $rec,
                     record => $rec,
                     parts => $parts,
                     part_required => $part_required
-                });
+                }));
             }
         },
         V => sub {
@@ -607,7 +697,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($vol) = grep {$_->id eq $id} @$vols;
-                push(@hold_data, {target => $vol, record => $vol->record});
+                push(@hold_data, data_filler({target => $vol, record => $vol->record}));
             }
         },
         C => sub {
@@ -623,7 +713,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($copy) = grep {$_->id eq $id} @$copies;
-                push(@hold_data, {target => $copy, record => $copy->call_number->record});
+                push(@hold_data, data_filler({target => $copy, record => $copy->call_number->record}));
             }
         },
         I => sub {
@@ -638,7 +728,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($iss) = grep {$_->id eq $id} @$isses;
-                push(@hold_data, {target => $iss, record => $iss->subscription->record_entry});
+                push(@hold_data, data_filler({target => $iss, record => $iss->subscription->record_entry}));
             }
         }
         # ...
@@ -758,10 +848,10 @@ sub attempt_hold_placement {
         my $breq = $bses->request( 
             $method, 
             $e->authtoken, 
-            {   patronid => $usr, 
+            data_filler({   patronid => $usr,
                 pickup_lib => $pickup_lib, 
                 hold_type => $hold_type
-            }, 
+            }),
             \@create_targets
         );
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm
new file mode 100644
index 0000000..bdaaddb
--- /dev/null
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm
@@ -0,0 +1,57 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use OpenILS::Event;
+use OpenSRF::Utils::JSON;
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+use DateTime;
+my $U = 'OpenILS::Application::AppUtils';
+
+sub load_sms_cn {
+    my $self = shift;
+    my $ctx = $self->ctx;
+    my $gos = $ctx->{get_org_setting};
+    my $e = $self->editor;
+    my $cgi = $self->cgi;
+
+    my $org_unit = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
+
+    $self->_load_user_with_prefs() if($e->checkauth);
+
+    $ctx->{page} = 'sms_cn';
+    $ctx->{sms_carrier} = $cgi->param('sms_carrier');
+    $ctx->{sms_notify} = $cgi->param('sms_notify');
+    $ctx->{copy_id} = $cgi->param('copy_id');
+    $ctx->{query} = $cgi->param('query');
+    $ctx->{origin} = $cgi->param('origin');
+
+    my $acn_results = $e->json_query({
+        select => {
+            acp => ['call_number']
+        },
+        from => 'acp',
+        where => {id => $ctx->{copy_id}}
+    });
+
+    my $acn_ids = [map { $_->{call_number} } @$acn_results];
+    $ctx->{acn_ids} = $acn_ids;
+
+    my $resp = $U->simplereq('open-ils.cat', 'open-ils.cat.acn.send_sms_text',
+        $e->authtoken, $org_unit,
+        $ctx->{sms_carrier}, $ctx->{sms_notify},
+        $acn_ids
+    );
+
+    $ctx->{event} = $resp;
+
+    $ctx->{orig_params} = $cgi->Vars;
+
+    return Apache2::Const::OK;
+}
+
+
diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 1333f27..32bc358 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -955,4 +955,12 @@ CREATE TRIGGER limit_logs_oust
     BEFORE INSERT OR UPDATE ON config.org_unit_setting_type_log
     FOR EACH ROW EXECUTE PROCEDURE limit_oustl();
 
+CREATE TABLE config.sms_carrier (
+    id              SERIAL PRIMARY KEY,
+    region          TEXT,
+    name            TEXT,
+    email_gateway   TEXT,
+    active          BOOLEAN DEFAULT TRUE
+);
+
 COMMIT;
diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql
index 0ead480..715c64a 100644
--- a/Open-ILS/src/sql/Pg/090.schema.action.sql
+++ b/Open-ILS/src/sql/Pg/090.schema.action.sql
@@ -394,6 +394,8 @@ CREATE TABLE action.hold_request (
 	holdable_formats	TEXT,
 	phone_notify		TEXT,
 	email_notify		BOOL				NOT NULL DEFAULT TRUE,
+	sms_notify		TEXT,
+	sms_carrier		INT REFERENCES config.sms_carrier (id),
 	frozen			BOOL				NOT NULL DEFAULT FALSE,
 	thaw_date		TIMESTAMP WITH TIME ZONE,
 	shelf_time		TIMESTAMP WITH TIME ZONE,
@@ -402,6 +404,11 @@ CREATE TABLE action.hold_request (
 	shelf_expire_time TIMESTAMPTZ,
 	current_shelf_lib INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED
 );
+ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
+    sms_notify IS NULL
+    OR sms_carrier IS NOT NULL -- and implied sms_notify IS NOT NULL
+);
+
 
 CREATE INDEX hold_request_target_idx ON action.hold_request (target);
 CREATE INDEX hold_request_usr_idx ON action.hold_request (usr);
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 3283727..3d725ff 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -2365,7 +2365,10 @@ INSERT INTO config.settings_group (name, label) VALUES
 ('recall', oils_i18n_gettext('config.settings_group.recall', 'Recalls', 'coust', 'label')),
 ('booking', oils_i18n_gettext('config.settings_group.booking', 'Booking', 'coust', 'label')),
 ('offline', oils_i18n_gettext('config.settings_group.offline', 'Offline', 'coust', 'label')),
-('receipt_template', oils_i18n_gettext('config.settings_group.receipt_template', 'Receipt Template', 'coust', 'label'));
+('receipt_template', oils_i18n_gettext('config.settings_group.receipt_template', 'Receipt Template', 'coust', 'label')),
+('sms', oils_i18n_gettext('sms','SMS Text Messages','csg','label'))
+;
+
 
 
 -- org_unit setting types
@@ -4512,6 +4515,34 @@ INSERT into config.org_unit_setting_type
     ),
     'bool', null)
 
+,( 'sms.enable', 'sms',
+    oils_i18n_gettext(
+        'sms.enable',
+        'Enable features that send SMS text messages.',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'sms.enable',
+        'Current features that use SMS include hold-ready-for-pickup notifications and a "Send Text" action for call numbers in the OPAC. If this setting is not enabled, the SMS options will not be offered to the user.  Unless you are carefully silo-ing patrons and their use of the OPAC, the context org for this setting should be the top org in the org hierarchy, otherwise patrons can trample their user settings when jumping between orgs.',
+        'coust',
+        'description'
+    ),
+    'bool', null)
+,( 'sms.disable_authentication_requirement.callnumbers', 'sms',
+    oils_i18n_gettext(
+        'sms.disable_authentication_requirement.callnumbers',
+        'Disable auth requirement for texting call numbers.',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'sms.disable_authentication_requirement.callnumbers',
+        'Disable authentication requirement for sending call number information via SMS from the OPAC.',
+        'coust',
+        'description'
+    ),
+    'bool', null)
 ;
 
 UPDATE config.org_unit_setting_type
@@ -10432,4 +10463,1339 @@ INSERT INTO container.biblio_record_entry_bucket_type (code, label) VALUES (
     oils_i18n_gettext('vandelay_queue', 'Vandelay Queue', 'cbrebt', 'label')
 );
 
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype,fm_class) VALUES (
+    'opac.default_sms_carrier',
+    'sms',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_sms_carrier',
+        'Default SMS/Text Carrier',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_sms_carrier',
+        'Default SMS/Text Carrier',
+        'cust',
+        'description'
+    ),
+    'link',
+    'csc'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'opac.default_sms_notify',
+    'sms',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_sms_notify',
+        'Default SMS/Text Number',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_sms_notify',
+        'Default SMS/Text Number',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'opac.default_phone',
+    'opac',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_phone',
+        'Default Phone Number',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_phone',
+        'Default Phone Number',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+SELECT setval( 'config.sms_carrier_id_seq', 1000 );
+INSERT INTO config.sms_carrier VALUES
+
+    -- Testing
+    (
+        1,
+        oils_i18n_gettext(
+            1,
+            'Local',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            1,
+            'Test Carrier',
+            'csc',
+            'name'
+        ),
+        'opensrf+$number at localhost',
+        FALSE
+    ),
+
+    -- Canada & USA
+    (
+        2,
+        oils_i18n_gettext(
+            2,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            2,
+            'Rogers Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at pcs.rogers.com',
+        TRUE
+    ),
+    (
+        3,
+        oils_i18n_gettext(
+            3,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            3,
+            'Rogers Wireless (Alternate)',
+            'csc',
+            'name'
+        ),
+        '1$number at mms.rogers.com',
+        TRUE
+    ),
+    (
+        4,
+        oils_i18n_gettext(
+            4,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            4,
+            'Telus Mobility',
+            'csc',
+            'name'
+        ),
+        '$number at msg.telus.com',
+        TRUE
+    ),
+
+    -- Canada
+    (
+        5,
+        oils_i18n_gettext(
+            5,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            5,
+            'Koodo Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at msg.telus.com',
+        TRUE
+    ),
+    (
+        6,
+        oils_i18n_gettext(
+            6,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            6,
+            'Fido',
+            'csc',
+            'name'
+        ),
+        '$number at fido.ca',
+        TRUE
+    ),
+    (
+        7,
+        oils_i18n_gettext(
+            7,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            7,
+            'Bell Mobility & Solo Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at txt.bell.ca',
+        TRUE
+    ),
+    (
+        8,
+        oils_i18n_gettext(
+            8,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            8,
+            'Bell Mobility & Solo Mobile (Alternate)',
+            'csc',
+            'name'
+        ),
+        '$number at txt.bellmobility.ca',
+        TRUE
+    ),
+    (
+        9,
+        oils_i18n_gettext(
+            9,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            9,
+            'Aliant',
+            'csc',
+            'name'
+        ),
+        '$number at sms.wirefree.informe.ca',
+        TRUE
+    ),
+    (
+        10,
+        oils_i18n_gettext(
+            10,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            10,
+            'PC Telecom',
+            'csc',
+            'name'
+        ),
+        '$number at mobiletxt.ca',
+        TRUE
+    ),
+    (
+        11,
+        oils_i18n_gettext(
+            11,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            11,
+            'SaskTel',
+            'csc',
+            'name'
+        ),
+        '$number at sms.sasktel.com',
+        TRUE
+    ),
+    (
+        12,
+        oils_i18n_gettext(
+            12,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            12,
+            'MTS Mobility',
+            'csc',
+            'name'
+        ),
+        '$number at text.mtsmobility.com',
+        TRUE
+    ),
+    (
+        13,
+        oils_i18n_gettext(
+            13,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            13,
+            'Virgin Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at vmobile.ca',
+        TRUE
+    ),
+
+    -- International
+    (
+        14,
+        oils_i18n_gettext(
+            14,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            14,
+            'Iridium',
+            'csc',
+            'name'
+        ),
+        '$number at msg.iridium.com',
+        TRUE
+    ),
+    (
+        15,
+        oils_i18n_gettext(
+            15,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            15,
+            'Globalstar',
+            'csc',
+            'name'
+        ),
+        '$number at msg.globalstarusa.com',
+        TRUE
+    ),
+    (
+        16,
+        oils_i18n_gettext(
+            16,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            16,
+            'Bulletin.net',
+            'csc',
+            'name'
+        ),
+        '$number at bulletinmessenger.net', -- International Formatted number
+        TRUE
+    ),
+    (
+        17,
+        oils_i18n_gettext(
+            17,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            17,
+            'Panacea Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at api.panaceamobile.com',
+        TRUE
+    ),
+
+    -- USA
+    (
+        18,
+        oils_i18n_gettext(
+            18,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            18,
+            'C Beyond',
+            'csc',
+            'name'
+        ),
+        '$number at cbeyond.sprintpcs.com',
+        TRUE
+    ),
+    (
+        19,
+        oils_i18n_gettext(
+            19,
+            'Alaska, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            19,
+            'General Communications, Inc.',
+            'csc',
+            'name'
+        ),
+        '$number at mobile.gci.net',
+        TRUE
+    ),
+    (
+        20,
+        oils_i18n_gettext(
+            20,
+            'California, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            20,
+            'Golden State Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at gscsms.com',
+        TRUE
+    ),
+    (
+        21,
+        oils_i18n_gettext(
+            21,
+            'Cincinnati, Ohio, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            21,
+            'Cincinnati Bell',
+            'csc',
+            'name'
+        ),
+        '$number at gocbw.com',
+        TRUE
+    ),
+    (
+        22,
+        oils_i18n_gettext(
+            22,
+            'Hawaii, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            22,
+            'Hawaiian Telcom Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at hawaii.sprintpcs.com',
+        TRUE
+    ),
+    (
+        23,
+        oils_i18n_gettext(
+            23,
+            'Midwest, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            23,
+            'i wireless (T-Mobile)',
+            'csc',
+            'name'
+        ),
+        '$number.iws at iwspcs.net',
+        TRUE
+    ),
+    (
+        24,
+        oils_i18n_gettext(
+            24,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            24,
+            'i-wireless (Sprint PCS)',
+            'csc',
+            'name'
+        ),
+        '$number at iwirelesshometext.com',
+        TRUE
+    ),
+    (
+        25,
+        oils_i18n_gettext(
+            25,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            25,
+            'MetroPCS',
+            'csc',
+            'name'
+        ),
+        '$number at mymetropcs.com',
+        TRUE
+    ),
+    (
+        26,
+        oils_i18n_gettext(
+            26,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            26,
+            'Kajeet',
+            'csc',
+            'name'
+        ),
+        '$number at mobile.kajeet.net',
+        TRUE
+    ),
+    (
+        27,
+        oils_i18n_gettext(
+            27,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            27,
+            'Element Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at SMS.elementmobile.net',
+        TRUE
+    ),
+    (
+        28,
+        oils_i18n_gettext(
+            28,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            28,
+            'Esendex',
+            'csc',
+            'name'
+        ),
+        '$number at echoemail.net',
+        TRUE
+    ),
+    (
+        29,
+        oils_i18n_gettext(
+            29,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            29,
+            'Boost Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at myboostmobile.com',
+        TRUE
+    ),
+    (
+        30,
+        oils_i18n_gettext(
+            30,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            30,
+            'BellSouth',
+            'csc',
+            'name'
+        ),
+        '$number at bellsouth.com',
+        TRUE
+    ),
+    (
+        31,
+        oils_i18n_gettext(
+            31,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            31,
+            'Bluegrass Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at sms.bluecell.com',
+        TRUE
+    ),
+    (
+        32,
+        oils_i18n_gettext(
+            32,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            32,
+            'AT&T Enterprise Paging',
+            'csc',
+            'name'
+        ),
+        '$number at page.att.net',
+        TRUE
+    ),
+    (
+        33,
+        oils_i18n_gettext(
+            33,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            33,
+            'AT&T Mobility/Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at txt.att.net',
+        TRUE
+    ),
+    (
+        34,
+        oils_i18n_gettext(
+            34,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            34,
+            'AT&T Global Smart Messaging Suite',
+            'csc',
+            'name'
+        ),
+        '$number at sms.smartmessagingsuite.com',
+        TRUE
+    ),
+    (
+        35,
+        oils_i18n_gettext(
+            35,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            35,
+            'Alltel (Allied Wireless)',
+            'csc',
+            'name'
+        ),
+        '$number at sms.alltelwireless.com',
+        TRUE
+    ),
+    (
+        36,
+        oils_i18n_gettext(
+            36,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            36,
+            'Alaska Communications',
+            'csc',
+            'name'
+        ),
+        '$number at msg.acsalaska.com',
+        TRUE
+    ),
+    (
+        37,
+        oils_i18n_gettext(
+            37,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            37,
+            'Ameritech',
+            'csc',
+            'name'
+        ),
+        '$number at paging.acswireless.com',
+        TRUE
+    ),
+    (
+        38,
+        oils_i18n_gettext(
+            38,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            38,
+            'Cingular (GoPhone prepaid)',
+            'csc',
+            'name'
+        ),
+        '$number at cingulartext.com',
+        TRUE
+    ),
+    (
+        39,
+        oils_i18n_gettext(
+            39,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            39,
+            'Cingular (Postpaid)',
+            'csc',
+            'name'
+        ),
+        '$number at cingular.com',
+        TRUE
+    ),
+    (
+        40,
+        oils_i18n_gettext(
+            40,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            40,
+            'Cellular One (Dobson) / O2 / Orange',
+            'csc',
+            'name'
+        ),
+        '$number at mobile.celloneusa.com',
+        TRUE
+    ),
+    (
+        41,
+        oils_i18n_gettext(
+            41,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            41,
+            'Cellular South',
+            'csc',
+            'name'
+        ),
+        '$number at csouth1.com',
+        TRUE
+    ),
+    (
+        42,
+        oils_i18n_gettext(
+            42,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            42,
+            'Cellcom',
+            'csc',
+            'name'
+        ),
+        '$number at cellcom.quiktxt.com',
+        TRUE
+    ),
+    (
+        43,
+        oils_i18n_gettext(
+            43,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            43,
+            'Chariton Valley Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at sms.cvalley.net',
+        TRUE
+    ),
+    (
+        44,
+        oils_i18n_gettext(
+            44,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            44,
+            'Cricket',
+            'csc',
+            'name'
+        ),
+        '$number at sms.mycricket.com',
+        TRUE
+    ),
+    (
+        45,
+        oils_i18n_gettext(
+            45,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            45,
+            'Cleartalk Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at sms.cleartalk.us',
+        TRUE
+    ),
+    (
+        46,
+        oils_i18n_gettext(
+            46,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            46,
+            'Edge Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at sms.edgewireless.com',
+        TRUE
+    ),
+    (
+        47,
+        oils_i18n_gettext(
+            47,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            47,
+            'Syringa Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at rinasms.com',
+        TRUE
+    ),
+    (
+        48,
+        oils_i18n_gettext(
+            48,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            48,
+            'T-Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at tmomail.net',
+        TRUE
+    ),
+    (
+        49,
+        oils_i18n_gettext(
+            49,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            49,
+            'Straight Talk / PagePlus Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at vtext.com',
+        TRUE
+    ),
+    (
+        50,
+        oils_i18n_gettext(
+            50,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            50,
+            'South Central Communications',
+            'csc',
+            'name'
+        ),
+        '$number at rinasms.com',
+        TRUE
+    ),
+    (
+        51,
+        oils_i18n_gettext(
+            51,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            51,
+            'Simple Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at smtext.com',
+        TRUE
+    ),
+    (
+        52,
+        oils_i18n_gettext(
+            52,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            52,
+            'Sprint (PCS)',
+            'csc',
+            'name'
+        ),
+        '$number at messaging.sprintpcs.com',
+        TRUE
+    ),
+    (
+        53,
+        oils_i18n_gettext(
+            53,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            53,
+            'Nextel',
+            'csc',
+            'name'
+        ),
+        '$number at messaging.nextel.com',
+        TRUE
+    ),
+    (
+        54,
+        oils_i18n_gettext(
+            54,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            54,
+            'Pioneer Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at zsend.com', -- nine digit number
+        TRUE
+    ),
+    (
+        55,
+        oils_i18n_gettext(
+            55,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            55,
+            'Qwest Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at qwestmp.com',
+        TRUE
+    ),
+    (
+        56,
+        oils_i18n_gettext(
+            56,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            56,
+            'US Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at email.uscc.net',
+        TRUE
+    ),
+    (
+        57,
+        oils_i18n_gettext(
+            57,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            57,
+            'Unicel',
+            'csc',
+            'name'
+        ),
+        '$number at utext.com',
+        TRUE
+    ),
+    (
+        58,
+        oils_i18n_gettext(
+            58,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            58,
+            'Teleflip',
+            'csc',
+            'name'
+        ),
+        '$number at teleflip.com',
+        TRUE
+    ),
+    (
+        59,
+        oils_i18n_gettext(
+            59,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            59,
+            'Virgin Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at vmobl.com',
+        TRUE
+    ),
+    (
+        60,
+        oils_i18n_gettext(
+            60,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            60,
+            'Verizon Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at vtext.com',
+        TRUE
+    ),
+    (
+        61,
+        oils_i18n_gettext(
+            61,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            61,
+            'USA Mobility',
+            'csc',
+            'name'
+        ),
+        '$number at usamobility.net',
+        TRUE
+    ),
+    (
+        62,
+        oils_i18n_gettext(
+            62,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            62,
+            'Viaero',
+            'csc',
+            'name'
+        ),
+        '$number at viaerosms.com',
+        TRUE
+    ),
+    (
+        63,
+        oils_i18n_gettext(
+            63,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            63,
+            'TracFone',
+            'csc',
+            'name'
+        ),
+        '$number at mmst5.tracfone.com',
+        TRUE
+    ),
+    (
+        64,
+        oils_i18n_gettext(
+            64,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            64,
+            'Centennial Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at cwemail.com',
+        TRUE
+    ),
+
+    -- South Korea and USA
+    (
+        65,
+        oils_i18n_gettext(
+            65,
+            'South Korea and USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            65,
+            'Helio',
+            'csc',
+            'name'
+        ),
+        '$number at myhelio.com',
+        TRUE
+    )
+;
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+    (
+        517,
+        'ADMIN_SMS_CARRIER',
+        oils_i18n_gettext(
+            517,
+            'Allows a user to add/create/delete SMS Carrier entries.',
+            'ppl',
+            'description'
+        )
+    )
+;
+
+INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
+    SELECT
+        pgt.id, perm.id, aout.depth, TRUE
+    FROM
+        permission.grp_tree pgt,
+        permission.perm_list perm,
+        actor.org_unit_type aout
+    WHERE
+        pgt.name = 'Global Administrator' AND
+        aout.name = 'Consortium' AND
+        perm.code = 'ADMIN_SMS_CARRIER';
+
+INSERT INTO action_trigger.reactor (
+    module,
+    description
+) VALUES (
+    'SendSMS',
+    'Send an SMS text message based on a user-defined template'
+);
+
+INSERT INTO action_trigger.event_definition (
+    active,
+    owner,
+    name,
+    hook,
+    validator,
+    reactor,
+    cleanup_success,
+    delay,
+    delay_field,
+    group_field,
+    template
+) VALUES (
+    true,
+    1, -- admin
+    'Hold Ready for Pickup SMS Notification',
+    'hold.available',
+    'HoldIsAvailable',
+    'SendSMS',
+    'CreateHoldNotification',
+    '00:30:00',
+    'shelf_time',
+    'sms_notify',
+    '[%- USE date -%]
+[%- user = target.0.usr -%]
+From: [%- params.sender_email || default_sender %]
+To: [%- params.recipient_email || helpers.get_sms_gateway_email(target.0.sms_carrier,target.0.sms_notify) %]
+Subject: [% target.size %] hold(s) ready
+
+[% FOR hold IN target %][%-
+  bibxml = helpers.xml_doc( hold.current_copy.call_number.record.marc );
+  title = "";
+  FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a"]'');
+    title = title _ part.textContent;
+  END;
+  author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
+%][% hold.usr.first_given_name %]:[% title %] @ [% hold.pickup_lib.name %]
+[% END %]
+'
+);
+
+INSERT INTO action_trigger.environment (
+    event_def,
+    path
+) VALUES (
+    currval('action_trigger.event_definition_id_seq'),
+    'current_copy.call_number.record.simple_record'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'usr'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'pickup_lib.billing_address'
+);
+
+INSERT INTO action_trigger.hook(
+    key,
+    core_type,
+    description,
+    passive
+) VALUES (
+    'acn.format.sms_text',
+    'acn',
+    oils_i18n_gettext(
+        'acn.format.sms_text',
+        'A text message has been requested for a call number.',
+        'ath',
+        'description'
+    ),
+    FALSE
+);
+
+INSERT INTO action_trigger.event_definition (
+    active,
+    owner,
+    name,
+    hook,
+    validator,
+    reactor,
+    template
+) VALUES (
+    true,
+    1, -- admin
+    'SMS Call Number',
+    'acn.format.sms_text',
+    'NOOP_True',
+    'SendSMS',
+    '[%- USE date -%]
+From: [%- params.sender_email || default_sender %]
+To: [%- params.recipient_email || helpers.get_sms_gateway_email(user_data.sms_carrier,user_data.sms_notify) %]
+Subject: Call Number
+
+[%-
+  bibxml = helpers.xml_doc( target.record.marc );
+  title = "";
+  FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a" or @code="b"]'');
+    title = title _ part.textContent;
+  END;
+  author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
+%]
+Call Number: [% target.label %]
+Location: [% helpers.get_most_populous_location( target.id ).name %]
+Library: [% target.owning_lib.name %]
+[%- IF title %]
+Title: [% title %]
+[%- END %]
+[%- IF author %]
+Author: [% author %]
+[%- END %]
+'
+);
+
+INSERT INTO action_trigger.environment (
+    event_def,
+    path
+) VALUES (
+    currval('action_trigger.event_definition_id_seq'),
+    'record.simple_record'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'owning_lib.billing_address'
+);
+
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql
new file mode 100644
index 0000000..fc134d6
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql
@@ -0,0 +1,1411 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- 950.data.seed-values.sql
+INSERT INTO config.settings_group (name, label) VALUES
+    (
+        'sms',
+        oils_i18n_gettext(
+            'sms',
+            'SMS Text Messages',
+            'csg',
+            'label'
+        )
+    )
+;
+
+INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
+    (
+        'sms.enable',
+        'sms',
+        oils_i18n_gettext(
+            'sms.enable',
+            'Enable features that send SMS text messages.',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'sms.enable',
+            'Current features that use SMS include hold-ready-for-pickup notifications and a "Send Text" action for call numbers in the OPAC. If this setting is not enabled, the SMS options will not be offered to the user.  Unless you are carefully silo-ing patrons and their use of the OPAC, the context org for this setting should be the top org in the org hierarchy, otherwise patrons can trample their user settings when jumping between orgs.',
+            'coust',
+            'description'
+        ),
+        'bool'
+    )
+    ,(
+        'sms.disable_authentication_requirement.callnumbers',
+        'sms',
+        oils_i18n_gettext(
+            'sms.disable_authentication_requirement.callnumbers',
+            'Disable auth requirement for texting call numbers.',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'sms.disable_authentication_requirement.callnumbers',
+            'Disable authentication requirement for sending call number information via SMS from the OPAC.',
+            'coust',
+            'description'
+        ),
+        'bool'
+    )
+;
+
+-- 002.schema.config.sql
+CREATE TABLE config.sms_carrier (
+    id              SERIAL PRIMARY KEY,
+    region          TEXT,
+    name            TEXT,
+    email_gateway   TEXT,
+    active          BOOLEAN DEFAULT TRUE
+);
+
+-- 090.schema.action.sql
+ALTER TABLE action.hold_request ADD COLUMN sms_notify TEXT;
+ALTER TABLE action.hold_request ADD COLUMN sms_carrier INT REFERENCES config.sms_carrier (id);
+ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
+    sms_notify IS NULL
+    OR sms_carrier IS NOT NULL -- and implied sms_notify IS NOT NULL
+);
+
+-- 950.data.seed-values.sql
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype,fm_class) VALUES (
+    'opac.default_sms_carrier',
+    'sms',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_sms_carrier',
+        'Default SMS/Text Carrier',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_sms_carrier',
+        'Default SMS/Text Carrier',
+        'cust',
+        'description'
+    ),
+    'link',
+    'csc'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'opac.default_sms_notify',
+    'sms',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_sms_notify',
+        'Default SMS/Text Number',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_sms_notify',
+        'Default SMS/Text Number',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'opac.default_phone',
+    'opac',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_phone',
+        'Default Phone Number',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_phone',
+        'Default Phone Number',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+SELECT setval( 'config.sms_carrier_id_seq', 1000 );
+INSERT INTO config.sms_carrier VALUES
+
+    -- Testing
+    (
+        1,
+        oils_i18n_gettext(
+            1,
+            'Local',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            1,
+            'Test Carrier',
+            'csc',
+            'name'
+        ),
+        'opensrf+$number at localhost',
+        FALSE
+    ),
+
+    -- Canada & USA
+    (
+        2,
+        oils_i18n_gettext(
+            2,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            2,
+            'Rogers Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at pcs.rogers.com',
+        TRUE
+    ),
+    (
+        3,
+        oils_i18n_gettext(
+            3,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            3,
+            'Rogers Wireless (Alternate)',
+            'csc',
+            'name'
+        ),
+        '1$number at mms.rogers.com',
+        TRUE
+    ),
+    (
+        4,
+        oils_i18n_gettext(
+            4,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            4,
+            'Telus Mobility',
+            'csc',
+            'name'
+        ),
+        '$number at msg.telus.com',
+        TRUE
+    ),
+
+    -- Canada
+    (
+        5,
+        oils_i18n_gettext(
+            5,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            5,
+            'Koodo Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at msg.telus.com',
+        TRUE
+    ),
+    (
+        6,
+        oils_i18n_gettext(
+            6,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            6,
+            'Fido',
+            'csc',
+            'name'
+        ),
+        '$number at fido.ca',
+        TRUE
+    ),
+    (
+        7,
+        oils_i18n_gettext(
+            7,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            7,
+            'Bell Mobility & Solo Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at txt.bell.ca',
+        TRUE
+    ),
+    (
+        8,
+        oils_i18n_gettext(
+            8,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            8,
+            'Bell Mobility & Solo Mobile (Alternate)',
+            'csc',
+            'name'
+        ),
+        '$number at txt.bellmobility.ca',
+        TRUE
+    ),
+    (
+        9,
+        oils_i18n_gettext(
+            9,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            9,
+            'Aliant',
+            'csc',
+            'name'
+        ),
+        '$number at sms.wirefree.informe.ca',
+        TRUE
+    ),
+    (
+        10,
+        oils_i18n_gettext(
+            10,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            10,
+            'PC Telecom',
+            'csc',
+            'name'
+        ),
+        '$number at mobiletxt.ca',
+        TRUE
+    ),
+    (
+        11,
+        oils_i18n_gettext(
+            11,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            11,
+            'SaskTel',
+            'csc',
+            'name'
+        ),
+        '$number at sms.sasktel.com',
+        TRUE
+    ),
+    (
+        12,
+        oils_i18n_gettext(
+            12,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            12,
+            'MTS Mobility',
+            'csc',
+            'name'
+        ),
+        '$number at text.mtsmobility.com',
+        TRUE
+    ),
+    (
+        13,
+        oils_i18n_gettext(
+            13,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            13,
+            'Virgin Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at vmobile.ca',
+        TRUE
+    ),
+
+    -- International
+    (
+        14,
+        oils_i18n_gettext(
+            14,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            14,
+            'Iridium',
+            'csc',
+            'name'
+        ),
+        '$number at msg.iridium.com',
+        TRUE
+    ),
+    (
+        15,
+        oils_i18n_gettext(
+            15,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            15,
+            'Globalstar',
+            'csc',
+            'name'
+        ),
+        '$number at msg.globalstarusa.com',
+        TRUE
+    ),
+    (
+        16,
+        oils_i18n_gettext(
+            16,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            16,
+            'Bulletin.net',
+            'csc',
+            'name'
+        ),
+        '$number at bulletinmessenger.net', -- International Formatted number
+        TRUE
+    ),
+    (
+        17,
+        oils_i18n_gettext(
+            17,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            17,
+            'Panacea Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at api.panaceamobile.com',
+        TRUE
+    ),
+
+    -- USA
+    (
+        18,
+        oils_i18n_gettext(
+            18,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            18,
+            'C Beyond',
+            'csc',
+            'name'
+        ),
+        '$number at cbeyond.sprintpcs.com',
+        TRUE
+    ),
+    (
+        19,
+        oils_i18n_gettext(
+            19,
+            'Alaska, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            19,
+            'General Communications, Inc.',
+            'csc',
+            'name'
+        ),
+        '$number at mobile.gci.net',
+        TRUE
+    ),
+    (
+        20,
+        oils_i18n_gettext(
+            20,
+            'California, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            20,
+            'Golden State Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at gscsms.com',
+        TRUE
+    ),
+    (
+        21,
+        oils_i18n_gettext(
+            21,
+            'Cincinnati, Ohio, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            21,
+            'Cincinnati Bell',
+            'csc',
+            'name'
+        ),
+        '$number at gocbw.com',
+        TRUE
+    ),
+    (
+        22,
+        oils_i18n_gettext(
+            22,
+            'Hawaii, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            22,
+            'Hawaiian Telcom Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at hawaii.sprintpcs.com',
+        TRUE
+    ),
+    (
+        23,
+        oils_i18n_gettext(
+            23,
+            'Midwest, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            23,
+            'i wireless (T-Mobile)',
+            'csc',
+            'name'
+        ),
+        '$number.iws at iwspcs.net',
+        TRUE
+    ),
+    (
+        24,
+        oils_i18n_gettext(
+            24,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            24,
+            'i-wireless (Sprint PCS)',
+            'csc',
+            'name'
+        ),
+        '$number at iwirelesshometext.com',
+        TRUE
+    ),
+    (
+        25,
+        oils_i18n_gettext(
+            25,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            25,
+            'MetroPCS',
+            'csc',
+            'name'
+        ),
+        '$number at mymetropcs.com',
+        TRUE
+    ),
+    (
+        26,
+        oils_i18n_gettext(
+            26,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            26,
+            'Kajeet',
+            'csc',
+            'name'
+        ),
+        '$number at mobile.kajeet.net',
+        TRUE
+    ),
+    (
+        27,
+        oils_i18n_gettext(
+            27,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            27,
+            'Element Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at SMS.elementmobile.net',
+        TRUE
+    ),
+    (
+        28,
+        oils_i18n_gettext(
+            28,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            28,
+            'Esendex',
+            'csc',
+            'name'
+        ),
+        '$number at echoemail.net',
+        TRUE
+    ),
+    (
+        29,
+        oils_i18n_gettext(
+            29,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            29,
+            'Boost Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at myboostmobile.com',
+        TRUE
+    ),
+    (
+        30,
+        oils_i18n_gettext(
+            30,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            30,
+            'BellSouth',
+            'csc',
+            'name'
+        ),
+        '$number at bellsouth.com',
+        TRUE
+    ),
+    (
+        31,
+        oils_i18n_gettext(
+            31,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            31,
+            'Bluegrass Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at sms.bluecell.com',
+        TRUE
+    ),
+    (
+        32,
+        oils_i18n_gettext(
+            32,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            32,
+            'AT&T Enterprise Paging',
+            'csc',
+            'name'
+        ),
+        '$number at page.att.net',
+        TRUE
+    ),
+    (
+        33,
+        oils_i18n_gettext(
+            33,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            33,
+            'AT&T Mobility/Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at txt.att.net',
+        TRUE
+    ),
+    (
+        34,
+        oils_i18n_gettext(
+            34,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            34,
+            'AT&T Global Smart Messaging Suite',
+            'csc',
+            'name'
+        ),
+        '$number at sms.smartmessagingsuite.com',
+        TRUE
+    ),
+    (
+        35,
+        oils_i18n_gettext(
+            35,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            35,
+            'Alltel (Allied Wireless)',
+            'csc',
+            'name'
+        ),
+        '$number at sms.alltelwireless.com',
+        TRUE
+    ),
+    (
+        36,
+        oils_i18n_gettext(
+            36,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            36,
+            'Alaska Communications',
+            'csc',
+            'name'
+        ),
+        '$number at msg.acsalaska.com',
+        TRUE
+    ),
+    (
+        37,
+        oils_i18n_gettext(
+            37,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            37,
+            'Ameritech',
+            'csc',
+            'name'
+        ),
+        '$number at paging.acswireless.com',
+        TRUE
+    ),
+    (
+        38,
+        oils_i18n_gettext(
+            38,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            38,
+            'Cingular (GoPhone prepaid)',
+            'csc',
+            'name'
+        ),
+        '$number at cingulartext.com',
+        TRUE
+    ),
+    (
+        39,
+        oils_i18n_gettext(
+            39,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            39,
+            'Cingular (Postpaid)',
+            'csc',
+            'name'
+        ),
+        '$number at cingular.com',
+        TRUE
+    ),
+    (
+        40,
+        oils_i18n_gettext(
+            40,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            40,
+            'Cellular One (Dobson) / O2 / Orange',
+            'csc',
+            'name'
+        ),
+        '$number at mobile.celloneusa.com',
+        TRUE
+    ),
+    (
+        41,
+        oils_i18n_gettext(
+            41,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            41,
+            'Cellular South',
+            'csc',
+            'name'
+        ),
+        '$number at csouth1.com',
+        TRUE
+    ),
+    (
+        42,
+        oils_i18n_gettext(
+            42,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            42,
+            'Cellcom',
+            'csc',
+            'name'
+        ),
+        '$number at cellcom.quiktxt.com',
+        TRUE
+    ),
+    (
+        43,
+        oils_i18n_gettext(
+            43,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            43,
+            'Chariton Valley Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at sms.cvalley.net',
+        TRUE
+    ),
+    (
+        44,
+        oils_i18n_gettext(
+            44,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            44,
+            'Cricket',
+            'csc',
+            'name'
+        ),
+        '$number at sms.mycricket.com',
+        TRUE
+    ),
+    (
+        45,
+        oils_i18n_gettext(
+            45,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            45,
+            'Cleartalk Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at sms.cleartalk.us',
+        TRUE
+    ),
+    (
+        46,
+        oils_i18n_gettext(
+            46,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            46,
+            'Edge Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at sms.edgewireless.com',
+        TRUE
+    ),
+    (
+        47,
+        oils_i18n_gettext(
+            47,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            47,
+            'Syringa Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at rinasms.com',
+        TRUE
+    ),
+    (
+        48,
+        oils_i18n_gettext(
+            48,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            48,
+            'T-Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at tmomail.net',
+        TRUE
+    ),
+    (
+        49,
+        oils_i18n_gettext(
+            49,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            49,
+            'Straight Talk / PagePlus Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at vtext.com',
+        TRUE
+    ),
+    (
+        50,
+        oils_i18n_gettext(
+            50,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            50,
+            'South Central Communications',
+            'csc',
+            'name'
+        ),
+        '$number at rinasms.com',
+        TRUE
+    ),
+    (
+        51,
+        oils_i18n_gettext(
+            51,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            51,
+            'Simple Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at smtext.com',
+        TRUE
+    ),
+    (
+        52,
+        oils_i18n_gettext(
+            52,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            52,
+            'Sprint (PCS)',
+            'csc',
+            'name'
+        ),
+        '$number at messaging.sprintpcs.com',
+        TRUE
+    ),
+    (
+        53,
+        oils_i18n_gettext(
+            53,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            53,
+            'Nextel',
+            'csc',
+            'name'
+        ),
+        '$number at messaging.nextel.com',
+        TRUE
+    ),
+    (
+        54,
+        oils_i18n_gettext(
+            54,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            54,
+            'Pioneer Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at zsend.com', -- nine digit number
+        TRUE
+    ),
+    (
+        55,
+        oils_i18n_gettext(
+            55,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            55,
+            'Qwest Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at qwestmp.com',
+        TRUE
+    ),
+    (
+        56,
+        oils_i18n_gettext(
+            56,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            56,
+            'US Cellular',
+            'csc',
+            'name'
+        ),
+        '$number at email.uscc.net',
+        TRUE
+    ),
+    (
+        57,
+        oils_i18n_gettext(
+            57,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            57,
+            'Unicel',
+            'csc',
+            'name'
+        ),
+        '$number at utext.com',
+        TRUE
+    ),
+    (
+        58,
+        oils_i18n_gettext(
+            58,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            58,
+            'Teleflip',
+            'csc',
+            'name'
+        ),
+        '$number at teleflip.com',
+        TRUE
+    ),
+    (
+        59,
+        oils_i18n_gettext(
+            59,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            59,
+            'Virgin Mobile',
+            'csc',
+            'name'
+        ),
+        '$number at vmobl.com',
+        TRUE
+    ),
+    (
+        60,
+        oils_i18n_gettext(
+            60,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            60,
+            'Verizon Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at vtext.com',
+        TRUE
+    ),
+    (
+        61,
+        oils_i18n_gettext(
+            61,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            61,
+            'USA Mobility',
+            'csc',
+            'name'
+        ),
+        '$number at usamobility.net',
+        TRUE
+    ),
+    (
+        62,
+        oils_i18n_gettext(
+            62,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            62,
+            'Viaero',
+            'csc',
+            'name'
+        ),
+        '$number at viaerosms.com',
+        TRUE
+    ),
+    (
+        63,
+        oils_i18n_gettext(
+            63,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            63,
+            'TracFone',
+            'csc',
+            'name'
+        ),
+        '$number at mmst5.tracfone.com',
+        TRUE
+    ),
+    (
+        64,
+        oils_i18n_gettext(
+            64,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            64,
+            'Centennial Wireless',
+            'csc',
+            'name'
+        ),
+        '$number at cwemail.com',
+        TRUE
+    ),
+
+    -- South Korea and USA
+    (
+        65,
+        oils_i18n_gettext(
+            65,
+            'South Korea and USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            65,
+            'Helio',
+            'csc',
+            'name'
+        ),
+        '$number at myhelio.com',
+        TRUE
+    )
+;
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+    (
+        517,
+        'ADMIN_SMS_CARRIER',
+        oils_i18n_gettext(
+            517,
+            'Allows a user to add/create/delete SMS Carrier entries.',
+            'ppl',
+            'description'
+        )
+    )
+;
+
+INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
+    SELECT
+        pgt.id, perm.id, aout.depth, TRUE
+    FROM
+        permission.grp_tree pgt,
+        permission.perm_list perm,
+        actor.org_unit_type aout
+    WHERE
+        pgt.name = 'Global Administrator' AND
+        aout.name = 'Consortium' AND
+        perm.code = 'ADMIN_SMS_CARRIER';
+
+INSERT INTO action_trigger.reactor (
+    module,
+    description
+) VALUES (
+    'SendSMS',
+    'Send an SMS text message based on a user-defined template'
+);
+
+INSERT INTO action_trigger.event_definition (
+    active,
+    owner,
+    name,
+    hook,
+    validator,
+    reactor,
+    cleanup_success,
+    delay,
+    delay_field,
+    group_field,
+    template
+) VALUES (
+    true,
+    1, -- admin
+    'Hold Ready for Pickup SMS Notification',
+    'hold.available',
+    'HoldIsAvailable',
+    'SendSMS',
+    'CreateHoldNotification',
+    '00:30:00',
+    'shelf_time',
+    'sms_notify',
+    '[%- USE date -%]
+[%- user = target.0.usr -%]
+From: [%- params.sender_email || default_sender %]
+To: [%- params.recipient_email || helpers.get_sms_gateway_email(target.0.sms_carrier,target.0.sms_notify) %]
+Subject: [% target.size %] hold(s) ready
+
+[% FOR hold IN target %][%-
+  bibxml = helpers.xml_doc( hold.current_copy.call_number.record.marc );
+  title = "";
+  FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a"]'');
+    title = title _ part.textContent;
+  END;
+  author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
+%][% hold.usr.first_given_name %]:[% title %] @ [% hold.pickup_lib.name %]
+[% END %]
+'
+);
+
+INSERT INTO action_trigger.environment (
+    event_def,
+    path
+) VALUES (
+    currval('action_trigger.event_definition_id_seq'),
+    'current_copy.call_number.record.simple_record'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'usr'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'pickup_lib.billing_address'
+);
+
+INSERT INTO action_trigger.hook(
+    key,
+    core_type,
+    description,
+    passive
+) VALUES (
+    'acn.format.sms_text',
+    'acn',
+    oils_i18n_gettext(
+        'acn.format.sms_text',
+        'A text message has been requested for a call number.',
+        'ath',
+        'description'
+    ),
+    FALSE
+);
+
+INSERT INTO action_trigger.event_definition (
+    active,
+    owner,
+    name,
+    hook,
+    validator,
+    reactor,
+    template
+) VALUES (
+    true,
+    1, -- admin
+    'SMS Call Number',
+    'acn.format.sms_text',
+    'NOOP_True',
+    'SendSMS',
+    '[%- USE date -%]
+From: [%- params.sender_email || default_sender %]
+To: [%- params.recipient_email || helpers.get_sms_gateway_email(user_data.sms_carrier,user_data.sms_notify) %]
+Subject: Call Number
+
+[%-
+  bibxml = helpers.xml_doc( target.record.marc );
+  title = "";
+  FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a" or @code="b"]'');
+    title = title _ part.textContent;
+  END;
+  author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
+%]
+Call Number: [% target.label %]
+Location: [% helpers.get_most_populous_location( target.id ).name %]
+Library: [% target.owning_lib.name %]
+[%- IF title %]
+Title: [% title %]
+[%- END %]
+[%- IF author %]
+Author: [% author %]
+[%- END %]
+'
+);
+
+INSERT INTO action_trigger.environment (
+    event_def,
+    path
+) VALUES (
+    currval('action_trigger.event_definition_id_seq'),
+    'record.simple_record'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'owning_lib.billing_address'
+);
+
+
+-- DELETE FROM actor.usr_setting WHERE name = 'opac.default_phone' OR name in ( SELECT name FROM config.usr_setting_type WHERE grp = 'sms' ); DELETE FROM config.usr_setting_type WHERE name = 'opac.default_phone' OR grp = 'sms'; DELETE FROM actor.org_unit_setting WHERE name in ( SELECT name FROM config.org_unit_setting_type WHERE grp = 'sms' ); DELETE FROM config.org_unit_setting_type_log WHERE field_name in ( SELECT name FROM config.org_unit_setting_type WHERE grp = 'sms' ); DELETE FROM config.org_unit_setting_type WHERE grp = 'sms'; DELETE FROM config.settings_group WHERE name = 'sms'; DELETE FROM permission.grp_perm_map WHERE perm = 517; DELETE FROM permission.perm_list WHERE id = 517; ALTER TABLE action.hold_request DROP CONSTRAINT sms_check; ALTER TABLE action.hold_request DROP COLUMN sms_notify; ALTER TABLE action.hold_request DROP COLUMN sms_carrier; DROP TABLE config.sms_carrier; DELETE FROM action_trigger.event WHERE event_def = ( SELECT id FROM action_trigger.event_
 definition WHERE name = 'Hold Ready for Pickup SMS Notification' ); DELETE FROM action_trigger.environment WHERE event_def = ( SELECT id FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification' ); DELETE FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification'; DELETE FROM action_trigger.event WHERE event_def IN ( SELECT id FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text' ); DELETE FROM action_trigger.environment WHERE event_def IN ( SELECT id FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text' ); DELETE FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text'; DELETE FROM action_trigger.hook WHERE key = 'acn.format.sms_text'; DELETE FROM action_trigger.reactor WHERE module = 'SendSMS'; DELETE FROM config.upgrade_log WHERE version = 'XXXX';
+
+COMMIT;
diff --git a/Open-ILS/src/templates/conify/global/config/sms_carrier.tt2 b/Open-ILS/src/templates/conify/global/config/sms_carrier.tt2
new file mode 100644
index 0000000..ce5d3d9
--- /dev/null
+++ b/Open-ILS/src/templates/conify/global/config/sms_carrier.tt2
@@ -0,0 +1,27 @@
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = 'SMS Carriers' %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/config/sms_carrier.js'> </script>
+
+<!-- grid -->
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+        <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+            <div>SMS Carriers</div>
+            <div>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.showCreateDialog()'>New Carrier</button>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.deleteSelected()'>Delete Selected</button>
+            </div>
+        </div>
+        <table  jsId="thingGrid"
+                dojoType="openils.widget.AutoGrid"
+                fieldOrder="['id', 'region', 'name', 'email_gateway', 'active']"
+                query="{id: '*'}"
+                defaultCellWidth='20'
+                fmClass='csc'
+                editOnEnter='true'>
+        </table>
+    </div>
+</div>
+[% END %]
+
+
diff --git a/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2 b/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
index 17d0b67..98cb03c 100644
--- a/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
+++ b/Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
@@ -12,6 +12,71 @@
             class="opac-button" />
     </div>
 
+    [% setting = 'opac.hold_notify' %]
+    <input name='[% setting %]' type="hidden"
+        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+
+    <table class="full-width data_grid" id="acct_search_main">
+        <tbody>
+
+            [% IF ctx.updated_user_settings %]
+            <tr><td colspan='2'>
+                <div class='renew-summary'>
+                    [% l('Account Successfully Updated') %]
+                </div>
+            </td></tr>
+            [% END %]
+
+            <tr>
+                <td>[% l('Notify by Email by default when a hold is ready for pickup?') %]</td>
+                <td>
+                    [% setting = 'opac.hold_notify' %]
+                    <input name='[% setting %].email' type="checkbox"
+                        [% IF (matches = ctx.user_setting_map.$setting.match('email')); %] checked='checked' [% END %]/>
+                </td>
+            </tr>
+            <tr>
+                <td>[% l('Notify by Phone by default when a hold is ready for pickup?') %]</td>
+                <td>
+                    [% setting = 'opac.hold_notify' %]
+                    <input name='[% setting %].phone' type="checkbox"
+                        [% IF (matches = ctx.user_setting_map.$setting.match('phone')); %] checked='checked' [% END %]/>
+                </td>
+            </tr>
+            <tr>
+                <td>[% l('Default Phone Number') %]</td>
+                <td>
+                    [% setting = 'opac.default_phone' %]
+                    <input name='[% setting %]' type="text"
+                        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                </td>
+            </tr>
+            [% IF ctx.get_org_setting(CGI.param('loc') OR ctx.aou_tree.id, 'sms.enable') == 1 %]
+            <tr>
+                <td>[% l('Notify by Text by default when a hold is ready for pickup?') %]</td>
+                <td>
+                    [% setting = 'opac.hold_notify' %]
+                    <input name='[% setting %].sms' type="checkbox"
+                        [% IF (matches = ctx.user_setting_map.$setting.match('sms')); %] checked='checked' [% END %]/>
+                </td>
+            </tr>
+            <tr>
+                <td>[% l('Default Mobile Carrier') %]</td>
+                <td>[% INCLUDE "opac/parts/sms_carrier_selector.tt2" sms_carrier_hide_label="true" %]</td>
+            </tr>
+            <tr>
+                <td>[% l('Default Mobile Number') %]</td>
+                <td>
+                    [% setting = 'opac.default_sms_notify' %]
+                    <input name='[% setting %]' type="text"
+                        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                    [% l('Hint: use the full 10 digits of your phone #, no spaces, no dashes'); %]
+                </td>
+            </tr>
+            [% END %]
+        </tbody>
+    </table>
+ 
     <table>
         <thead><tr>
             <th>[% l('Notifation Type') %]</th>
diff --git a/Open-ILS/src/templates/opac/parts/place_hold.tt2 b/Open-ILS/src/templates/opac/parts/place_hold.tt2
index adb1483..bd95814 100644
--- a/Open-ILS/src/templates/opac/parts/place_hold.tt2
+++ b/Open-ILS/src/templates/opac/parts/place_hold.tt2
@@ -70,6 +70,35 @@
             [% PROCESS "opac/parts/org_selector.tt2";
                 PROCESS build_org_selector name='pickup_lib' value=ctx.default_pickup_lib id='pickup_lib' can_have_vols_only=1 %]
         </p>
+        <p>
+            [% l('Notify when hold is ready for pickup?') %]
+            <blockquote>
+                <input type="checkbox" name="email_notify" value="t"
+                    [% IF ctx.default_email_notify %]checked="checked"[% END %]/>
+                    [% l('Yes, by Email') %]<br/>
+                <input type="checkbox" name="phone_notify_checkbox"
+                    [% IF ctx.default_phone_notify %]checked="checked"[% END %]/>
+                    [% l('Yes, by Phone') %]<br/>
+                <blockquote>
+                    [% l('Phone Number:') %]<input type="text" name="phone_notify" [% setting = 'opac.default_phone';
+                    IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                </blockquote>
+                [% IF ctx.get_org_setting(CGI.param('loc') OR ctx.aou_tree.id, 'sms.enable') == 1 %]
+                <input type="checkbox" name="sms_notify_checkbox"
+                    [% IF ctx.default_sms_notify %]checked="checked"[% END %]/>
+                    [% l('Yes, by Text Messaging') %]<br/>
+                <blockquote>
+                    [% INCLUDE "opac/parts/sms_carrier_selector.tt2" %]<br/>
+                    [% INCLUDE "opac/parts/sms_number_textbox.tt2" %]<br/>
+                </blockquote>
+                [% END %]
+            </blockquote>
+        </p>
+        <p>
+            [% |l %]If you use the Traveling Library Center (TLC) and ABC Express
+            services, please select "Outreach" to have the item delivered
+            during your scheduled visit.[% END %]
+        </p>
         <input type="submit" name="submit" value="[% l('Submit') %]" title="[% l('Submit') %]"
             alt="[% l('Submit') %]" class="opac-button" />
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
diff --git a/Open-ILS/src/templates/opac/parts/record/copy_table.tt2 b/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
index f6af5b1..c06c694 100644
--- a/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
+++ b/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
@@ -48,7 +48,7 @@ END;
                 org_name | html
             -%]
             </td>
-            <td header='copy_header_callnumber'>[% callnum | html %]</td>
+            <td header='copy_header_callnumber'>[% callnum | html %] [% IF ctx.get_org_setting(CGI.param('loc') OR ctx.aou_tree.id, 'sms.enable') == 1 %](<a href="[% mkurl(ctx.opac_root _ '/sms_cn', {copy_id => copy_info.id}) %]">Text</a>)[% END %]</td>
             [%- IF has_parts == 'true' %]
             <td header='copy_header_part'>[% copy_info.part_label | html %]</td>
             [%- END %]
diff --git a/Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2 b/Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2
new file mode 100644
index 0000000..919ba07
--- /dev/null
+++ b/Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2
@@ -0,0 +1,28 @@
+[%
+    setting = 'opac.default_sms_carrier';
+    IF ctx.user_setting_map.$setting;
+        default_carrier = ctx.user_setting_map.$setting;
+    END;
+
+    temp = ctx.search_csc('active','t');
+
+    # turn the list of objects into a list of hashes to
+    # leverage TT's array.sort('<hashkey>') behavior
+    carriers = [];
+    FOR o IN temp;
+        carriers.push({
+            id => o.id,
+            region => o.region,
+            name => o.name
+        });
+    END;
+%]
+[% IF NOT sms_carrier_hide_label; l('Mobile carrier:'); END; %]
+<select name="sms_carrier">
+    [% FOR carrier IN carriers.sort('name','region') -%]
+    <option value='[% carrier.id | html %]'[%
+        default_carrier == carrier.id ? ' selected="selected"' : ''
+    %]>[% carrier.name | html %] ([% carrier.region | html %])</option>
+    [% END -%]
+</select>
+[% IF NOT sms_carrier_hide_warning; l('Note: carrier charges may apply'); END; %]
diff --git a/Open-ILS/src/templates/opac/parts/sms_number_textbox.tt2 b/Open-ILS/src/templates/opac/parts/sms_number_textbox.tt2
new file mode 100644
index 0000000..e8d7eb8
--- /dev/null
+++ b/Open-ILS/src/templates/opac/parts/sms_number_textbox.tt2
@@ -0,0 +1,4 @@
+[% IF NOT sms_number_hide_label; l('Mobile number:'); END; %]
+<input type="text" name="sms_notify" [% setting = 'opac.default_sms_notify';
+IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+[% IF NOT sms_number_hide_hint; l('Hint: use the full 10 digits of your phone #, no spaces, no dashes'); END; %]
diff --git a/Open-ILS/src/templates/opac/sms_cn.tt2 b/Open-ILS/src/templates/opac/sms_cn.tt2
new file mode 100644
index 0000000..6a1dc16
--- /dev/null
+++ b/Open-ILS/src/templates/opac/sms_cn.tt2
@@ -0,0 +1,49 @@
+[%  PROCESS "opac/parts/header.tt2";
+    WRAPPER "opac/parts/base.tt2";
+    INCLUDE "opac/parts/topnav.tt2";
+    ctx.page_title = l("Send Call Number via Text/SMS") %]
+    <div id="search-wrapper">
+        [% INCLUDE "opac/parts/searchbar.tt2" %]
+    </div>
+    <div id="content-wrapper">
+        <div id="main-content">
+            <div class="common-full-pad"></div>
+            <div>
+                <p>
+                    [% IF ctx.event != -1 %]
+                    <br/>
+                    [% IF ctx.sms_notify %]
+                    <h1>Your message has been sent!</h1>
+                    <a href="[% ctx.origin %]">Return to record</a>
+                    <pre>[% ctx.event.template_output.data %]</pre>
+                    [% ELSE %]
+                    <h1>Text call number</h1>
+                    <a href="[% ctx.origin %]">Return to record</a>
+                    <pre>[% ctx.event.template_output.data %]</pre>
+                    <blockquote>
+                        <form method="POST">
+                            <blockquote>
+                                <input type="hidden" name="copy_id" value="[% ctx.copy_id %]"/>
+                                <input type="hidden" name="origin" value="[% ctx.origin %]"/>
+                                [% INCLUDE "opac/parts/sms_carrier_selector.tt2" sms_carrier_hide_warning="true" %]<br/>
+                                [% INCLUDE "opac/parts/sms_number_textbox.tt2" %]<br/>
+                                <input type="submit"
+                                    name="submit"
+                                    value="[% l('Submit') %]"
+                                    title="[% l('Submit') %]"
+                                    alt="[% l('Submit') %]"
+                                    class="opac-button" />
+                                <br/>[% l('Note: carrier charges may apply'); %]
+                            </blockquote>
+                        </form>
+                    </blockquote>
+                    [% END %]
+                    [% ELSE %]
+                    <span>SMS not enabled for this site.</span>
+                    [% END %]
+                </p>
+            </div>
+            <div class="common-full-pad"></div>
+        </div>
+    </div>
+[% END %]
diff --git a/Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js b/Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js
new file mode 100644
index 0000000..80407e5
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js
@@ -0,0 +1,46 @@
+dojo.require('dojox.grid.DataGrid');
+dojo.require('openils.widget.AutoGrid');
+dojo.require('dojox.grid.cells.dijit');
+dojo.require('dojo.data.ItemFileWriteStore');
+dojo.require('dijit.Dialog');
+dojo.require('openils.PermaCrud');
+
+var thingList;
+
+function thingInit() {
+
+    thingGrid.disableSelectorForRow = function(rowIdx) {
+        var item = thingGrid.getItem(rowIdx);
+        return (thingGrid.store.getValue(item, 'id') < 0);
+    }
+
+    buildGrid();
+}
+
+function buildGrid() {
+
+    fieldmapper.standardRequest(
+        ['open-ils.pcrud', 'open-ils.pcrud.search.csc.atomic'],
+        {   async: true,
+            params: [
+                openils.User.authtoken,
+                {"id":{"!=":null}},
+                {"order_by":{"csc":"name"}}
+            ],
+            oncomplete: function(r) {
+                if(thingList = openils.Util.readResponse(r)) {
+                    thingList = openils.Util.objectSort(thingList,'name');
+                    dojo.forEach(thingList,
+                                 function(e) {
+                                     thingGrid.store.newItem(csc.toStoreItem(e));
+                                 }
+                                );
+                }
+            }
+        }
+    );
+}
+
+openils.Util.addOnLoad(thingInit);
+
+
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index c26e5b8..e2bc1bb 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -729,6 +729,7 @@
 <!ENTITY staff.main.menu.admin.server_admin.conify.acn_prefix.label "Call Number Prefixes">
 <!ENTITY staff.main.menu.admin.server_admin.conify.acn_suffix.label "Call Number Suffixes">
 <!ENTITY staff.main.menu.admin.server_admin.conify.billing_type.label "Billing Types">
+<!ENTITY staff.main.menu.admin.server_admin.conify.sms_carrier.label "SMS Carriers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.z3950_source.label "Z39.50 Servers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.circulation_modifier.label "Circulation Modifiers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.org_unit_setting_type "Organization Unit Setting Types">
@@ -2510,8 +2511,12 @@
 <!ENTITY staff.circ.holds.retrieve_patron.accesskey "P">
 <!ENTITY staff.circ.holds.edit_pickup_library "Edit Pickup Library">
 <!ENTITY staff.circ.holds.edit_pickup_library.accesskey "L">
-<!ENTITY staff.circ.holds.edit_phone_notification "Edit Phone Notification">
+<!ENTITY staff.circ.holds.edit_phone_notification "Edit Phone Number">
 <!ENTITY staff.circ.holds.edit_phone_notification.accesskey "P">
+<!ENTITY staff.circ.holds.edit_sms_notification "Edit Mobile/Text Number">
+<!ENTITY staff.circ.holds.edit_sms_notification.accesskey "T">
+<!ENTITY staff.circ.holds.edit_sms_carrier_notification "Edit Mobile/Text Carrier">
+<!ENTITY staff.circ.holds.edit_sms_carrier_notification.accesskey "C">
 <!ENTITY staff.circ.holds.set_email_notification "Set Email Notification">
 <!ENTITY staff.circ.holds.set_email_notification.accesskey "E">
 <!ENTITY staff.circ.holds.edit_activation_date "Edit Activation Date">
diff --git a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
index c256315..0dda428 100644
--- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
+++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
@@ -436,7 +436,7 @@ OpenILS.data.prototype = {
                 },
                 'hold_slip' : {
                     'type' : 'holds',
-                    'header' : 'This item needs to be routed to <b>%route_to%</b>:<br/>\r\nBarcode: %item_barcode%<br/>\r\nTitle: %item_title%<br/>\r\n<br/>\r\n%hold_for_msg%<br/>\r\nBarcode: %PATRON_BARCODE%<br/>\r\nNotify by phone: %notify_by_phone%<br/>\r\nNotify by email: %notify_by_email%<br/>\r\n',
+                    'header' : 'This item needs to be routed to <b>%route_to%</b>:<br/>\r\nBarcode: %item_barcode%<br/>\r\nTitle: %item_title%<br/>\r\n<br/>\r\n%hold_for_msg%<br/>\r\nBarcode: %PATRON_BARCODE%<br/>\r\nNotify by phone: %notify_by_phone%<br/>\r\nNotified by text: %notify_by_text%<br/>\r\nNotified by email: %notify_by_email%<br/>\r\n',
                     'line_item' : '%formatted_note%<br/>\r\n',
                     'footer' : '<br/>\r\nRequest date: %request_date%<br/>\r\nSlip Date: %TODAY_TRIM%<br/>\r\nPrinted by %STAFF_FIRSTNAME% at %SHORTNAME%<br/>\r\n<br/>\r\n'
                 },
@@ -448,7 +448,7 @@ OpenILS.data.prototype = {
                 },
                 'hold_transit_slip' : {
                     'type' : 'transits',
-                    'header' : 'This item needs to be routed to <b>%route_to%</b>:<br/>\r\n%route_to_org_fullname%<br/>\r\n%street1%<br/>\r\n%street2%<br/>\r\n%city_state_zip%<br/>\r\n<br/>\r\nBarcode: %item_barcode%<br/>\r\nTitle: %item_title%<br/>\r\nAuthor: %item_author%<br>\r\n<br/>\r\n%hold_for_msg%<br/>\r\nBarcode: %PATRON_BARCODE%<br/>\r\nNotify by phone: %notify_by_phone%<br/>\r\nNotify by email: %notify_by_email%<br/>\r\n',
+                    'header' : 'This item needs to be routed to <b>%route_to%</b>:<br/>\r\n%route_to_org_fullname%<br/>\r\n%street1%<br/>\r\n%street2%<br/>\r\n%city_state_zip%<br/>\r\n<br/>\r\nBarcode: %item_barcode%<br/>\r\nTitle: %item_title%<br/>\r\nAuthor: %item_author%<br>\r\n<br/>\r\n%hold_for_msg%<br/>\r\nBarcode: %PATRON_BARCODE%<br/>\r\nNotify by phone: %notify_by_phone%<br/>\r\nNotified by text: %notify_by_text%<br/>\r\nNotified by email: %notify_by_email%<br/>\r\n',
                     'line_item' : '%formatted_note%<br/>\r\n',
                     'footer' : '<br/>\r\nRequest date: %request_date%<br/>\r\nSlip Date: %TODAY_TRIM%<br/>\r\nPrinted by %STAFF_FIRSTNAME% at %SHORTNAME%<br/>\r\n<br/>\r\n'
                 },
@@ -926,6 +926,27 @@ OpenILS.data.prototype = {
         this.chain.push(
             function() {
                 var f = gen_fm_retrieval_func(
+                    'csc',
+                    [
+                        api.FM_CSC_RETRIEVE_VIA_PCRUD.app,
+                        api.FM_CSC_RETRIEVE_VIA_PCRUD.method,
+                        [ obj.session.key, {"id":{"!=":null}}, {"order_by":{"csc":"name"}} ],
+                        false
+                    ]
+                );
+                try {
+                    f();
+                } catch(E) {
+                    var error = 'Error: ' + js2JSON(E);
+                    obj.error.sdump('D_ERROR',error);
+                    throw(E);
+                }
+            }
+        );
+
+        this.chain.push(
+            function() {
+                var f = gen_fm_retrieval_func(
                     'acnp',
                     [
                         api.FM_ACNP_RETRIEVE_VIA_PCRUD.app,
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/constants.js b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
index f7ecfe0..4120b6f 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/constants.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
@@ -258,6 +258,7 @@ var api = {
     'FM_CNAL_RETRIEVE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.net_access_level.retrieve.all', 'secure' : false },
     'FM_CNCT_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.non_cat_types.retrieve.all', 'secure' : false },
     'FM_CRAHP_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.config.rules.age_hold_protect.retrieve.all', 'secure' : false },
+    'FM_CSC_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.csc.atomic' },
     'FM_CSP_PCRUD_SEARCH' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.csp.atomic', 'secure' : false },
     'FM_CST_RETRIEVE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.standings.retrieve', 'secure' : false },
     'FM_MB_CREATE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.money.billing.create' },
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index ac82ea4..402acb0 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -1000,6 +1000,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/acq/distribution_formula', null, event); }
             ],
+            'cmd_server_admin_sms_carrier' : [
+                ['oncommand'],
+                function(event) { open_eg_web_page('conify/global/config/sms_carrier', null, event); }
+            ],
             'cmd_server_admin_z39_source' : [
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/config/z3950_source', null, event); }
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index 923000b..dd1f479 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -190,6 +190,7 @@
     <command id="cmd_server_admin_acq_currency_type" />
     <command id="cmd_server_admin_acq_exchange_rate" />
     <command id="cmd_server_admin_acq_distrib_formula" />
+    <command id="cmd_server_admin_sms_carrier" />
     <command id="cmd_server_admin_z39_source" />
     <command id="cmd_server_admin_circ_mod" 
              perm="CREATE_CIRC_MOD DELETE_CIRC_MOD UPDATE_CIRC_MOD ADMIN_CIRC_MOD"
@@ -518,6 +519,7 @@
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.coded_value_maps.label;" command="cmd_server_admin_coded_value_map"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.metabib_field.label;" command="cmd_server_admin_metabib_field"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.billing_type.label;" command="cmd_server_admin_billing_type"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.sms_carrier.label;" command="cmd_server_admin_sms_carrier"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_source.label;" command="cmd_server_admin_z39_source"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_modifier.label;" command="cmd_server_admin_circ_mod"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.global_flag.label;" command="cmd_server_admin_global_flag"/>
diff --git a/Open-ILS/xul/staff_client/server/circ/util.js b/Open-ILS/xul/staff_client/server/circ/util.js
index 3b16945..de7f0e3 100644
--- a/Open-ILS/xul/staff_client/server/circ/util.js
+++ b/Open-ILS/xul/staff_client/server/circ/util.js
@@ -2146,6 +2146,24 @@ circ.util.hold_columns = function(modify,params) {
         },
         {
             'persist' : 'hidden width ordinal',
+            'id' : 'sms_notify',
+            'label' : document.getElementById('commonStrings').getString('staff.ahr_sms_notify_label'),
+            'flex' : 1,
+            'primary' : false,
+            'hidden' : true,
+            'editable' : false, 'render' : function(my) { return my.ahr.sms_notify(); }
+        },
+        {
+            'persist' : 'hidden width ordinal',
+            'id' : 'sms_carrier',
+            'label' : document.getElementById('commonStrings').getString('staff.ahr_sms_carrier_label'),
+            'flex' : 1,
+            'primary' : false,
+            'hidden' : true,
+            'editable' : false, 'render' : function(my) { return data.hash.csc[ my.ahr.sms_carrier() ].name(); }
+        },
+        {
+            'persist' : 'hidden width ordinal',
             'id' : 'prev_check_time',
             'label' : document.getElementById('commonStrings').getString('staff.ahr_prev_check_time_label'),
             'flex' : 1,
@@ -2931,6 +2949,12 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
                             msg += print_data.notify_by_phone_msg;
                             msg += '\n';
                         }
+                        if (check.payload.hold.sms_notify()) {
+                            print_data.notify_by_text_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.payload.hold.sms_notify', [check.payload.hold.sms_notify()]);
+                            print_data.notify_by_text = check.payload.hold.sms_notify();
+                            msg += print_data.notify_by_text_msg;
+                            msg += '\n';
+                        }
                         if (get_bool(check.payload.hold.email_notify())) {
                             var payload_email = au_obj.email() ? au_obj.email() : '';
                             print_data.notify_by_email_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.payload.hold.email_notify', [payload_email]);
@@ -3309,6 +3333,12 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
                     msg += print_data.notify_by_phone_msg;
                     msg += '\n';
                 }
+                if (check.payload.hold.sms_notify()) {
+                    print_data.notify_by_text_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.payload.hold.sms_notify', [check.payload.hold.sms_notify()]);
+                    print_data.notify_by_text = check.payload.hold.sms_notify();
+                    msg += print_data.notify_by_text_msg;
+                    msg += '\n';
+                }
                 if (get_bool(check.payload.hold.email_notify())) {
                     var payload_email = au_obj.email() ? au_obj.email() : '';
                     print_data.notify_by_email_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.payload.hold.email_notify', [payload_email]);
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/circ.properties b/Open-ILS/xul/staff_client/server/locale/en-US/circ.properties
index a8d2ce3..2dc7a10 100644
--- a/Open-ILS/xul/staff_client/server/locale/en-US/circ.properties
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/circ.properties
@@ -364,6 +364,7 @@ staff.circ.utils.holds.shelf_expire_time=Shelf Expire Time
 staff.circ.utils.payload.hold.patron=Hold for patron %1$s, %2$s %3$s
 staff.circ.utils.payload.hold.patron_alias=Hold for patron %1$s
 staff.circ.utils.payload.hold.phone_notify=Notify by phone: %1$s
+staff.circ.utils.payload.hold.sms_notify=Notify by text: %1$s
 staff.circ.utils.payload.hold.email_notify=Notify by email: %1$s
 staff.circ.utils.payload.hold.request_date=Request Date: %1$s
 staff.circ.utils.payload.hold.slip_date=Slip Date: %1$s
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/common.properties b/Open-ILS/xul/staff_client/server/locale/en-US/common.properties
index f73ab08..ce0ac8e 100644
--- a/Open-ILS/xul/staff_client/server/locale/en-US/common.properties
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/common.properties
@@ -57,6 +57,8 @@ staff.ahr_holdable_part_label=Holdable Part
 staff.ahr_issuance_label_label=Issuance Label
 staff.ahr_id_label=Hold ID
 staff.ahr_phone_notify_label=Phone Notify
+staff.ahr_sms_notify_label=Text Notify
+staff.ahr_sms_carrier_label=Text Carrier
 staff.ahr_pickup_lib_label=Pickup Library
 staff.ahr_prev_check_time_label=Previous Check Time
 staff.ahr_requestor_label=Requestor
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties b/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
index 4f0ced6..f910d37 100644
--- a/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
@@ -171,6 +171,24 @@ staff.patron.holds.holds_edit_phone_notify.choose_phone_number=Choose a Hold Not
 staff.patron.holds.holds_edit_phone_notify.confirm_phone_number_change.singular=Are you sure you would like to change the Notification Phone Number for hold %1$s to "%2$s"?
 staff.patron.holds.holds_edit_phone_notify.confirm_phone_number_change.plural=Are you sure you would like to change the Notification Phone Number for holds %1$s to "%2$s"?
 staff.patron.holds.holds_edit_phone_notify.modifying_holds_title=Modifying Holds
+staff.patron.holds.holds_edit_sms_notify.new_phone_number=Please enter a new mobile number for hold notification via text (leave the field empty to disable):
+staff.patron.holds.holds_edit_sms_notify.btn_done.label=Done
+staff.patron.holds.holds_edit_sms_notify.btn_done.accesskey=D
+staff.patron.holds.holds_edit_sms_notify.btn_cancel.label=Cancel
+staff.patron.holds.holds_edit_sms_notify.btn_cancel.accesskey=C
+staff.patron.holds.holds_edit_sms_notify.choose_phone_number=Choose a Hold Notification Mobile Text Number
+staff.patron.holds.holds_edit_sms_notify.confirm_phone_number_change.singular=Are you sure you would like to change the Notification Mobile/Text Number for hold %1$s to "%2$s"?
+staff.patron.holds.holds_edit_sms_notify.confirm_phone_number_change.plural=Are you sure you would like to change the Notification Mobile/Text Number for holds %1$s to "%2$s"?
+staff.patron.holds.holds_edit_sms_notify.modifying_holds_title=Modifying Holds
+staff.patron.holds.holds_edit_sms_carrier.new_carrier=Please select a new mobile carrier for hold notification via text:
+staff.patron.holds.holds_edit_sms_carrier.btn_done.label=Done
+staff.patron.holds.holds_edit_sms_carrier.btn_done.accesskey=D
+staff.patron.holds.holds_edit_sms_carrier.btn_cancel.label=Cancel
+staff.patron.holds.holds_edit_sms_carrier.btn_cancel.accesskey=C
+staff.patron.holds.holds_edit_sms_carrier.choose_carrier=Select a Hold Notification Mobile Text Carrier
+staff.patron.holds.holds_edit_sms_carrier.confirm_carrier_change.singular=Are you sure you would like to change the Notification Mobile/Text Carrier for hold %1$s to "%2$s"?
+staff.patron.holds.holds_edit_sms_carrier.confirm_carrier_change.plural=Are you sure you would like to change the Notification Mobile/Text Carrier for holds %1$s to "%2$s"?
+staff.patron.holds.holds_edit_sms_carrier.modifying_holds_title=Modifying Holds
 staff.patron.holds.holds_edit_email_notify.description=Send email notifications (when appropriate)?  The email address used is found in the hold recipient account.
 staff.patron.holds.holds_edit_email_notify.btn_email.label=Email
 staff.patron.holds.holds_edit_email_notify.btn_email.accesskey=E
diff --git a/Open-ILS/xul/staff_client/server/patron/holds.js b/Open-ILS/xul/staff_client/server/patron/holds.js
index 190d630..bec92d3 100644
--- a/Open-ILS/xul/staff_client/server/patron/holds.js
+++ b/Open-ILS/xul/staff_client/server/patron/holds.js
@@ -598,6 +598,133 @@ patron.holds.prototype = {
                             }
                         }
                     ],
+
+                    'cmd_holds_edit_sms_notify' : [
+                        ['command'],
+                        function() {
+                            try {
+                                var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: vertical">';
+                                xml += '<description>'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.new_phone_number')+'</description>';
+                                xml += '<textbox id="phone" name="fancy_data" context="clipboard"/>';
+                                xml += '</vbox>';
+                                var bot_xml = '<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: vertical">';
+                                bot_xml += '<spacer flex="1"/><button label="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.btn_done.label')+'"';
+                                bot_xml += ' accesskey="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.btn_done.accesskey')+'" name="fancy_submit"/>';
+                                bot_xml += '<button label="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.btn_cancel.label')+'"';
+                                bot_xml += ' accesskey="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.btn_cancel.accesskey')+'" name="fancy_cancel"/></hbox>';
+                                netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect UniversalBrowserWrite');
+                                //obj.data.temp_mid = xml; obj.data.stash('temp_mid');
+                                //obj.data.temp_bot = bot_xml; obj.data.stash('temp_bot');
+                                JSAN.use('util.window'); var win = new util.window();
+                                var fancy_prompt_data = win.open(
+                                    urls.XUL_FANCY_PROMPT,
+                                    //+ '?xml_in_stash=temp_mid'
+                                    //+ '&bottom_xml_in_stash=temp_bot'
+                                    //+ '&title=' + window.escape('Choose a Hold Notification Phone Number')
+                                    //+ '&focus=phone',
+                                    'fancy_prompt', 'chrome,resizable,modal',
+                                    { 'xml' : xml, 'bottom_xml' : bot_xml, 'title' : $("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.choose_phone_number'), 'focus' : 'phone' }
+                                );
+                                if (fancy_prompt_data.fancy_status == 'incomplete') { return; }
+                                var phone = fancy_prompt_data.phone;
+
+                                var hold_list = util.functional.map_list(obj.retrieve_ids, function(o){return o.id;});
+                                var msg = '';
+                                if(obj.retrieve_ids.length > 1) {
+                                    msg = $("patronStrings").getFormattedString('staff.patron.holds.holds_edit_sms_notify.confirm_phone_number_change.plural',[hold_list.join(', '), phone]);
+                                } else {
+                                    msg = $("patronStrings").getFormattedString('staff.patron.holds.holds_edit_sms_notify.confirm_phone_number_change.singular',[hold_list.join(', '), phone]);
+                                }
+                                var r = obj.error.yns_alert(msg,
+                                        $("patronStrings").getString('staff.patron.holds.holds_edit_sms_notify.modifying_holds_title'),
+                                        $("commonStrings").getString('common.yes'),
+                                        $("commonStrings").getString('common.no'),
+                                        null,
+                                        $("commonStrings").getString('common.check_to_confirm')
+                                );
+                                if (r == 0) {
+                                    var hparams = {
+                                        'sms_notify' : phone == '' ? null : phone 
+                                    }
+                                    if (phone == '') {
+                                        hparams.sms_carrier = null;
+                                    }
+                                    circ.util.batch_hold_update(hold_list, hparams, { 'progressmeter' : progressmeter, 'oncomplete' :  function() { obj.clear_and_retrieve(true); } });
+                                }
+                            } catch(E) {
+                                obj.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.holds.holds_not_modified'),E);
+                            }
+                        }
+                    ],
+
+                    'cmd_holds_edit_sms_carrier' : [
+                        ['command'],
+                        function() {
+                            try {
+                                JSAN.use('util.widgets'); JSAN.use('util.functional');
+
+                                var list = util.functional.map_list(
+                                    obj.data.list.csc,
+                                    function(o) {
+                                        return [
+                                            o.name() + ' (' + o.region() + ')',
+                                            o.id(),
+                                            ( !isTrue(o.active()) ),
+                                            0
+                                        ];
+                                    }
+                                );
+                                ml = util.widgets.make_menulist( list, obj.data.list.au[0].ws_ou() );
+                                ml.setAttribute('id','carrier');
+                                ml.setAttribute('name','fancy_data');
+                                var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: vertical">';
+                                xml += '<description>'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.new_carrier')+'</description>';
+                                xml += util.widgets.serialize_node(ml);
+                                xml += '</vbox>';
+                                var bot_xml = '<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: vertical">';
+                                bot_xml += '<spacer flex="1"/><button label="'+ $("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.btn_done.label') +'"';
+                                bot_xml += ' accesskey="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.btn_done.accesskey')+'" name="fancy_submit"/>';
+                                bot_xml += '<button label="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.btn_cancel.label')+'"';
+                                bot_xml += ' accesskey="'+$("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.btn_cancel.accesskey')+'" name="fancy_cancel"/></hbox>';
+                                netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect UniversalBrowserWrite');
+                                //obj.data.temp_mid = xml; obj.data.stash('temp_mid');
+                                //obj.data.temp_bot = bot_xml; obj.data.stash('temp_bot');
+                                JSAN.use('util.window'); var win = new util.window();
+                                var fancy_prompt_data = win.open(
+                                    urls.XUL_FANCY_PROMPT,
+                                    //+ '?xml_in_stash=temp_mid'
+                                    //+ '&bottom_xml_in_stash=temp_bot'
+                                    //+ '&title=' + window.escape('Choose a Pick Up Library'),
+                                    'fancy_prompt', 'chrome,resizable,modal',
+                                    { 'xml' : xml, 'bottom_xml' : bot_xml, 'title' : $("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.choose_carrier') }
+                                );
+                                if (fancy_prompt_data.fancy_status == 'incomplete') { return; }
+                                var sms_carrier = fancy_prompt_data.carrier;
+
+                                var hold_list = util.functional.map_list(obj.retrieve_ids, function(o){return o.id;});
+                                var msg = '';
+
+                                if(obj.retrieve_ids.length > 1) {
+                                    msg = $("patronStrings").getFormattedString('staff.patron.holds.holds_edit_sms_carrier.confirm_carrier_change.plural',[hold_list.join(', '), obj.data.hash.csc[sms_carrier].name()]);
+                                } else {
+                                    msg = $("patronStrings").getFormattedString('staff.patron.holds.holds_edit_sms_carrier.confirm_carrier_change.singular',[hold_list.join(', '), obj.data.hash.csc[sms_carrier].name()]);
+                                }
+                                var r = obj.error.yns_alert(msg,
+                                        $("patronStrings").getString('staff.patron.holds.holds_edit_sms_carrier.modifying_holds_title'),
+                                        $("commonStrings").getString('common.yes'),
+                                        $("commonStrings").getString('common.no'),
+                                        null,
+                                        $("commonStrings").getString('common.check_to_confirm')
+                                );
+                                if (r == 0) {
+                                    circ.util.batch_hold_update(hold_list, { 'sms_carrier' : sms_carrier }, { 'progressmeter' : progressmeter, 'oncomplete' :  function() { obj.clear_and_retrieve(true); } });
+                                }
+                            } catch(E) {
+                                obj.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.holds.holds_not_modified'),E);
+                            }
+                        }
+                    ],
+
                     'cmd_holds_edit_email_notify' : [
                         ['command'],
                         function() {
diff --git a/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul b/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul
index ce36619..1e05002 100644
--- a/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul
+++ b/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul
@@ -25,6 +25,12 @@
         <command id="cmd_holds_edit_desire_mint_condition" />
         <command id="cmd_holds_edit_pickup_lib" />
         <command id="cmd_holds_edit_phone_notify" />
+        <command id="cmd_holds_edit_sms_notify"
+            label="&staff.circ.holds.edit_sms_notification;"
+            accesskey="&staff.circ.holds.edit_sms_notification.accesskey;"/>
+        <command id="cmd_holds_edit_sms_carrier"
+            label="&staff.circ.holds.edit_sms_carrier_notification;"
+            accesskey="&staff.circ.holds.edit_sms_carrier_notification.accesskey;"/>
         <command id="cmd_holds_edit_email_notify" />
         <command id="cmd_holds_edit_thaw_date" />
         <command id="cmd_holds_edit_request_date" />
@@ -66,6 +72,8 @@
             <menuitem label="&staff.circ.holds.edit_desire_mint_condition;" command="cmd_holds_edit_desire_mint_condition" accesskey="&staff.circ.holds.edit_desire_mint_condition.accesskey;"/>
             <menuitem label="&staff.circ.holds.edit_pickup_library;" command="cmd_holds_edit_pickup_lib" accesskey="&staff.circ.holds.edit_pickup_library.accesskey;"/>
             <menuitem label="&staff.circ.holds.edit_phone_notification;" command="cmd_holds_edit_phone_notify" accesskey="&staff.circ.holds.edit_phone_notification.accesskey;"/>
+            <menuitem command="cmd_holds_edit_sms_carrier" />
+            <menuitem command="cmd_holds_edit_sms_notify" />
             <menuitem label="&staff.circ.holds.set_email_notification;" command="cmd_holds_edit_email_notify" accesskey="&staff.circ.holds.set_email_notification.accesskey;"/>
             <menuitem label="&staff.circ.holds.edit_expire_time;" command="cmd_holds_edit_expire_time" accesskey="&staff.circ.holds.edit_expire_time.accesskey;"/>
             <menuitem label="&staff.circ.holds.edit_shelf_expire_time;" command="cmd_holds_edit_shelf_expire_time" accesskey="&staff.circ.holds.edit_shelf_expire_time.accesskey;"/>
@@ -149,6 +157,8 @@
                     <menuitem label="&staff.circ.holds.edit_desire_mint_condition;" command="cmd_holds_edit_desire_mint_condition" accesskey="&staff.circ.holds.edit_desire_mint_condition.accesskey;"/>
                     <menuitem label="&staff.circ.holds.edit_pickup_library;" command="cmd_holds_edit_pickup_lib" accesskey="&staff.circ.holds.edit_pickup_library.accesskey;"/>
                     <menuitem label="&staff.circ.holds.edit_phone_notification;" command="cmd_holds_edit_phone_notify" accesskey="&staff.circ.holds.edit_phone_notification.accesskey;"/>
+                    <menuitem command="cmd_holds_edit_sms_carrier" />
+                    <menuitem command="cmd_holds_edit_sms_notify" />
                     <menuitem label="&staff.circ.holds.set_email_notification;" command="cmd_holds_edit_email_notify" accesskey="&staff.circ.holds.set_email_notification.accesskey;"/>
                     <menuitem label="&staff.circ.holds.edit_expire_time;" command="cmd_holds_edit_expire_time" accesskey="&staff.circ.holds.edit_expire_time.accesskey;"/>
                     <menuitem label="&staff.circ.holds.edit_shelf_expire_time;" command="cmd_holds_edit_shelf_expire_time" accesskey="&staff.circ.holds.edit_shelf_expire_time.accesskey;"/>

commit 764544d0cbad934d6fc0c673a032755fd34eb2e5
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Sep 29 09:26:49 2011 -0400

    flesh settings when retrieving patron by barcode
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
index 93f1a75..c646769 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
@@ -465,7 +465,8 @@ sub flesh_user {
 		"addresses",
 		"billing_address",
 		"mailing_address",
-		"stat_cat_entries"
+		"stat_cat_entries",
+		"settings"
     ];
     push @$fields, "home_ou" if $home_ou;
 	return new_flesh_user($id, $fields, $e );

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   22 +
 .../src/perlmods/lib/OpenILS/Application/Actor.pm  |    3 +-
 .../src/perlmods/lib/OpenILS/Application/Cat.pm    |   69 +
 .../lib/OpenILS/Application/Trigger/Reactor.pm     |   60 +
 .../OpenILS/Application/Trigger/Reactor/SendSMS.pm |   21 +
 .../src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm    |    8 +
 .../lib/OpenILS/WWW/EGCatLoader/Account.pm         |  114 ++-
 .../perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm    |   57 +
 Open-ILS/src/sql/Pg/002.schema.config.sql          |   10 +-
 Open-ILS/src/sql/Pg/090.schema.action.sql          |    7 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       | 1368 +++++++++++++++++++-
 .../sql/Pg/upgrade/0666.schema.sms_carriers.sql    | 1411 ++++++++++++++++++++
 .../templates/conify/global/config/sms_carrier.tt2 |   27 +
 .../src/templates/opac/myopac/prefs_notify.tt2     |   67 +-
 Open-ILS/src/templates/opac/parts/place_hold.tt2   |   29 +
 .../src/templates/opac/parts/record/copy_table.tt2 |    2 +-
 .../templates/opac/parts/sms_carrier_selector.tt2  |   28 +
 .../templates/opac/parts/sms_number_textbox.tt2    |    4 +
 Open-ILS/src/templates/opac/sms_cn.tt2             |   49 +
 .../ui/default/conify/global/config/sms_carrier.js |   46 +
 Open-ILS/web/opac/locale/en-US/lang.dtd            |    7 +-
 .../staff_client/chrome/content/OpenILS/data.js    |   25 +-
 .../staff_client/chrome/content/main/constants.js  |    1 +
 .../xul/staff_client/chrome/content/main/menu.js   |    4 +
 .../chrome/content/main/menu_frame_menus.xul       |    2 +
 Open-ILS/xul/staff_client/server/circ/util.js      |   30 +
 .../server/locale/en-US/circ.properties            |    1 +
 .../server/locale/en-US/common.properties          |    2 +
 .../server/locale/en-US/patron.properties          |   18 +
 Open-ILS/xul/staff_client/server/patron/holds.js   |  127 ++
 .../staff_client/server/patron/holds_overlay.xul   |   10 +
 31 files changed, 3610 insertions(+), 19 deletions(-)
 create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm
 create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0666.schema.sms_carriers.sql
 create mode 100644 Open-ILS/src/templates/conify/global/config/sms_carrier.tt2
 create mode 100644 Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2
 create mode 100644 Open-ILS/src/templates/opac/parts/sms_number_textbox.tt2
 create mode 100644 Open-ILS/src/templates/opac/sms_cn.tt2
 create mode 100644 Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list