From: Jonathan Prykop Date: Tue, 18 Aug 2015 04:01:31 +0000 (-0500) Subject: RT#18830: Upload file to message template X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=89525f062092c185344ec7318406b1c9086d1eda RT#18830: Upload file to message template --- diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 184c6c951..55dc99eca 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 000000000..e7f4baba5 --- /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 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; + ''; +} + +=item process_image_delete + +Process for deleting an image. Run as a job using L. + +=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. + +=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 + +=cut + +1; + diff --git a/httemplate/browse/msg_template.html b/httemplate/browse/msg_template.html index ef0b2dafd..1646bc169 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 000000000..eb4325f15 --- /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!view! }; + +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 + $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 . 'delete'; +} : ''; + + diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html index 7f3824127..df72c5b66 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) {
Substitutions: ' . $widget->html . -'
Click links to insert. -
Enclose substitutions and other Perl expressions in braces: +'

Click above links to insert substitution code.

+

+Enclose substitutions and other Perl expressions in braces:
{ $name } = ExampleCo (Smith, John)
{ time2str("%D", time) } = '.time2str("%D", time).' +

'; +$sidebar .= include('/elements/template_image-dialog.html', + 'callback' => 'insertHtml' + ); +$sidebar .= '

Insert Uploaded Image

'; diff --git a/httemplate/elements/form-file_upload.html b/httemplate/elements/form-file_upload.html index 45b6c97f2..3542a5a8e 100644 --- a/httemplate/elements/form-file_upload.html +++ b/httemplate/elements/form-file_upload.html @@ -69,6 +69,7 @@ Example:
+ +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 + + + +<% include('/elements/xmlhttp.html', + 'url' => $p.'misc/xmlhttp-template_image.cgi', + 'subs' => [ 'get_template_image' ], + ) %> + +
+ + + + + +% if ($opt{'callback'}) { + + + + + + + + + + + + + + + + + + + +% } # if $opt{'callback'} + +
+ + + +<% &ntable("#cccccc", 2) %> + +
Image + +
Width
Height
Align + +
Alt Text
+ +
+ + + +% if ($canedit) { + +

<% emt('Upload New Image') %>

+ +<% 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' => "Agent", + 'empty_label' => 'Select agent', + 'agent_virt' => 1, + 'agent_null_right' => 'Edit global templates', + ) + %> + + <% include( '/elements/tr-file-upload.html', + 'field' => 'file', + 'label' => 'File', + ) + %> + + + + + + + + + + + +% } #if canedit + + + +
+

<% emt('Image Preview') %>

+ (<% emt('Loading image...') %>) + +
+ + +
+ + + +<%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' ]); + + diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index 47e6a5b48..8ac44afc1 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 % } -
+ %# 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 000000000..58c3f2c68 --- /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; + + diff --git a/httemplate/misc/process/template_image-upload.cgi b/httemplate/misc/process/template_image-upload.cgi new file mode 100644 index 000000000..c3c905981 --- /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; + + diff --git a/httemplate/misc/xmlhttp-template_image.cgi b/httemplate/misc/xmlhttp-template_image.cgi new file mode 100644 index 000000000..a8c50edf0 --- /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 + + +<% 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; + +