[freeside-commits] branch master updated. 89525f062092c185344ec7318406b1c9086d1eda

Jonathan Prykop jonathan at 420.am
Mon Aug 17 21:02:16 PDT 2015


The branch, master has been updated
       via  89525f062092c185344ec7318406b1c9086d1eda (commit)
      from  ef2bc5dcb69e67077ce45a624c107894765e3907 (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 89525f062092c185344ec7318406b1c9086d1eda
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Mon Aug 17 23:01:31 2015 -0500

    RT#18830: Upload file to message template

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 184c6c9..55dc99e 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -204,6 +204,7 @@ sub dbdef_dist {
            && ( ! /^queue(_arg|_depend|_stat)?$/ || ! $opt->{'queue-no_history'} )
            && ! $tables_hashref_torrus->{$_}
            && ! /^cacti_page$/
+           && ! /^template_image$/
          }
       $dbdef->tables
   ) {
@@ -6346,6 +6347,19 @@ sub tables_hashref {
                         ],
     },
 
+    'template_image' => {
+      'columns' => [
+        'imgnum',     'serial',     '',      '', '', '',
+        'name',      'varchar',     '', $char_d, '', '',
+        'agentnum',      'int', 'NULL',      '', '', '',
+        'mime_type', 'varchar',     '', $char_d, '', '',
+        'base64',       'text',     '',      '', '', '',
+      ],
+      'primary_key'  => 'imgnum',
+      'unique'       => [ ],
+      'index'        => [ ['name'], ['agentnum'] ],
+    },
+
     'cust_msg' => {
       'columns' => [
         'custmsgnum', 'serial',     '',     '', '', '',
diff --git a/FS/FS/template_image.pm b/FS/FS/template_image.pm
new file mode 100644
index 0000000..e7f4bab
--- /dev/null
+++ b/FS/FS/template_image.pm
@@ -0,0 +1,222 @@
+package FS::template_image;
+use base qw( FS::Agent_Mixin FS::Record );
+
+use strict;
+use FS::Record qw( qsearchs );
+use File::Slurp qw( slurp );
+use MIME::Base64 qw( encode_base64 );
+
+my %ext_to_type = (
+  'jpeg' => 'image/jpeg',
+  'jpg'  => 'image/jpeg',
+  'png'  => 'image/png',
+  'gif'  => 'image/gif',
+);
+
+=head1 NAME
+
+FS::template_image - Object methods for template_image records
+
+=head1 SYNOPSIS
+
+  use FS::template_image;
+
+  $record = new FS::template_image {
+              'name'      => 'logo',
+              'agentnum'  => $agentnum,
+              'base64'    => encode_base64($rawdata),
+              'mime_type' => 'image/jpg',
+  };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::template_image object represents an uploaded image for insertion into templates.
+FS::template_image inherits from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item imgnum - primary key
+
+=item name - unique name, for selecting/editing images
+
+=item agentnum - image agent
+
+=item mime-type - image mime-type
+
+=item base64 - base64-encoded raw contents of image file
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new object.  To add the object 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_image'; }
+
+=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('imgnum','agentnum')
+    || $self->ut_text('name','mime-type')
+    || $self->ut_anything('base64')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item src
+
+Returns a data url for this image, incorporating mime_type & base64
+
+=cut
+
+sub src {
+  my $self = shift;
+  'data:'
+  . $self->mime_type
+  . ';base64,'
+  . $self->base64;
+}
+
+=item html
+
+Returns html for a basic img tag for this image (no attributes)
+
+=cut
+
+sub html {
+  my $self = shift;
+  '<IMG SRC="'
+  . $self->src
+  . '">';
+}
+
+=item process_image_delete
+
+Process for deleting an image.  Run as a job using L<FS::queue>.
+
+=cut
+
+sub process_image_delete {
+  my $job = shift;
+  my $param = shift;
+  my $template_image = qsearchs('template_image',{ 'imgnum' => $param->{'imgnum'} })
+    or die "Could not load template_image";
+  my $error = $template_image->delete;
+  die $error if $error;
+  '';
+}
+
+=item process_image_upload
+
+Process for uploading an image.  Run as a job using L<FS::queue>.
+
+=cut
+
+sub process_image_upload {
+  my $job = shift;
+  my $param = shift;
+
+  my $files = $param->{'uploaded_files'}
+    or die "No files provided.\n";
+
+  my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
+
+  my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
+  my $file = $dir. $files{'file'};
+
+  my $type;
+  if ( $file =~ /\.(\w+)$/i ) {
+    my $ext = lc($1);
+    die "Unrecognized file extension $ext"
+      unless $ext_to_type{$ext};
+    $type = $ext_to_type{$ext};
+  } else {
+    die "Cannot upload image file without extension"
+  }
+
+  my $template_image = new FS::template_image {
+    'name'   => $param->{'name'},
+    'mime_type' => $type,
+    'agentnum'  => $param->{'agentnum'},
+    'base64'    => encode_base64( slurp($file, binmode => ':raw'), '' ),
+  };
+  my $error = $template_image->insert();
+  die $error if $error;
+  unlink $file;
+  '';
+
+}
+
+=back
+
+=head1 BUGS
+
+Will be described here once found.
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/httemplate/browse/msg_template.html b/httemplate/browse/msg_template.html
index ef0b2da..1646bc1 100644
--- a/httemplate/browse/msg_template.html
+++ b/httemplate/browse/msg_template.html
@@ -28,6 +28,7 @@ my @menubar = ();
 if ( $curuser->access_right(['Edit templates', 'Edit global templates']) ) {
   push @menubar, 'Add a new template' => $p.'edit/msg_template.html';
 }
+push @menubar, 'View template images' => $p.'browse/template_image.html';
 
 my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ];
 
@@ -52,7 +53,7 @@ my $disable_link = sub {
     action      => $p.'misc/disable-msg_template.cgi?msgnum=' .
                      $template->msgnum .
                      ($template->disabled ? ';enable=1' : ''),
-    actionlabel => 'Disable lemplate',
+    actionlabel => 'Disable template',
   );
 };
 
diff --git a/httemplate/browse/template_image.html b/httemplate/browse/template_image.html
new file mode 100644
index 0000000..eb4325f
--- /dev/null
+++ b/httemplate/browse/template_image.html
@@ -0,0 +1,68 @@
+<% include('/elements/init_overlib.html') %>
+
+<% include( 'elements/browse.html',
+              'title'         => 'Template images',
+              'name_singular' => 'image',
+              'menubar'       => \@menubar,
+              'query'         => { 'table' => 'template_image', },
+              'count_query'   => 'SELECT COUNT(*) FROM template_image',
+              'agent_virt'         => 1,
+              'agent_null_right'   => ['View global templates','Edit global templates'],
+              'agent_pos'          => 1,
+              'header'      => [ 'Name', '', '' ],
+              'fields'      => [ 'name', $tag, $delete_text ],
+              'links'       => [ '', '', '' ],
+              'cell_style'    => [ '', '', '' ],
+          )
+%>
+
+<% include('/elements/template_image-dialog.html',
+     'url' => $p.'browse/template_image.html'
+   ) %>
+
+<%init>
+use FS::template_image;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+  unless $curuser->access_right([ 'View templates', 'View global templates',
+                                  'Edit templates', 'Edit global templates', ]);
+
+my $canedit = $curuser->access_right(['Edit templates', 'Edit global templates']);
+
+my @menubar = ();
+if ($canedit) {
+  push @menubar, 'Upload a new image' => 'javascript:insertImageDialog(\'upload\')';
+}
+push @menubar, ( 'View message templates' => $p.'browse/msg_template.html' );
+
+my $tag = sub { qq!<A HREF="javascript:insertImageDialog(! . $_[0]->imgnum . qq!)">view</A>! };
+
+my $delete_text = $canedit ? sub {
+  my $image = shift;
+  my $imgnum = $image->imgnum;
+  unless ($image->agentnum) {
+    unless ($FS::CurrentUser::CurrentUser->access_right('Edit global templates')) {
+      return '';
+    }
+  }
+  my $out = <<EOF;
+<FORM name="delete_template_image_$imgnum">
+<INPUT TYPE="hidden" name="imgnum" value="$imgnum">
+</FORM>
+EOF
+  $out .= include('/elements/progress-init.html',
+            "delete_template_image_$imgnum",
+            [ 'imgnum' ],
+            $p.'misc/process/template_image-delete.cgi',
+            $p.'browse/template_image.html',
+            "imgnum$imgnum",
+          );
+  my $onclick = 'if ( confirm(\'';
+  $onclick .= emt('Are you sure you want to delete template image ') . $imgnum;
+  $onclick .= '\') ) { imgnum' . $imgnum . 'process() }';
+  return $out . '<A HREF="javascript:void(0)" ONCLICK="' . $onclick . '">delete</A>';
+} : '';
+
+</%init>
diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html
index 7f38241..df72c5b 100644
--- a/httemplate/edit/msg_template.html
+++ b/httemplate/edit/msg_template.html
@@ -27,6 +27,7 @@
      'no_submit'        => $no_submit,
 &>
 <%init>
+use FS::template_image;
 
 my $curuser = $FS::CurrentUser::CurrentUser;
 
@@ -345,10 +346,16 @@ function areyousure(url, message) {
 <TD valign="top"><FORM name="dummy">
 Substitutions: '
 . $widget->html .
-'<BR>Click links to insert.
-<BR>Enclose substitutions and other Perl expressions in braces:
+'<P>Click above links to insert substitution code.</P>
+<P>
+Enclose substitutions and other Perl expressions in braces:
 <BR>{ $name } = ExampleCo (Smith, John)
 <BR>{ time2str("%D", time) } = '.time2str("%D", time).'
+</P>';
+$sidebar .= include('/elements/template_image-dialog.html',
+              'callback' => 'insertHtml'
+            );
+$sidebar .= '<P><A HREF="javascript:insertImageDialog()">Insert Uploaded Image</A></P>
 </FONT></TD>
 ';
 
diff --git a/httemplate/elements/form-file_upload.html b/httemplate/elements/form-file_upload.html
index 45b6c97..3542a5a 100644
--- a/httemplate/elements/form-file_upload.html
+++ b/httemplate/elements/form-file_upload.html
@@ -69,6 +69,7 @@ Example:
 <div style="display:none:" id="uploadError"></div>
 
 <FORM NAME     = "<% $opt{name} %>"
+      ID       = "<% $opt{id} %>"
       ACTION   = "<% $fsurl %>misc/file-upload.html"
       METHOD   = "POST"
       ENCTYPE  = "multipart/form-data"
diff --git a/httemplate/elements/images/ui-icons_ef8c08_256x240.png b/httemplate/elements/images/ui-icons_ef8c08_256x240.png
new file mode 100644
index 0000000..85e63e9
Binary files /dev/null and b/httemplate/elements/images/ui-icons_ef8c08_256x240.png differ
diff --git a/httemplate/elements/template_image-dialog.html b/httemplate/elements/template_image-dialog.html
new file mode 100644
index 0000000..5691d52
--- /dev/null
+++ b/httemplate/elements/template_image-dialog.html
@@ -0,0 +1,279 @@
+<%doc>
+
+Creates a jquery dialog box that opens when javascript function insertImageDialog
+is called, allows user to select an image and specify attributes for it, then passes 
+img tag with base64 encoded data url to a callback javascript function.
+
+Accepts the following options:
+
+callback - pass the html for the selected img to this javascript function;
+if omitted, will only include fields for viewing/uploading image
+
+url - to redirect to after upload, otherwise just refreshes dialog window
+
+</%doc>
+
+<% include('/elements/xmlhttp.html',
+        'url'  => $p.'misc/xmlhttp-template_image.cgi',
+        'subs' => [ 'get_template_image' ],
+   ) %>
+
+<DIV ID="insert_image_dialog" title="Template Images">
+
+<TABLE BORDER="0" STYLE="width: 100%"><TR><TD>
+
+<FORM ID="insert_image_form">
+
+<% &ntable("#cccccc", 2) %>
+
+  <TR>
+    <TH>Image</TH>
+    <TD>
+      <SELECT ID="insert_image_imgnum" ONCHANGE="insertImageDialog($('#insert_image_imgnum').val())">
+        <OPTION VALUE="">(select an image)</OPTION>
+      </SELECT>
+    </TD>
+  </TR>
+% if ($opt{'callback'}) {
+  <TR>
+    <TH>Width</TH>
+    <TD><INPUT TYPE="text" SIZE="5" ID="insert_image_width" ONCHANGE="previewInsertImage()"></TD>
+  </TR>
+  <TR>
+    <TH>Height</TH>
+    <TD><INPUT TYPE="text" SIZE="5" ID="insert_image_height" ONCHANGE="previewInsertImage()"></TD>
+  </TR>
+  <TR>
+    <TH>Align</TH>
+    <TD>
+      <SELECT ID="insert_image_float" ONCHANGE="previewInsertImage()">
+        <OPTION VALUE="none">inline</OPTION>
+        <OPTION VALUE="left">left</OPTION>
+        <OPTION VALUE="right">right</OPTION>
+      </SELECT>
+    </TD>
+  </TR>
+  <TR>
+    <TH>Alt Text</TH>
+    <TD><INPUT TYPE="text" SIZE="20" ID="insert_image_alt" ONCHANGE="previewInsertImage()"></TD>
+  </TR>
+  <TR>
+    <TD COLSPAN="2" ALIGN="center" STYLE="padding-top:6px">
+      <INPUT TYPE="button" ID="insert_image_button" VALUE="Insert Image" ONCLICK="insertImage()">
+    </TD>
+  </TR>
+% } # if $opt{'callback'}
+
+</TABLE>
+
+</FORM>
+
+% if ($canedit) {
+
+<P><B><% emt('Upload New Image') %></B></P>
+
+<% include('/elements/form-file_upload.html',
+     'name'      => 'TemplateImageUploadForm',
+     'id'        => 'TemplateImageUploadForm',
+     'action'    => $p.'misc/process/template_image-upload.cgi',
+     'num_files' => 1,
+     'fields'    => [ 'name', 'agentnum' ],
+     'url'       => $opt{'url'} || 'javascript:refreshImageList(1)',
+   )
+ %>
+
+ <% &ntable("#cccccc", 2) %>
+
+  <% include( '/elements/tr-input-text.html',
+                'field' => 'name',
+                'label' => 'Name',
+                'required' => 1,
+                'id' => 'upload_form_name',
+            )
+  %>
+
+  <% include( '/elements/tr-select-agent.html',
+                 'label'       => "<B>Agent</B>",
+                 'empty_label' => 'Select agent',
+                 'agent_virt'  => 1,
+                 'agent_null_right' => 'Edit global templates',
+             )
+  %>
+
+  <% include( '/elements/tr-file-upload.html',
+                'field' => 'file',
+                'label' => 'File',
+            )
+  %>
+
+  <TR>
+    <TD COLSPAN="2" ALIGN="center" STYLE="padding-top:6px">
+      <INPUT TYPE    = "submit"
+             NAME    = "submitButton"
+             ID      = "submitButton"
+             VALUE   = "Upload image"
+      >
+    </TD>
+  </TR>
+
+</TABLE>
+
+</FORM>
+
+% } #if canedit
+
+</TD><TD width="100%">
+
+<DIV ID="insert_image_preview_box">
+  <P><B><% emt('Image Preview') %></B></P>
+  <SPAN ID="insert_image_loading"><B>(<% emt('Loading image...') %>)</B></SPAN>
+  <IMG SRC="" ID="insert_image_preview">
+</DIV>
+
+</TD></TR></TABLE>
+</DIV>
+
+<SCRIPT>
+
+// initialize & close dialog window, initialize imgobj cache && image list
+$( '#insert_image_dialog' ).dialog({
+  width: 800,
+  height: 550,
+  resizable: true,
+  autoOpen: false,
+});
+var imgobj = new Object;
+refreshImageList(0);
+
+// this is the main func to invoke from links outside this file.
+// opens dialog if needed
+// updates dialog with passed imgnum
+// caches image info through an xmlhttp request if needed
+// pass 'upload' as imgnum for upload-only view
+function insertImageDialog (imgnum) {
+  if (imgnum == 'upload') {
+    $('#insert_image_form').hide();
+    $('#insert_image_preview_box').hide();
+    imgnum = undefined;
+  } else {
+    $('#insert_image_form').show();
+    $('#insert_image_preview_box').show();
+  }
+  if (imgnum && !imgobj[imgnum]) {
+    clearInsertImageDialog();
+    $('#insert_image_loading').show();
+    $('#insert_image_imgnum').val(imgnum);
+    get_template_image('imgnum',imgnum,
+      function (result) {
+        var images = JSON.parse(result) || [];
+        for (i = 0; i < images.length; i++) {
+          imgobj[images[i].imgnum] = images[i];
+        }
+        updateInsertImageDialog();
+      }
+    );
+  } else if (imgnum) {
+    $('#insert_image_imgnum').val(imgnum);
+    updateInsertImageDialog();
+  } else {
+    clearInsertImageDialog();
+  }
+  if (!$( '#insert_image_dialog' ).dialog( 'isOpen' )) {
+    $( '#insert_image_dialog' ).dialog( 'open' );
+  }
+}
+
+// sets dialog values to a default "Loading..." state, including imgnum
+function clearInsertImageDialog () {
+  $('#insert_image_imgnum').val('');
+  $('#insert_image_preview').attr('src','');
+  $('#insert_image_loading').hide();
+}
+
+// updates preview src from cache based on imgnum from form
+// then calls previewInsertImage
+function updateInsertImageDialog () {
+  var imgnum = $('#insert_image_imgnum').val();
+  $('#insert_image_loading').hide();
+  $('#insert_image_preview').attr('src',imgobj[imgnum].src);
+  previewInsertImage();
+}
+
+// updates preview width/height/alt/float based on current form values
+function previewInsertImage () {
+  $('#insert_image_preview').css('width',$('#insert_image_width').val());
+  $('#insert_image_preview').css('height',$('#insert_image_height').val());
+  $('#insert_image_preview').css('float',$('#insert_image_float').val());
+  $('#insert_image_preview').attr('alt',$('#insert_image_alt').val());
+}
+
+// constructs html based on the form contents,
+// passes it to callback & closes dialog
+function insertImage() {
+  var imgnum = $('#insert_image_imgnum').val();
+  if (!(imgnum && imgobj[imgnum])) {
+    return '';
+  }
+  var width = $('#insert_image_width').val() || '';
+  var height = $('#insert_image_height').val() || '';
+  var alt = $('#insert_image_alt').val() || '';
+  var float = $('#insert_image_float').val();
+  var imgtag = '<IMG SRC="' + imgobj[imgnum].src + '"';
+  if (width) {
+    imgtag += ' WIDTH="' + width + '"';
+  }
+  if (height) {
+    imgtag += ' HEIGHT="' + height + '"';
+  }
+  if (alt) {
+    imgtag += ' ALT="' + alt + '"';
+  }
+  if (float) {
+    imgtag += ' STYLE="float: ' + float + '"';
+  }
+  imgtag += '>';
+  <% $opt{'callback'} %>(imgtag);
+  $( '#insert_image_dialog' ).dialog( 'close' );
+}
+
+// uses xmlhttp request to initialize image list & refresh it after uploads
+function refreshImageList (fromupload) {
+  get_template_image('no_src','1',
+    function (result) {
+      if (fromupload) {
+        $("#TemplateImageUploadForm")[0].reset();
+      }
+      var images = JSON.parse(result) || [];
+      var latest;
+      for (i = 0; i < images.length; i++) {
+        if ( $("#insert_image_imgnum option[value='" + images[i].imgnum + "']").length == 0 ) {
+          $("#insert_image_imgnum").append('<OPTION VALUE="'+images[i].imgnum+'">'+images[i].name+'</OPTION>');
+          latest = images[i].imgnum;
+        }
+      }
+      if (fromupload) {
+        location.hash = "insert_image_dialog";
+        if (latest) {
+          // small risk of a race condition with other newly-uploaded images,
+          // but does no real damage (our image still shows up in the list)
+          insertImageDialog(latest);
+        }
+      }
+    }
+  );
+}
+
+</SCRIPT>
+
+<%init>
+my %opt = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+  unless $curuser->access_right([ 'View templates', 'View global templates',
+                                  'Edit templates', 'Edit global templates', ]);
+
+my $canedit = $curuser->access_right([ 'Edit templates', 'Edit global templates' ]);
+</%init>
+
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
index 47e6a5b..8ac44af 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -36,7 +36,7 @@ should be used to set msgnum or from/subject/html_body cgi params
 % }
 
 
-<FORM NAME="OneTrueForm" ACTION="<% $form_action %>" METHOD="GET">
+<FORM NAME="OneTrueForm" ACTION="<% $form_action %>" METHOD="POST">
 <INPUT TYPE="hidden" NAME="table" VALUE="<% $table %>">
 %# Mixing search params with from address, subject, etc. required special-case
 %# handling of those, risked name conflicts, and caused massive problems with 
diff --git a/httemplate/misc/process/template_image-delete.cgi b/httemplate/misc/process/template_image-delete.cgi
new file mode 100644
index 0000000..58c3f2c
--- /dev/null
+++ b/httemplate/misc/process/template_image-delete.cgi
@@ -0,0 +1,28 @@
+<% $server->process %>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+# make sure user can generally edit
+die "access denied"
+  unless $curuser->access_right([ 'Edit templates', 'Edit global templates' ]);
+
+# make sure user can edit this particular image
+my %arg = $cgi->param('arg');
+my $imgnum = $arg{'imgnum'};
+die "bad imgnum" unless $imgnum =~ /^\d+$/;
+die "access denied" unless qsearchs({
+               'table'     => 'template_image',
+               'select'    => 'imgnum',
+               'hashref'   => { 'imgnum' => $imgnum },
+               'extra_sql' => ' AND ' . 
+                              $curuser->agentnums_sql(
+                                'null_right' => ['Edit global templates']
+                              ),
+             });
+
+my $server =
+  new FS::UI::Web::JSRPC 'FS::template_image::process_image_delete', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/template_image-upload.cgi b/httemplate/misc/process/template_image-upload.cgi
new file mode 100644
index 0000000..c3c9059
--- /dev/null
+++ b/httemplate/misc/process/template_image-upload.cgi
@@ -0,0 +1,26 @@
+<% $server->process %>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+  unless $curuser->access_right([ 'Edit templates', 'Edit global templates' ]);
+
+my %arg = $cgi->param('arg');
+my $agentnum = $arg{'agentnum'};
+
+if (!$agentnum) {
+  die "access denied"
+    unless $curuser->access_right([ 'Edit global templates' ]);
+} else {
+  die "bad agentnum"
+    unless $agentnum =~ /^\d+$/;
+  die "access denied"
+    unless $curuser->agentnum($agentnum);
+}
+
+my $server =
+  new FS::UI::Web::JSRPC 'FS::template_image::process_image_upload', $cgi;
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-template_image.cgi b/httemplate/misc/xmlhttp-template_image.cgi
new file mode 100644
index 0000000..a8c50ed
--- /dev/null
+++ b/httemplate/misc/xmlhttp-template_image.cgi
@@ -0,0 +1,48 @@
+<%doc>
+Returns JSON encoded array of objects with details about FS::template_image
+objects.  Attributes in each returned object are imgnum, name, and src.
+
+Accepts the following options:
+
+imgnum - only return object for this imgnum
+
+no_src - do not include the src field
+
+</%doc>
+<% encode_json(\@result) %>\
+<%init>
+use FS::template_image;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+  unless $curuser->access_right([ 'View templates', 'View global templates',
+                                  'Edit templates', 'Edit global templates', ]);
+
+my %arg = $cgi->param('arg');
+
+my $search = {
+  'table' => 'template_image',
+  'hashref' => {},
+};
+
+my $imgnum = $arg{'imgnum'} || '';
+die "Bad imgnum" unless $imgnum =~ /^\d*$/;
+$search->{'hashref'}->{'imgnum'} = $imgnum if $imgnum;
+
+$search->{'select'} = 'imgnum, name' if $arg{'no_src'};
+
+$search->{'extra_sql'} = ($imgnum ? ' AND ' : ' WHERE ')
+                       . $curuser->agentnums_sql(
+                           'null_right' => ['View global templates','Edit global templates']
+                         );
+
+my @images = qsearch($search); #needs agent virtualization
+
+my @result = map { +{
+  'imgnum' => $_->imgnum,
+  'name'   => $_->name,
+  'src'    => $arg{'no_src'} ? '' : $_->src,
+} } @images;
+
+</%init>

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

Summary of changes:
 FS/FS/Schema.pm                                    |   14 +
 FS/FS/template_image.pm                            |  222 ++++++++++++++++
 httemplate/browse/msg_template.html                |    3 +-
 httemplate/browse/template_image.html              |   68 +++++
 httemplate/edit/msg_template.html                  |   11 +-
 httemplate/elements/form-file_upload.html          |    1 +
 .../elements/images}/ui-icons_ef8c08_256x240.png   |  Bin 4369 -> 4369 bytes
 httemplate/elements/template_image-dialog.html     |  279 ++++++++++++++++++++
 httemplate/misc/email-customers.html               |    2 +-
 httemplate/misc/process/template_image-delete.cgi  |   28 ++
 httemplate/misc/process/template_image-upload.cgi  |   26 ++
 httemplate/misc/xmlhttp-template_image.cgi         |   48 ++++
 12 files changed, 698 insertions(+), 4 deletions(-)
 create mode 100644 FS/FS/template_image.pm
 create mode 100644 httemplate/browse/template_image.html
 copy {rt/share/html/NoAuth/images/jquery_ui => httemplate/elements/images}/ui-icons_ef8c08_256x240.png (100%)
 mode change 100755 => 100644
 create mode 100644 httemplate/elements/template_image-dialog.html
 create mode 100644 httemplate/misc/process/template_image-delete.cgi
 create mode 100644 httemplate/misc/process/template_image-upload.cgi
 create mode 100644 httemplate/misc/xmlhttp-template_image.cgi




More information about the freeside-commits mailing list