message template localization, #13601
authormark <mark>
Thu, 28 Jul 2011 22:01:47 +0000 (22:01 +0000)
committermark <mark>
Thu, 28 Jul 2011 22:01:47 +0000 (22:01 +0000)
FS/FS/Mason.pm
FS/FS/Misc.pm
FS/FS/Schema.pm
FS/FS/msg_template.pm
FS/FS/template_content.pm [new file with mode: 0644]
FS/MANIFEST
FS/t/template_content.t [new file with mode: 0644]
httemplate/browse/msg_template.html
httemplate/edit/msg_template.html
httemplate/elements/tablebreak-tabs.html [new file with mode: 0644]

index 3691975..7305702 100644 (file)
@@ -289,6 +289,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::rate_center;
   use FS::cust_msg;
   use FS::radius_group;
   use FS::rate_center;
   use FS::cust_msg;
   use FS::radius_group;
+  use FS::template_content;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
index 3b0de3d..6f6f2e2 100644 (file)
@@ -9,6 +9,7 @@ use IPC::Run qw( run timeout );   # for _pslatex
 use IPC::Run3; # for do_print... should just use IPC::Run i guess
 use File::Temp;
 use Tie::IxHash;
 use IPC::Run3; # for do_print... should just use IPC::Run i guess
 use File::Temp;
 use Tie::IxHash;
+use Encode;
 #do NOT depend on any FS:: modules here, causes weird (sometimes unreproducable
 #until on client machine) dependancy loops.  put them in FS::Misc::Something
 #instead
 #do NOT depend on any FS:: modules here, causes weird (sometimes unreproducable
 #until on client machine) dependancy loops.  put them in FS::Misc::Something
 #instead
@@ -187,7 +188,7 @@ sub send_email {
     'Sender'     => $options{'from'},
     'Reply-To'   => $options{'from'},
     'Date'       => time2str("%a, %d %b %Y %X %z", $time),
     'Sender'     => $options{'from'},
     'Reply-To'   => $options{'from'},
     'Date'       => time2str("%a, %d %b %Y %X %z", $time),
-    'Subject'    => $options{'subject'},
+    'Subject'    => Encode::encode('MIME-Header', $options{'subject'}),
     'Message-ID' => "<$message_id>",
     @mimeargs,
   );
     'Message-ID' => "<$message_id>",
     @mimeargs,
   );
@@ -352,8 +353,8 @@ sub generate_email {
 
   $alternative->attach(
     'Type'        => 'text/plain',
 
   $alternative->attach(
     'Type'        => 'text/plain',
-    #'Encoding'    => 'quoted-printable',
-    'Encoding'    => '7bit',
+    'Encoding'    => 'quoted-printable',
+    #'Encoding'    => '7bit',
     'Data'        => $data,
     'Disposition' => 'inline',
   );
     'Data'        => $data,
     'Disposition' => 'inline',
   );
index 49eddf6..5c0c71e 100644 (file)
@@ -3386,8 +3386,21 @@ sub tables_hashref {
         'bcc_addr',  'varchar', 'NULL',     255, '', '',
       ],
       'primary_key' => 'msgnum',
         'bcc_addr',  'varchar', 'NULL',     255, '', '',
       ],
       'primary_key' => 'msgnum',
-      'unique'      => [ ['msgname', 'mime_type'] ],
-      'index'       => [ ['agentnum'], ]
+      'unique'      => [ ],
+      'index'       => [ ['agentnum'], ],
+    },
+
+    'template_content' => {
+      'columns' => [
+        'contentnum', 'serial',     '',      '', '', '',
+        'msgnum',        'int',     '',      '', '', '',
+        'locale',    'varchar', 'NULL',      16, '', '',
+        'subject',   'varchar', 'NULL',     512, '', '',
+        'body',         'text', 'NULL',      '', '', '',
+      ],
+      'primary_key' => 'contentnum',
+      'unique'      => [ ['msgnum', 'locale'] ],
+      'index'       => [ ],
     },
 
     'cust_msg' => {
     },
 
     'cust_msg' => {
index 65acd9a..c183477 100644 (file)
@@ -6,9 +6,11 @@ use Text::Template;
 use FS::Misc qw( generate_email send_email );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
 use FS::Misc qw( generate_email send_email );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
+use FS::UID qw( dbh );
 
 use FS::cust_main;
 use FS::cust_msg;
 
 use FS::cust_main;
 use FS::cust_msg;
+use FS::template_content;
 
 use Date::Format qw( time2str );
 use HTML::Entities qw( decode_entities encode_entities ) ;
 
 use Date::Format qw( time2str );
 use HTML::Entities qw( decode_entities encode_entities ) ;
@@ -47,37 +49,19 @@ supported:
 
 =over 4
 
 
 =over 4
 
-=item msgnum
+=item msgnum - primary key
 
 
-primary key
+=item msgname - Name of the template.  This will appear in the user interface;
+if it needs to be localized for some users, add it to the message catalog.
 
 
-=item msgname
+=item agentnum - Agent associated with this template.  Can be NULL for a 
+global template.
 
 
-Template name.
+=item mime_type - MIME type.  Defaults to text/html.
 
 
-=item agentnum
+=item from_addr - Source email address.
 
 
-Agent associated with this template.  Can be NULL for a global template.
-
-=item mime_type
-
-MIME type.  Defaults to text/html.
-
-=item from_addr
-
-Source email address.
-
-=item subject
-
-The message subject line, in L<Text::Template> format.
-
-=item body
-
-The message body, as plain text or HTML, in L<Text::Template> format.
-
-=item disabled
-
-disabled
+=item disabled - disabled ('Y' or NULL).
 
 =back
 
 
 =back
 
@@ -98,14 +82,41 @@ points to.  You can ask the object for a copy with the I<hash> method.
 
 sub table { 'msg_template'; }
 
 
 sub table { 'msg_template'; }
 
-=item insert
+=item insert [ CONTENT ]
 
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
 
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
+A default (no locale) L<FS::template_content> object will be created.  CONTENT 
+is an optional hash containing 'subject' and 'body' for this object.
+
 =cut
 
 =cut
 
-# the insert method can be inherited from FS::Record
+sub insert {
+  my $self = shift;
+  my %content = @_;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( !$error ) {
+    $content{'msgnum'} = $self->msgnum;
+    $content{'subject'} ||= '';
+    $content{'body'} ||= '';
+    my $template_content = new FS::template_content (\%content);
+    $error = $template_content->insert;
+  }
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit if $oldAutoCommit;
+  return;
+}
 
 =item delete
 
 
 =item delete
 
@@ -115,14 +126,59 @@ Delete this record from the database.
 
 # the delete method can be inherited from FS::Record
 
 
 # the delete method can be inherited from FS::Record
 
-=item replace OLD_RECORD
+=item replace [ OLD_RECORD ] [ CONTENT ]
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
+CONTENT is an optional hash containing 'subject', 'body', and 'locale'.  If 
+supplied, an L<FS::template_content> object will be created (or modified, if 
+one already exists for this locale).
+
 =cut
 
 =cut
 
-# the replace method can be inherited from FS::Record
+sub replace {
+  my $self = shift;
+  my $old = ( ref($_[0]) and $_[0]->isa('FS::Record') ) 
+              ? shift
+              : $self->replace_old;
+  my %content = @_;
+  
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::replace($old);
+
+  if ( !$error and %content ) {
+    $content{'locale'} ||= '';
+    my $new_content = qsearchs('template_content', {
+                        'msgnum' => $self->msgnum,
+                        'locale' => $content{'locale'},
+                      } );
+    if ( $new_content ) {
+      $new_content->subject($content{'subject'});
+      $new_content->body($content{'body'});
+      $error = $new_content->replace;
+    }
+    else {
+      $content{'msgnum'} = $self->msgnum;
+      $new_content = new FS::template_content \%content;
+      $error = $new_content->insert;
+    }
+  }
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  warn "committing FS::msg_template->replace\n" if $DEBUG and $oldAutoCommit;
+  $dbh->commit if $oldAutoCommit;
+  return;
+}
+    
+
 
 =item check
 
 
 =item check
 
@@ -143,8 +199,6 @@ sub check {
     || $self->ut_text('msgname')
     || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
     || $self->ut_textn('mime_type')
     || $self->ut_text('msgname')
     || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
     || $self->ut_textn('mime_type')
-    || $self->ut_anything('subject')
-    || $self->ut_anything('body')
     || $self->ut_enum('disabled', [ '', 'Y' ] )
     || $self->ut_textn('from_addr')
   ;
     || $self->ut_enum('disabled', [ '', 'Y' ] )
     || $self->ut_textn('from_addr')
   ;
@@ -155,6 +209,21 @@ sub check {
   $self->SUPER::check;
 }
 
   $self->SUPER::check;
 }
 
+=item content_locales
+
+Returns a hashref of the L<FS::template_content> objects attached to 
+this template, with the locale as key.
+
+=cut
+
+sub content_locales {
+  my $self = shift;
+  return $self->{'_content_locales'} ||= +{
+    map { $_->locale , $_ } 
+    qsearch('template_content', { 'msgnum' => $self->msgnum })
+  };
+}
+
 =item prepare OPTION => VALUE
 
 Fills in the template and returns a hash of the 'from' address, 'to' 
 =item prepare OPTION => VALUE
 
 Fills in the template and returns a hash of the 'from' address, 'to' 
@@ -202,6 +271,12 @@ sub prepare {
 
   my $cust_main = $opt{'cust_main'};
   my $object = $opt{'object'};
 
   my $cust_main = $opt{'cust_main'};
   my $object = $opt{'object'};
+
+  # localization
+  my $locale = $cust_main->locale || '';
+  warn "no locale for cust#".$cust_main->custnum."; using default content\n"
+    if $DEBUG and !$locale;
+  my $content = $self->content($cust_main->locale);
   warn "preparing template '".$self->msgname."' to cust#".$cust_main->custnum."\n"
     if($DEBUG);
 
   warn "preparing template '".$self->msgname."' to cust#".$cust_main->custnum."\n"
     if($DEBUG);
 
@@ -257,11 +332,11 @@ sub prepare {
   ###
   my $subject_tmpl = new Text::Template (
     TYPE   => 'STRING',
   ###
   my $subject_tmpl = new Text::Template (
     TYPE   => 'STRING',
-    SOURCE => $self->subject,
+    SOURCE => $content->subject,
   );
   my $subject = $subject_tmpl->fill_in( HASH => \%hash );
 
   );
   my $subject = $subject_tmpl->fill_in( HASH => \%hash );
 
-  my $body = $self->body;
+  my $body = $content->body;
   my ($skin, $guts) = eviscerate($body);
   @$guts = map { 
     $_ = decode_entities($_); # turn all punctuation back into itself
   my ($skin, $guts) = eviscerate($body);
   @$guts = map { 
     $_ = decode_entities($_); # turn all punctuation back into itself
@@ -505,6 +580,32 @@ sub substitutions {
   };
 }
 
   };
 }
 
+=item content LOCALE
+
+Returns the L<FS::template_content> object appropriate to LOCALE, if there 
+is one.  If not, returns the one with a NULL locale.
+
+=cut
+
+sub content {
+  my $self = shift;
+  my $locale = shift;
+  qsearchs('template_content', 
+            { 'msgnum' => $self->msgnum, 'locale' => $locale }) || 
+  qsearchs('template_content',
+            { 'msgnum' => $self->msgnum, 'locale' => '' });
+}
+
+=item agent
+
+Returns the L<FS::agent> object for this template.
+
+=cut
+
+sub agent {
+  qsearchs('agent', { 'agentnum' => $_[0]->agentnum });
+}
+
 sub _upgrade_data {
   my ($self, %opts) = @_;
 
 sub _upgrade_data {
   my ($self, %opts) = @_;
 
@@ -524,14 +625,14 @@ sub _upgrade_data {
       my ($newname, $oldname, $subject, $from, $bcc) = @$_;
       if ($conf->exists($oldname, $agentnum)) {
         my $new = new FS::msg_template({
       my ($newname, $oldname, $subject, $from, $bcc) = @$_;
       if ($conf->exists($oldname, $agentnum)) {
         my $new = new FS::msg_template({
-           'msgname'   => $oldname,
-           'agentnum'  => $agentnum,
-           'from_addr' => ($from && $conf->config($from, $agentnum)) || 
-                          $conf->config('invoice_from', $agentnum),
-           'bcc_addr'  => ($bcc && $conf->config($from, $agentnum)) || '',
-           'subject'   => ($subject && $conf->config($subject, $agentnum)) || '',
-           'mime_type' => 'text/html',
-           'body'      => join('<BR>',$conf->config($oldname, $agentnum)),
+          'msgname'   => $oldname,
+          'agentnum'  => $agentnum,
+          'from_addr' => ($from && $conf->config($from, $agentnum)) || 
+                         $conf->config('invoice_from', $agentnum),
+          'bcc_addr'  => ($bcc && $conf->config($from, $agentnum)) || '',
+          'subject'   => ($subject && $conf->config($subject, $agentnum)) || '',
+          'mime_type' => 'text/html',
+          'body'      => join('<BR>',$conf->config($oldname, $agentnum)),
         });
         my $error = $new->insert;
         die $error if $error;
         });
         my $error = $new->insert;
         die $error if $error;
@@ -542,6 +643,19 @@ sub _upgrade_data {
       }
     }
   }
       }
     }
   }
+  foreach my $msg_template ( qsearch('msg_template', {}) ) {
+    if ( $msg_template->subject || $msg_template->body ) {
+      # create new default content
+      my %content;
+      foreach ('subject','body') {
+        $content{$_} = $msg_template->$_;
+        $msg_template->setfield($_, '');
+      }
+
+      my $error = $msg_template->replace(%content);
+      die $error if $error;
+    }
+  }
 }
 
 sub eviscerate {
 }
 
 sub eviscerate {
diff --git a/FS/FS/template_content.pm b/FS/FS/template_content.pm
new file mode 100644 (file)
index 0000000..a30d41f
--- /dev/null
@@ -0,0 +1,124 @@
+package FS::template_content;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::template_content - Object methods for template_content records
+
+=head1 SYNOPSIS
+
+  use FS::template_content;
+
+  $record = new FS::template_content \%hash;
+  $record = new FS::template_content { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::template_content object represents the content of a message template
+(subject line and body) for a specific region.  FS::template_content inherits 
+from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item contentnum - primary key
+
+=item msgnum - the L<FS::msg_template> for which this is the content.
+
+=item locale - locale (such as 'en_US'); can be NULL.
+
+=item subject - Subject: line of the message, in L<Text::Template> format.
+
+=item body - Message body, as plain text or HTML, in L<Text::Template> format.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example.  To add the example 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 { 'template_content'; }
+
+=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 example.  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('contentnum')
+    || $self->ut_foreign_key('msgnum', 'msg_template', 'msgnum')
+    || $self->ut_textn('locale')
+    || $self->ut_anything('subject')
+    || $self->ut_anything('body')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index ceeb3b7..43a1ed8 100644 (file)
@@ -608,3 +608,5 @@ FS/Maketext.pm
 t/Maketext.t
 FS/radius_group.pm
 t/radius_group.t
 t/Maketext.t
 FS/radius_group.pm
 t/radius_group.t
+FS/template_content.pm
+t/template_content.t
diff --git a/FS/t/template_content.t b/FS/t/template_content.t
new file mode 100644 (file)
index 0000000..c1e527a
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::template_content;
+$loaded=1;
+print "ok 1\n";
index 252ee1f..8a6ccf7 100644 (file)
               'disabled_statuspos' => 2,
               'agent_virt'         => 1,
               'agent_null_right'   => ['Edit global templates','Configuration'],
               'disabled_statuspos' => 2,
               'agent_virt'         => 1,
               'agent_null_right'   => ['Edit global templates','Configuration'],
-              'agent_pos'          => 3,
-              'header' => [ 'Name' ],
-              'fields' => [ 'msgname' ],
-              'links'  => [ $link ],
+              'agent_pos'          => 1,
+              'header' => [ 'Name', '', ('' x scalar @locales) ],
+              'fields' => [ 'msgname', @locales ],
+              'links'  => [ $link, @locale_links ],
+              'cell_style' => 
+                          [ '', '', ($locale_style) x (scalar @locales) ],
           )
 %>
 <%init>
           )
 %>
 <%init>
@@ -23,6 +25,22 @@ die "access denied"
   ||     $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
   ||     $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
   ||     $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
   ||     $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
-my $link = [ "${p}edit/msg_template.html?", 'msgnum' ];
+my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ];
+
+my $locale_style = 'font-size:0.8em; padding:3px; background-color:';
+
+my (@locales, @locale_links);
+foreach my $l ( FS::Locales->locales ) {
+  push @locales, sub {
+    exists ( $_[0]->content_locales->{$l} )
+    ? +{ FS::Locales->locale_info($l) }->{'name'} 
+    : '';
+  };
+  push @locale_links, sub {
+    my $content = $_[0]->content_locales->{$l} or return '';
+    [ "${p}edit/msg_template.html?locale=$l;msgnum=", 'msgnum' ];
+  };
+}
+    
 
 </%init>
 
 </%init>
index 758aab6..9415545 100644 (file)
@@ -1,28 +1,47 @@
 <% include( 'elements/edit.html',  
 <% include( 'elements/edit.html',  
-              'html_init'     => '<TABLE id="outerTable"><TR><TD>',
-              'name_singular' => 'template',
-              'table'         => 'msg_template',
-              'viewall_dir'   => 'browse',
-              'agent_virt'    => 1,
-              'agent_null'    => 1,
-              'agent_null_right' => ['Edit global templates', 'Configuration'],
-
-              'fields' => [ 'msgname',
-                            { field=>'from_addr', size=>60, },
-                            { field=>'bcc_addr',  size=>60, },
-                            { field=>'subject',   size=>80, },
-                            { field=>'body', type=>'htmlarea', width=>763 },
-                          ],
-              'labels' => { 'msgnum'    => 'Template',
-                            'msgname'   => 'Template name',
-                            'from_addr' => 'From: ',
-                            'bcc_addr'  => 'Bcc: ',
-                            'subject'   => 'Subject: ',
-                            'body'      => 'Message template',
-                          },
-              'html_foot' => "</TD>$sidebar</TR></TABLE>",
-          )
-%>
+    'html_init'     => '<TABLE id="outerTable"><TR><TD>',
+    'body_etc'      => $body_etc,
+    'name_singular' => 'template',
+    'table'         => 'msg_template',
+    'viewall_dir'   => 'browse',
+    'agent_virt'    => 1,
+    'agent_null'    => 1,
+    'agent_null_right' => ['Edit global templates', 'Configuration'],
+
+    'fields' => [
+      { field => 'agentnum',
+        type  => 'select-agent',
+      },
+      { field => 'msgname',   size=>60, },
+      { field => 'from_addr', size=>60, },
+      { field => 'bcc_addr',  size=>60, },
+      { type  => 'tablebreak-tabs',
+        include_opt_callback => \&menubar_opt_callback,
+      },
+      # template_content fields
+      { field => 'locale', type => 'hidden' },
+      { field => 'subject',   size=>60, },
+      { field => 'body',
+        type  => 'htmlarea',
+        width => 763
+      },
+    ],
+    'labels' => { 
+      'msgnum'    => 'Template',
+      'agentnum'  => 'Agent',
+      'msgname'   => 'Template name',
+      'from_addr' => 'From: ',
+      'bcc_addr'  => 'Bcc: ',
+      'locale'    => 'Language',
+      'subject'   => 'Subject: ',
+      'body'      => 'Message body',
+    },
+    'edit_callback'   => \&edit_callback,
+    'error_callback'  => \&edit_callback,
+    'html_bottom' => '</DIV>',
+    'html_foot' => "</TD>$sidebar</TR></TABLE>",
+    )
+    %>
 <%init>
 
 die "access denied"
 <%init>
 
 die "access denied"
@@ -30,6 +49,95 @@ die "access denied"
   ||     $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
   ||     $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
   ||     $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
   ||     $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
+my $body_etc = '';
+$body_etc = q!onload="document.getElementById('locale').onchange()"!
+  if $cgi->param('locale') eq 'new';
+
+sub new_callback {
+  my ($cgi, $object, $fields_listref, $opt_hashref) = @_;
+  my $template_content = new FS::template_content { 'locale' => '' };
+  $object->{'Hash'} = { $object->hash, $template_content->hash };
+}
+
+sub edit_callback {
+  my ($cgi, $object, $fields_listref, $opt_hashref) = @_;
+  $cgi->param('locale') =~ /^(\w*)$/ or die 'bad locale '.$cgi->param('locale');
+  my $locale = $1;
+
+  # fetch the content object and merge its fields
+  my %args = (
+    'msgnum' => $object->msgnum,
+    'locale' => $locale
+  );
+  my $template_content = qsearchs('template_content', \%args) 
+                        || new FS::template_content( { %args });
+  $object->{'Hash'} = { $object->hash, $template_content->hash };
+
+  # set up the locale selector if this is a new content
+  if ( $locale eq 'new' ) {
+
+    # make a list of available locales
+    my $content_locales = $object->content_locales;
+    my @locales = grep { !exists($content_locales->{$_}) } FS::Locales->locales;
+    my %labels;
+    foreach (@locales) {
+      my %info = FS::Locales->locale_info($_);
+      $labels{$_} = $info{'name'}; # ignoring country for now
+    }
+    unshift @locales, 'new';
+    $labels{'new'} = 'Select language';
+
+    # insert a field def
+    my $i = 0;
+    $i++ until ( $fields_listref->[$i]->{'field'} eq 'locale' );
+    my $locale_field = $fields_listref->[$i];
+
+    my $onchange_locale = "document.getElementById('submit').disabled = 
+    (this.options[this.selectedIndex].value == 'new');";
+
+    %$locale_field = (
+      field   => 'locale',
+      type    => 'select',
+      options => \@locales,
+      labels  => \%labels,
+      curr_value  => 'new',
+      onchange    => $onchange_locale,
+    );
+  } # else it's already set up correctly
+}
+
+sub menubar_opt_callback {
+  my $object = shift;
+  # generate no tabs for new msg_templates.
+  my $msgnum = $object->msgnum or return; 
+  my (@tabs, @options, %labels);
+  push @tabs, mt('Default'), '';
+  my $display_new = 0;
+  my $selected = '';
+  foreach my $l (FS::Locales->locales) {
+    if ( exists $object->content_locales->{$l} ) {
+      my %info = FS::Locales->locale_info($l);
+      push @tabs,
+           $info{'name'},
+           ';locale='.$l;
+      $selected = $info{'name'} if $object->locale eq $l;
+    }
+    else {
+      $display_new = 1; # there is at least one unused locale left
+    }
+  }
+  push @tabs, mt('New'), ';locale=new' if $display_new;
+  $selected = mt('New') if $object->locale eq 'new';
+  $selected ||= mt('Default');
+  (
+    'url_base' => $p.'edit/msg_template.html?msgnum='.$msgnum,
+    'selected' => $selected,
+    'tabs'     => \@tabs
+  );
+}
+
+my $onchange_locale = '';
+
 # Create hints pane
 
 my %substitutions = (
 # Create hints pane
 
 my %substitutions = (
diff --git a/httemplate/elements/tablebreak-tabs.html b/httemplate/elements/tablebreak-tabs.html
new file mode 100644 (file)
index 0000000..91c28ce
--- /dev/null
@@ -0,0 +1,34 @@
+<%doc>
+<& /elements/tablebreak-tabs.html,
+   tabs => [                              #required
+     'Foo' => ';mode=foo',
+     'Bar' => ';mode=bar',
+     'Other' => '',
+   ],
+   selected => 'Foo',                     #required
+   url_base => $p.'edit/something.html',  #optional
+   table_id => 'OneTrueTable',            #optional
+   always_show_tabs => 0, #optional, 
+                          #controls whether this will show only one tab
+&>
+
+For use in edit/elements/edit.html 'fields' hashes.
+
+</%doc>
+% if (scalar(@$tabs) >= 2 or $opt{'always_show_tabs'}) {
+</TABLE><BR>
+<& /elements/menubar.html,
+    { newstyle => 1, %opt },
+    @$tabs &>
+<DIV CLASS="fstabcontainer">
+<TABLE <% $id %> BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+% }
+<%init>
+
+my %opt = @_;
+my $tabs = delete $opt{'tabs'} || []; # must be an arrayref
+
+my $id = '';
+$id = 'ID="'. $opt{'table_id'}. '"' if $opt{'table_id'};
+
+</%init>