[freeside-commits] branch FREESIDE_3_BRANCH updated. 6a38d3f3aa3e7953d3da2c6a48466bf600366732

Mark Wells mark at 420.am
Wed Jul 6 13:32:24 PDT 2016


The branch, FREESIDE_3_BRANCH has been updated
       via  6a38d3f3aa3e7953d3da2c6a48466bf600366732 (commit)
       via  6bed58c0b8187d22efb7cd0aba9d62b6a3b4ca32 (commit)
       via  0141b002ab937b2b3a21a92c3728bc3101f4668a (commit)
       via  d49458237ef94dc89633846c0bdca56be2f34264 (commit)
      from  f2c6964e324bce1113c90af0a09bc92c74f76aee (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 6a38d3f3aa3e7953d3da2c6a48466bf600366732
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Jul 6 12:46:38 2016 -0700

    backport fixes

diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index 01b8e10..60889c6 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -635,6 +635,12 @@ sub svc_locale {
   $part_svc_msgcat->svc;
 }
 
+# 3.x stub
+sub part_svc_msgcat {
+  my $self = shift;
+  qsearch('part_svc_msgcat', { 'svcpart' => $self->svcpart });
+}
+
 =back
 
 =head1 CLASS METHODS
diff --git a/FS/FS/part_svc_msgcat.pm b/FS/FS/part_svc_msgcat.pm
index 6d69198..796f9d8 100644
--- a/FS/FS/part_svc_msgcat.pm
+++ b/FS/FS/part_svc_msgcat.pm
@@ -3,6 +3,7 @@ use base qw( FS::Record );
 
 use strict;
 use FS::Locales;
+use FS::part_svc;
 
 =head1 NAME
 
@@ -117,6 +118,12 @@ sub check {
   $self->SUPER::check;
 }
 
+# 3.x stub
+sub part_svc {
+  my $self = shift;
+  FS::part_svc->by_key($self->svcpart);
+}
+
 =back
 
 =head1 BUGS

commit 6bed58c0b8187d22efb7cd0aba9d62b6a3b4ca32
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Jul 6 12:46:38 2016 -0700

    use localized service labels in invoices, #71347

diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 6b23149..ed4bb62 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -3356,6 +3356,7 @@ sub _items_cust_bill_pkg {
 
           # append the word 'Setup' to the setup line if there's going to be
           # a recur line for the same package (i.e. not a one-time charge) 
+          # XXX localization
           my $description = $desc;
           $description .= ' Setup'
             if $cust_bill_pkg->recur != 0
@@ -3376,8 +3377,11 @@ sub _items_cust_bill_pkg {
           # always pass the svc_label through to the template, even if 
           # not displaying it as an ext_description
           my @svc_labels = map &{$escape_function}($_),
-                      $cust_pkg->h_labels_short($self->_date, undef, 'I');
-
+            $cust_pkg->h_labels_short($self->_date,
+                                      undef,
+                                      'I',
+                                      $self->conf->{locale},
+                                     );
           $svc_label = $svc_labels[0];
 
           unless ( $cust_pkg->part_pkg->hide_svc_detail
@@ -3467,7 +3471,9 @@ sub _items_cust_bill_pkg {
           push @dates, undef if !$prev;
 
           my @svc_labels = map &{$escape_function}($_),
-                      $cust_pkg->h_labels_short(@dates, 'I');
+            $cust_pkg->h_labels_short(@dates,
+                                      'I',
+                                      $self->conf->{locale});
           $svc_label = $svc_labels[0];
 
           # show service labels, unless...
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index aa7cb4e..e9feabd 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -3769,23 +3769,27 @@ sub labels {
   map { [ $_->label ] } $self->cust_svc;
 }
 
-=item h_labels END_TIMESTAMP [ START_TIMESTAMP ] [ MODE ]
+=item h_labels END_TIMESTAMP [, START_TIMESTAMP [, MODE [, LOCALE ] ] ]
 
 Like the labels method, but returns historical information on services that
 were active as of END_TIMESTAMP and (optionally) not cancelled before
 START_TIMESTAMP.  If MODE is 'I' (for 'invoice'), services with the 
 I<pkg_svc.hidden> flag will be omitted.
 
-Returns a list of lists, calling the label method for all (historical) services
-(see L<FS::h_cust_svc>) of this billing item.
+If LOCALE is passed, service definition names will be localized.
+
+Returns a list of lists, calling the label method for all (historical)
+services (see L<FS::h_cust_svc>) of this billing item.
 
 =cut
 
 sub h_labels {
   my $self = shift;
-  warn "$me _h_labels called on $self\n"
+  my ($end, $start, $mode, $locale) = @_;
+  warn "$me h_labels\n"
     if $DEBUG;
-  map { [ $_->label(@_) ] } $self->h_cust_svc(@_);
+  map { [ $_->label($end, $start, $locale) ] }
+        $self->h_cust_svc($end, $start, $mode);
 }
 
 =item labels_short
@@ -3798,15 +3802,15 @@ individual services rather than individual items.
 =cut
 
 sub labels_short {
-  shift->_labels_short( 'labels', @_ );
+  shift->_labels_short( 'labels' ); # 'labels' takes no further arguments
 }
 
-=item h_labels_short END_TIMESTAMP [ START_TIMESTAMP ]
+=item h_labels_short END_TIMESTAMP [, START_TIMESTAMP [, MODE [, LOCALE ] ] ]
 
 Like h_labels, except returns a simple flat list, and shortens long
-(currently >5 or the cust_bill-max_same_services configuration value) lists of
-identical services to one line that lists the service label and the number of
-individual services rather than individual items.
+(currently >5 or the cust_bill-max_same_services configuration value) lists
+of identical services to one line that lists the service label and the
+number of individual services rather than individual items.
 
 =cut
 
@@ -3814,6 +3818,9 @@ sub h_labels_short {
   shift->_labels_short( 'h_labels', @_ );
 }
 
+# takes a method name ('labels' or 'h_labels') and all its arguments;
+# maybe should be "shorten($self->h_labels( ... ) )"
+
 sub _labels_short {
   my( $self, $method ) = ( shift, shift );
 
diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
index ca09034..511fcf3 100644
--- a/FS/FS/cust_svc.pm
+++ b/FS/FS/cust_svc.pm
@@ -588,10 +588,10 @@ sub pkg_cancel_date {
   return $cust_pkg->getfield('cancel') || '';
 }
 
-=item label
+=item label [ LOCALE ]
 
 Returns a list consisting of:
-- The name of this service (from part_svc)
+- The name of this service (from part_svc), optionally localized
 - A meaningful identifier (username, domain, or mail alias)
 - The table name (i.e. svc_domain) for this service
 - svcnum
@@ -600,7 +600,7 @@ Usage example:
 
   my($label, $value, $svcdb) = $cust_svc->label;
 
-=item label_long
+=item label_long [ LOCALE ]
 
 Like the B<label> method, except the second item in the list ("meaningful
 identifier") may be longer - typically, a full name is included.
@@ -613,20 +613,25 @@ sub label_long { shift->_label('svc_label_long', @_); }
 sub _label {
   my $self = shift;
   my $method = shift;
+  my $locale = shift;
   my $svc_x = $self->svc_x
     or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
 
-  $self->$method($svc_x);
+  $self->$method($svc_x, undef, undef, $locale);
 }
 
+# svc_label(_long) takes three arguments: end date, start date, locale
+# and FS::svc_*::label methods must accept those also, if they even care
+
 sub svc_label      { shift->_svc_label('label',      @_); }
 sub svc_label_long { shift->_svc_label('label_long', @_); }
 
 sub _svc_label {
   my( $self, $method, $svc_x ) = ( shift, shift, shift );
+  my ($end, $start, $locale) = @_;
 
   (
-    $self->part_svc->svc,
+    $self->part_svc->svc_locale($locale),
     $svc_x->$method(@_),
     $self->part_svc->svcdb,
     $self->svcnum
diff --git a/FS/FS/h_cust_svc.pm b/FS/FS/h_cust_svc.pm
index d280d53..d018507 100644
--- a/FS/FS/h_cust_svc.pm
+++ b/FS/FS/h_cust_svc.pm
@@ -39,14 +39,14 @@ sub date_deleted {
   $self->h_date('delete');
 }
 
-=item label END_TIMESTAMP [ START_TIMESTAMP ] 
+=item label END_TIMESTAMP [ START_TIMESTAMP ] [ LOCALE ]
 
-Returns a label for this historical service, if the service was created before
-END_TIMESTAMP and (optionally) not deleted before START_TIMESTAMP.  Otherwise,
-returns an empty list.
+Returns a label for this historical service, if the service was created
+before END_TIMESTAMP and (optionally) not deleted before START_TIMESTAMP.
+Otherwise, returns an empty list.
 
 If a service is found, returns a list consisting of:
-- The name of this historical service (from part_svc)
+- The name of this historical service (from part_svc), optionally localized
 - A meaningful identifier (username, domain, or mail alias)
 - The table name (i.e. svc_domain) for this historical service
 
@@ -55,13 +55,34 @@ If a service is found, returns a list consisting of:
 sub label      { shift->_label('svc_label',      @_); }
 sub label_long { shift->_label('svc_label_long', @_); }
 
+# Parameters to _label:
+#
+# 1: the cust_svc method we should call to produce the label. (svc_label
+# and svc_label_long are defined in FS::cust_svc, not here, and take a svc_x
+# object as first argument.)
+# 2, 3: date range to use to find the h_svc_x, which will be passed to
+# svc_label(_long) and eventually have ->label called on it.
+# 4: locale, passed to svc_label(_long) also.
+#
+# however, if label is called with a locale only, must DTRT (this is a
+# FS::cust_svc subclass)
+
 sub _label {
   my $self = shift;
   my $method = shift;
+  my ($end, $start, $locale);
+  if (defined($_[0])) {
+    if ( $_[0] =~ /^\d+$/ ) {
+      ($end, $start, $locale) = @_;
+    } else {
+      $locale = shift;
+      $end = $self->history_date;
+    }
+  }
 
   #carp "FS::h_cust_svc::_label called on $self" if $DEBUG;
   warn "FS::h_cust_svc::_label called on $self for $method" if $DEBUG;
-  my $svc_x = $self->h_svc_x(@_);
+  my $svc_x = $self->h_svc_x($end, $start);
   return () unless $svc_x;
   my $part_svc = $self->part_svc;
 
@@ -71,7 +92,7 @@ sub _label {
   }
 
   my @label;
-  eval { @label = $self->$method($svc_x, @_); };
+  eval { @label = $self->$method($svc_x, $end, $start, $locale); };
 
   if ($@) {
     carp 'while resolving history record for svcdb/svcnum ' . 
@@ -85,9 +106,9 @@ sub _label {
 
 =item h_svc_x END_TIMESTAMP [ START_TIMESTAMP ] 
 
-Returns the FS::h_svc_XXX object for this service as of END_TIMESTAMP (i.e. an
-FS::h_svc_acct object or FS::h_svc_domain object, etc.) and (optionally) not
-cancelled before START_TIMESTAMP.
+Returns the FS::h_svc_XXX object for this service as of END_TIMESTAMP (i.e.
+an FS::h_svc_acct object or FS::h_svc_domain object, etc.) and (optionally)
+not cancelled before START_TIMESTAMP.
 
 =cut
 

commit 0141b002ab937b2b3a21a92c3728bc3101f4668a
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Jul 6 11:53:13 2016 -0700

    service label localization, internals and UI, #71347
    
    Conflicts:
    	FS/FS/Schema.pm
    	FS/FS/part_svc.pm
    	httemplate/edit/part_pkg.cgi
    	httemplate/edit/process/part_pkg.cgi
    	httemplate/elements/freeside.css

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 2303176..3ab8aeb 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -392,6 +392,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::fiber_olt;
   use FS::olt_site;
   use FS::access_user_page_pref;
+  use FS::part_svc_msgcat;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index c8f8c81..45ff037 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2491,6 +2491,23 @@ sub tables_hashref {
       'index' => [ ['disabled'] ],
     },
 
+    'part_svc_msgcat' => {
+      'columns' => [
+        'svcpartmsgnum',  'serial',     '',        '', '', '',
+        'svcpart',           'int',     '',        '', '', '',
+        'locale',        'varchar',     '',        16, '', '',
+        'svc',           'varchar',     '',   $char_d, '', '',
+      ],
+      'primary_key'  => 'svcpartmsgnum',
+      'unique'       => [ [ 'svcpart', 'locale' ] ],
+      'index'        => [],
+      'foreign_keys' => [
+                          { columns    => [ 'svcpart' ],
+                            table      => 'part_svc',
+                          },
+                        ],
+    },
+
     #(this should be renamed to part_pop)
     'svc_acct_pop' => {
       'columns' => [
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index 884e3ad..01b8e10 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -1,5 +1,6 @@
 package FS::part_svc;
 
+use base qw(FS::o2m_Common FS::Record);
 use strict;
 use vars qw( @ISA $DEBUG );
 use Tie::IxHash;
@@ -10,8 +11,7 @@ use FS::part_export;
 use FS::export_svc;
 use FS::cust_svc;
 use FS::part_svc_class;
-
- at ISA = qw(FS::Record);
+use FS::part_svc_msgcat;
 
 $DEBUG = 0;
 
@@ -617,6 +617,24 @@ sub svc_x {
   map { $_->svc_x } $self->cust_svc;
 }
 
+=item svc_locale LOCALE
+
+Returns a customer-viewable service definition label in the chosen LOCALE.
+If there is no entry for that locale or if LOCALE is empty, returns
+part_svc.svc.
+
+=cut
+
+sub svc_locale {
+  my( $self, $locale ) = @_;
+  return $self->svc unless $locale;
+  my $part_svc_msgcat = qsearchs('part_svc_msgcat', {
+    svcpart => $self->svcpart,
+    locale  => $locale
+  }) or return $self->svc;
+  $part_svc_msgcat->svc;
+}
+
 =back
 
 =head1 CLASS METHODS
@@ -874,6 +892,12 @@ sub process {
     $param->{'svcpart'} = $new->getfield('svcpart');
   }
 
+  $error ||= $new->process_o2m(
+    'table'   => 'part_svc_msgcat',
+    'params'  => $param,
+    'fields'  => [ 'locale', 'svc' ],
+  );
+
   die "$error\n" if $error;
 }
 
diff --git a/FS/FS/part_svc_msgcat.pm b/FS/FS/part_svc_msgcat.pm
new file mode 100644
index 0000000..6d69198
--- /dev/null
+++ b/FS/FS/part_svc_msgcat.pm
@@ -0,0 +1,131 @@
+package FS::part_svc_msgcat;
+use base qw( FS::Record );
+
+use strict;
+use FS::Locales;
+
+=head1 NAME
+
+FS::part_svc_msgcat - Object methods for part_svc_msgcat records
+
+=head1 SYNOPSIS
+
+  use FS::part_svc_msgcat;
+
+  $record = new FS::part_svc_msgcat \%hash;
+  $record = new FS::part_svc_msgcat { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_svc_msgcat object represents localized labels of a service 
+definition.  FS::part_svc_msgcat inherits from FS::Record.  The following
+fields are currently supported:
+
+=over 4
+
+=item svcpartmsgnum
+
+primary key
+
+=item svcpart
+
+Service definition
+
+=item locale
+
+locale
+
+=item svc
+
+Localized service name (customer-viewable)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_svc_msgcat'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('svcpartmsgnum')
+    || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
+    || $self->ut_enum('locale', [ FS::Locales->locales ] )
+    || $self->ut_text('svc')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_svc>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index dee4394..b947463 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -112,8 +112,24 @@ function part_export_areyousure(href) {
     </TD>
 % } 
 
-    <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>">
-      <% $part_svc->svc %></A></TD>
+    <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
+      <A HREF="<% $url %>">
+        <% $part_svc->svc %>
+      </A>
+%   # any alternate names of the service
+%   my %msgcat = map { $_->locale => $_ } $part_svc->part_svc_msgcat;
+%   my %labels = map { $_ => FS::Locales->description($_) } keys %msgcat;
+%   my @locales = sort { $labels{$a} cmp $labels{$b} } keys %msgcat;
+%   if ( @locales ) {
+      <BR>
+      <FONT SIZE="-1">
+%     foreach my $locale (@locales) {
+        <% $labels{$locale} %>: <% $msgcat{$locale}->get('svc') %>
+        <BR>
+%     }
+      </FONT>
+%   }
+    </TD>
 
     <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
       <% $svcdb %></TD>
diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html
index d2aa14e..93d3b3d 100644
--- a/httemplate/edit/elements/part_svc_column.html
+++ b/httemplate/edit/elements/part_svc_column.html
@@ -268,8 +268,9 @@ that field.
 <& /elements/progress-init.html,
   $svcdb, #form name
   [ # form fields to send
-    qw(svc svcpart classnum selfservice_access disabled preserve exportnum),
-    @fields
+    'ALL'
+#    qw(svc svcpart classnum selfservice_access disabled preserve exportnum),
+#    @fields
   ],
   'process/part_svc.cgi',   # target
   $p.'browse/part_svc.cgi', # redirect landing
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index 2802ddc..eed466e 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -82,7 +82,7 @@
                             { type => 'columnstart' },
                             
                               { field     => 'pkg',
-                                type      => 'text',
+                                type      => 'input-locale-text',
                                 size      => 40, #32
                                 maxlength => 50,
                               },
@@ -434,42 +434,6 @@ my $recur_show_zero_disabled = 1;
 
 my $pkgpart = '';
 
-my $splice_locale_fields = sub {
-  my( $fields, $pkey_value_callback, $pkg_value_callback ) = @_;
-
-  my $n = 0;
-  my @locale_fields = (
-    map { 
-          my $pkey_value= $pkey_value_callback ? &$pkey_value_callback($_) : '';
-          my $pkg_value = $pkg_value_callback
-                            ? $pkg_value_callback eq 'cgiparam'
-                                ? $cgi->param('pkgpartmsgnum'. $n. '_pkg')
-                                : &$pkg_value_callback($_)
-                            : '';
-          (
-            { field     => 'pkgpartmsgnum'. $n,
-              type      => 'hidden',
-              value     => $pkey_value,
-            },
-            { field     => 'pkgpartmsgnum'. $n. '_locale',
-              type      => 'hidden',
-              value     => $_,
-            },
-            { field     => 'pkgpartmsgnum'. $n++. '_pkg',
-              type      => 'text',
-              size      => 40,
-              #maxlength => 50,
-              value     => $pkg_value,
-            },
-          );
-  
-        }
-      @locales
-  );
-  splice(@$fields, 7, 0, @locale_fields); #XXX 7 is arbitrary above
-
-};
-
 my $error_callback = sub {
   my($cgi, $object, $fields, $opt ) = @_;
 
@@ -510,16 +474,6 @@ my $error_callback = sub {
 
   $pkgpart = $object->pkgpart;
 
-  &$splice_locale_fields(
-    $fields,
-    sub {
-          my $locale = shift;
-          my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
-          $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : '';
-        },
-    'cgiparam'
-  );
-
 };
 
 my $new_hashref_callback = sub { { 'plan' => 'flat' }; };
@@ -585,20 +539,6 @@ my $edit_callback = sub {
 
   $pkgpart = $object->pkgpart;
 
-  &$splice_locale_fields(
-    $fields,
-    sub {
-          my $locale = shift;
-          my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
-          $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : '';
-        },
-    sub {
-          my $locale = shift;
-          my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
-          $part_pkg_msgcat ? $part_pkg_msgcat->pkg : '';
-        }
-  );
-
 };
 
 my $new_callback = sub {
@@ -612,8 +552,6 @@ my $new_callback = sub {
 
   $options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill');
 
-  &$splice_locale_fields($fields, '', '');
-
 };
 
 my $clone_callback = sub {
@@ -648,15 +586,6 @@ my $clone_callback = sub {
 
   $recur_disabled = $object->freq ? 0 : 1;
 
-  &$splice_locale_fields(
-    $fields,
-    '',
-    sub {
-      my $locale = shift;
-      my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
-      $part_pkg_msgcat ? $part_pkg_msgcat->pkg : '';
-    }
-  );
 };
 
 my $discount_error_callback = sub {
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 7a47f15..6d8a40f 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -36,9 +36,26 @@
 }
 </STYLE>
 <SCRIPT TYPE="text/javascript">
+// copy all fields from the outer form (svc and its localizations, plus
+// preserve, selfservice_access, etc.) into the inner form, creating hidden
+// inputs if needed
 function fixup_submit(layer) {
-  document.forms[layer].submit.disabled = true;
-  fixup(document.forms[layer]);
+  var layer_form = $(document.forms[layer]);
+  var main_form = $(document.forms['SvcEditMain']);
+  var data = main_form.serializeArray();
+  for (var i = 0; i < data.length; i++) {
+    var input = layer_form.children('[name=' + data[i].name + ']');
+    if (input[0]) {
+      input.prop('value', data[i].value);
+    } else {
+      $( '<input type="hidden">' )
+        .attr('name', data[i].name)
+        .prop('value', data[i].value)
+        .appendTo(layer_form);
+    }
+  }
+  layer_form[0]['submit'].disabled = true;
+  //fixup(document.forms[layer]);
   window[layer+'process'].call();
 }
 
@@ -141,19 +158,26 @@ window.onload = function() {
 
 </SCRIPT>
 
-<FORM NAME="dummy">
+<FORM NAME="SvcEditMain">
 
 <FONT CLASS="fsinnerbox-title">Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %></FONT>
 <TABLE CLASS="fsinnerbox">
-<TR>
-  <TD ALIGN="right">Service</TD>
-  <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD>
-<TR>
+<& /elements/tr-input-locale-text.html,
+  'object' => $part_svc,
+  'cgi'    => $cgi,
+  'field'  => 'svc',
+  'label'  => 'Service',
+  'curr_value' => $hashref->{svc},
+&>
+%#<TR>
+%#  <TD ALIGN="right">Service</TD>
+%#  <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD>
+%#<TR>
 
 <& /elements/tr-select-part_svc_class.html, curr_value=>$hashref->{classnum} &>
 
 <TR>
-  <TD ALIGN="right">Self-service access</TD>
+  <TH ALIGN="right">Self-service access</TD>
   <TD>
     <SELECT NAME="selfservice_access">
 % tie my %selfservice_access, 'Tie::IxHash', #false laziness w/browse/part_svc
@@ -172,12 +196,12 @@ window.onload = function() {
 
 
 <TR>
-  <TD ALIGN="right">Disable new orders</TD>
+  <TH ALIGN="right">Disable new orders</TD>
   <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD>
 </TR>
 
 <TR>
-  <TD ALIGN="right">Preserve this service on package cancellation</TD>
+  <TH ALIGN="right">Preserve this service on package cancellation</TD>
   <TD><INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>> </TD>
 </TR>
 
@@ -241,12 +265,12 @@ my $widget = new HTML::Widgets::SelectLayers(
   #'selected_layer' => $p_svcdb,
   'selected_layer' => $hashref->{svcdb} || 'svc_acct',
   'options'        => \%svcdb,
-  'form_name'      => 'dummy',
+  'form_name'      => 'SvcEditMain',
   #'form_action'    => 'process/part_svc.cgi',
   'form_action'    => 'part_svc.cgi', #self
-  'form_elements'  => [qw( svc svcpart classnum selfservice_access
-                           disabled preserve
-                      )],
+#  'form_elements'  => [qw( svc svcpart classnum selfservice_access
+#                           disabled preserve
+#                      )],
   'html_between'   => $help,
   'layer_callback' => sub {
     include('elements/part_svc_column.html',
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index 8979537..89a38f4 100644
--- a/httemplate/edit/process/elements/process.html
+++ b/httemplate/edit/process/elements/process.html
@@ -62,6 +62,8 @@ Example:
                       'fields' => [qw( fieldname fieldname2 )],
                     },
 
+   'process_locale' => 'fieldname', # update entries in the _msgcat table
+
    'process_upload' => {
                          'process'  => 'misc/mytable-import.html',
                           # fields to pass to the back end job, besides the 
@@ -356,12 +358,21 @@ foreach my $value ( @values ) {
 
   }
 
-  if ( !$error && $opt{'process_o2m'} ) {
-
-    my @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY'
-                           ? @{ $opt{'process_o2m'} }
-                           :  ( $opt{'process_o2m'} );
+  my @process_o2m;
+  if ( $opt{'process_o2m'} ) {
+    @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY'
+                        ? @{ $opt{'process_o2m'} }
+                        :  ( $opt{'process_o2m'} );
+  }
+  if ( $opt{'process_locale'} ) {
+    push @process_o2m,
+    {
+      'table'  => $table . '_msgcat',
+      'fields' => [ 'locale', $opt{'process_locale'} ],
+    };
+  }
 
+  if ( !$error ) {
 
     foreach my $process_o2m (@process_o2m) {
 
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
index 3ffd5fc..631a26b 100755
--- a/httemplate/edit/process/part_pkg.cgi
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -9,8 +9,8 @@
               'edit_ext'          => 'cgi',
               'precheck_callback' => $precheck_callback,
               'args_callback'     => $args_callback,
+              'process_locale'    => 'pkg',
               'process_m2m'       => \@process_m2m,
-              'process_o2m'       => \@process_o2m,
           )
 %>
 <%init>
@@ -253,11 +253,4 @@ push @process_m2m, {
   'params'       => \@agents,
 };
 
-my @process_o2m = (
-  {
-    'table'  => 'part_pkg_msgcat',
-    'fields' => [qw( locale pkg )],
-  },
-);
-
 </%init>
diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html
index e38dde6..0c2b816 100644
--- a/httemplate/elements/progress-init.html
+++ b/httemplate/elements/progress-init.html
@@ -98,14 +98,14 @@ function <%$key%>process () {
 
   overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
 
+  // jQuery .serializeArray() maybe?
+  var copy_fields = <% encode_json(\%copy_fields) %>;
   var Hash = new Array();
   var x = 0;
   var fieldName;
   for (var i = 0; i<document.<%$formname%>.elements.length; i++) {
     field  = document.<%$formname%>.elements[i];
-    if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %>
-       )
-    {
+    if ( <% $all_fields %> || copy_fields[ field.name ] ) {
         if ( field.type == 'select-multiple' ) {
           //alert('select-multiple ' + field.name);
           for (var j=0; j < field.options.length; j++) {
@@ -168,6 +168,14 @@ $progress_url->query_form(
   %dest_info,
 );
 
+my $all_fields = 0;
+my %copy_fields;
+if (grep '/^ALL$/', @$fields) {
+  $all_fields = 1;
+} else {
+  %copy_fields = map { $_ => 1 } @$fields;
+}
+
 #stupid safari is caching the "location" of popup iframs, and submitting them
 #instead of displaying them.  this should prevent that.
 my $popup_name = 'popup-'.random_id();
diff --git a/httemplate/elements/tr-input-locale-text.html b/httemplate/elements/tr-input-locale-text.html
new file mode 100644
index 0000000..110a8aa
--- /dev/null
+++ b/httemplate/elements/tr-input-locale-text.html
@@ -0,0 +1,120 @@
+<%doc>
+Usage:
+
+In edit/foo.html:
+
+<& /elements/tr-input-locale-text.html,
+  cgi     => $cgi, # needed to preserve values in error redirect
+  object  => $record,
+  field   => 'myfield',
+  label   => 'My Field',
+&>
+
+And in edit/process/foo.html:
+<& elements/process.html,
+  ...
+  process_locale => 'myfield',
+&>
+
+'object' needs to be an FS::Record subclass instance for a table that has
+a '_msgcat' localization table. For a table "foo" where "foo.myfield"
+contains some customer-visible label (in the default locale),
+"foo_msgcat.myfield" contains the translation of that label for a customer
+locale. The foreign key in foo_msgcat must have the same name as the primary
+key of foo.
+
+Currently only a single field can be localized this way; including this
+element more than once in the form will lead to conflicts. This is how
+it should work; if at some point we need to localize several fields of the
+same record, we should modify this element to show multiple inputs for each
+locale.
+
+</%doc>
+<%init>
+
+my %opt = @_;
+my $object = delete $opt{object};
+my $field = delete $opt{field};
+
+# identify our locales
+my $conf = FS::Conf->new;
+my $default_locale = $conf->config('locale') || 'en_';
+my @locales = grep { ! /^$default_locale/ } $conf->config('available-locales');
+
+my $label = delete $opt{label};
+my %labels = map { $_ => "$label—".FS::Locales->description($_) }
+              @locales;
+ at locales = sort { $labels{$a} cmp $labels{$b} } @locales;
+my %curr_values;
+
+# where are the msgcat records?
+my $msgcat_table = $object->table . '_msgcat';
+my $msgcat_pkey = dbdef->table($msgcat_table)->primary_key;
+my %msgcat_pkeyvals;
+
+# find existing msgcat records, if any, and record their message values
+# and pkeys
+my $pkey = $object->primary_key;
+my $pkeyval = $object->get($pkey);
+if ($pkeyval) { # of course if this is a new record there won't be any
+  my @linked = qsearch($msgcat_table, { $pkey => $pkeyval });
+  foreach (@linked) {
+    $curr_values{ $_->locale } = $_->get( $field );
+    $msgcat_pkeyvals{ $_->locale } = $_->get( $msgcat_pkey );
+  }
+}
+
+# sticky-on-error the locale inputs
+if( my $cgi = $opt{cgi} ) {
+  my $i = 0;
+  # they're named 'foomsgnum0_locale' and 'foomsgnum0_myfield'
+  while ( my $locale = $cgi->param($msgcat_pkey . $i . '_locale') ) {
+    my $value = $cgi->param($msgcat_pkey . $i . '_' . $field);
+    $curr_values{ $locale } = $value;
+    $i++;
+  }
+}
+
+# compat with tr-input-text for styling
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+
+</%init>
+% # pass through %opt on all of these to retain formatting
+% # one tr, td, and input for the default locale
+<& tr-input-text.html,
+  %opt,
+  'label' => $label,
+  'field' => $field
+&>
+% # and one for each of the others 
+% my $i = 0;
+% foreach my $locale (@locales) {
+%   my $basename = $msgcat_pkey . $i;
+%   my $lfield = $basename . '_' . $field;
+<& tr-td-label.html,
+  %opt,
+  'id' => $lfield, # uniqueness
+  'label' => $labels{$locale}
+&>
+  <TD <% $colspan %><% $cell_style %> ID="<% $lfield %>_input0">
+    <& hidden.html,
+      'field' => $basename,
+      'curr_value' => $msgcat_pkeyvals{$locale},
+      # will be empty if this is a new record and/or new locale, that's fine
+    &>
+    <& hidden.html,
+      'field' => $basename . '_locale',
+      'curr_value' => $locale,
+    &>
+    <& input-text.html,
+      %opt,
+      'field' => $lfield,
+      'curr_value' => $curr_values{$locale},
+    &>
+  </TD>
+</TR>
+%   $i++;
+% } # foreach $locale

commit d49458237ef94dc89633846c0bdca56be2f34264
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Jul 5 16:41:25 2016 -0700

    UTF-8 form input vs. xmlhttp, #71347

diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html
index 2f4f0d5..e708711 100644
--- a/httemplate/elements/xmlhttp.html
+++ b/httemplate/elements/xmlhttp.html
@@ -44,7 +44,7 @@ my %initialized = ();#won't work if component is "preloaded"... so don't do that
             len = args.length - 1;
         }
         for (var i = 0; i < len; i++) 
-            content = content + "&arg=" + escape(args[i]);
+            content = content + "&arg=" + encodeURIComponent(args[i]);
         content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs 
 
         if ( '<%$method%>' == 'GET' ) {

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

Summary of changes:
 FS/FS/Mason.pm                                   |    1 +
 FS/FS/Schema.pm                                  |   17 +++
 FS/FS/Template_Mixin.pm                          |   12 ++-
 FS/FS/cust_pkg.pm                                |   27 +++--
 FS/FS/cust_svc.pm                                |   15 ++-
 FS/FS/h_cust_svc.pm                              |   41 ++++++--
 FS/FS/part_svc.pm                                |   34 +++++-
 FS/FS/{part_pkg_msgcat.pm => part_svc_msgcat.pm} |   50 ++++-----
 httemplate/browse/part_svc.cgi                   |   20 +++-
 httemplate/edit/elements/part_svc_column.html    |    5 +-
 httemplate/edit/part_pkg.cgi                     |   73 +------------
 httemplate/edit/part_svc.cgi                     |   52 +++++++---
 httemplate/edit/process/elements/process.html    |   21 +++-
 httemplate/edit/process/part_pkg.cgi             |    9 +-
 httemplate/elements/progress-init.html           |   14 ++-
 httemplate/elements/tr-input-locale-text.html    |  120 ++++++++++++++++++++++
 httemplate/elements/xmlhttp.html                 |    2 +-
 17 files changed, 351 insertions(+), 162 deletions(-)
 copy FS/FS/{part_pkg_msgcat.pm => part_svc_msgcat.pm} (66%)
 create mode 100644 httemplate/elements/tr-input-locale-text.html




More information about the freeside-commits mailing list