view release on metacpan or search on metacpan
1.001008 2019-12-05
- Fixed incorrect plugin priority handling on newer perls
1.001007 2017-01-04
- Update for 5.26 '{' in regex stuff
1.001006 2015-03-21
- Fixed FileUpload plugin not erroring out correctly for
too-large uploads (Fixes #7)
- Fixed FileUpload plugin not taking scalar ref as `name` value
as advertized (Fixes #6)
1.001005 2015-03-10
- Fixes #4: zofcms_helper must respect zcms_template_extension
1.001004 2014-02-14
- Added missing zofcms_helper that got left out during
conversion to dzil
- Added a big warning in the docs, discouraging people use this framework
ZofCMS stands for "Zoffix's Content Management System", however I prefer
it to be just a name. It is a small web framework/templating system designed
to be easily installed and workable on limited severs, i.e. the ones that do
not allow you to install perl modules from CPAN, don't have ssh and
occasionally don't even offer any SQL databases. If you have more freedom
than that you may want to give [Catalyst](https://metacpan.org/pod/Catalyst) a try which, my opinion, is
a great framework, not just for web, and it offers far more functionality
than ZofCMS ever will.
ZofCMS is plugin based. If you create your own plugins, please upload them
to [App::ZofCMS::Plugin](https://metacpan.org/pod/App%3A%3AZofCMS%3A%3APlugin) namespace or email it to me (`zoffix@cpan.org`)
and I will package it, upload it, and give you corresponding credits.
ZofCMS currently uses [HTML::Template](https://metacpan.org/pod/HTML%3A%3ATemplate) as a module to interpret HTML
templates. And so far, I have no plans to change this to anything alike
[Template::Toolkit](https://metacpan.org/pod/Template%3A%3AToolkit).
Despite the "core" of the framework along with all of its plugins being
on CPAN there is a helper script (`zofcms_helper`) which can produce
a ready-for-upload set of files which you can simply upload to your server
without having to install anything from CPAN on the server itself. See
`perldoc zofcms_helper`.
# HYSTORY
This section does not say anything useful, you can skip it if you are
not interested in what made me create ZofCMS.
For about two-three years name "ZofCMS" lived more as a joke. A lot of
people in IRC channels such as `#css` would ask me what web framework
or ability to install any perl modules directly from CPAN, I already felt
that something needed to be done. The "perl hashref" templates which I used
to make all those products displayed with only one [HTML::Template](https://metacpan.org/pod/HTML%3A%3ATemplate)
template proved to be flexible, extendable and maintainable and that's
exactly from where ZofCMS template format came.
The last site I coded before starting to implement ZofCMS was a private
web application which had a message board along with a few other features.
Mostly everything was coded from scratch once more... The final breaking
point when a few weeks later I was asked to add two sections for file
uploads to that site. No, it wasn't hard to add them, it's just that
I found myself adding a couple lines of code to the "core" modules that
called modules which provided new functionality and those modules were
loaded on any page of the site; even the ones that would never require
functionality from those modules. That's where the idea of plugins came
to life including the idea of "page templates" asking for plugins which
are needed only on that specific page.
After being told at work that I will be putting up about nine sites in
near future I started putting actual ZofCMS code "on paper". The first
"site" was a single page because the content for it was not yet ready, we
bin/zofcms_helper view on Meta::CPAN
=head2 C<--plugins>
zofcms_helper --site zcms --plugins DBI,QueryToTemplate,OtherPlugin
This options takes a comma (C<,>) separated list of plugin names to copy
over into your "core" directory. This is useful if your server does
not support module installation from CPAN; i.e. the helper script
copies the modules installed on your system into "core" directory from
where ZofCMS can load them, thus when you are done with your site you
can simply upload C<--site> and C<--core> directories to your server and
everything should work just fine.
B<NOTE:> do not include the C<App::ZofCMS::Plugin::> part of the name
of the modules, the above command installs L<App::ZofCMS::Plugin::DBI>,
L<App::ZofCMS::Plugin::QueryToTemplate> and
C<App::ZofCMS::Plugin::OtherPlugin> modules into the "core" directory.
B<Note:> the command presented above will recreate the "core" files and
C<index.pl> file. To avoid that, i.e. just add the plugins, use the
<--nocore> option
bin/zofcms_helper view on Meta::CPAN
=head2 C<--cpan>
zofcms_helper --site zcms --cpan Data::Tranformer,Foo::Bar::Baz
This option has the same purpose as the C<--plugins> except this one
copies your installed CPAN modules to $core_dir/CPAN/
For example, Data::Transformer module is required by L<App::ZofCMS::Tagged>
plugin, using the C<--cpan> command you can copy it over into ZofCMS
"core directory" and upload your application to the server that does
not have Data::Transformer installed. B<Note:> this option is not that
smart, it's not going to copy anything but the actual .pm file for
the module you've specified.
=head2 C<--nocore>
zofcms_helper --nocore --site zcms --plugins DBI,QueryToTemplate
The C<--nocore> option tells the helper script not to create any "core"
directories or files, the command above will only install C<DBI> and
lib/App/ZofCMS.pm view on Meta::CPAN
ZofCMS stands for "Zoffix's Content Management System", however I prefer
it to be just a name. It is a small web framework/templating system designed
to be easily installed and workable on limited severs, i.e. the ones that do
not allow you to install perl modules from CPAN, don't have ssh and
occasionally don't even offer any SQL databases. If you have more freedom
than that you may want to give L<Catalyst> a try which, my opinion, is
a great framework, not just for web, and it offers far more functionality
than ZofCMS ever will.
ZofCMS is plugin based. If you create your own plugins, please upload them
to L<App::ZofCMS::Plugin> namespace or email it to me (C<zoffix@cpan.org>)
and I will package it, upload it, and give you corresponding credits.
ZofCMS currently uses L<HTML::Template> as a module to interpret HTML
templates. And so far, I have no plans to change this to anything alike
L<Template::Toolkit>.
Despite the "core" of the framework along with all of its plugins being
on CPAN there is a helper script (C<zofcms_helper>) which can produce
a ready-for-upload set of files which you can simply upload to your server
without having to install anything from CPAN on the server itself. See
C<perldoc zofcms_helper>.
=head1 HYSTORY
This section does not say anything useful, you can skip it if you are
not interested in what made me create ZofCMS.
For about two-three years name "ZofCMS" lived more as a joke. A lot of
people in IRC channels such as C<#css> would ask me what web framework
lib/App/ZofCMS.pm view on Meta::CPAN
or ability to install any perl modules directly from CPAN, I already felt
that something needed to be done. The "perl hashref" templates which I used
to make all those products displayed with only one L<HTML::Template>
template proved to be flexible, extendable and maintainable and that's
exactly from where ZofCMS template format came.
The last site I coded before starting to implement ZofCMS was a private
web application which had a message board along with a few other features.
Mostly everything was coded from scratch once more... The final breaking
point when a few weeks later I was asked to add two sections for file
uploads to that site. No, it wasn't hard to add them, it's just that
I found myself adding a couple lines of code to the "core" modules that
called modules which provided new functionality and those modules were
loaded on any page of the site; even the ones that would never require
functionality from those modules. That's where the idea of plugins came
to life including the idea of "page templates" asking for plugins which
are needed only on that specific page.
After being told at work that I will be putting up about nine sites in
near future I started putting actual ZofCMS code "on paper". The first
"site" was a single page because the content for it was not yet ready, we
lib/App/ZofCMS/Plugin.pm view on Meta::CPAN
}
1;
__END__
PLEASE INCLUDE DECENT PLUGIN DOCUMENTATION
=head1 DESCRIPTION
This documentation is intended for ZofCMS plugin authors, whether you are
coding a plugin for personal use or planning to upload to CPAN. Uploads
are more than welcome.
First of all, the plugin must be located in App::ZofCMS::Plugin:: namespace.
At the very least the plugin must contain to subs:
sub new { bless {}, shift }
This is a constructor, you don't have to use a hashref for the object but
it's recommended. Currently no arguments (except a class name) are passed
lib/App/ZofCMS/Plugin/BoolSettingsManager.pm view on Meta::CPAN
In ZofCMS Template:
plugins => [
qw/BoolSettingsManager/,
],
plug_bool_settings_manager => {
settings => [
notice_forum => q|new forum posts|,
notice_flyers => q|new flyer uploads|,
notice_photo_library => q|new images added to Photo Library|,
],
# everything below is optional; default values are shown
dsn => "DBI:mysql:database=test;host=localhost",
user => '',
pass => undef,
opt => { RaiseError => 1, AutoCommit => 1 },
table => 'users',
login_col => 'login',
lib/App/ZofCMS/Plugin/BoolSettingsManager.pm view on Meta::CPAN
plugins => [ qw/BoolSettingsManager/ ],
B<Mandatory>. You need to include the plugin in the list of plugins
to execute.
=head2 C<plug_bool_settings_manager>
plug_bool_settings_manager => {
settings => [
notice_forum => q|new forum posts|,
notice_flyers => q|new flyer uploads|,
notice_photo_library => q|new images added to Photo Library|,
],
# everything below is optional; default values are shown
dsn => "DBI:mysql:database=test;host=localhost",
user => '',
pass => undef,
opt => { RaiseError => 1, AutoCommit => 1 },
table => 'users',
login_col => 'login',
lib/App/ZofCMS/Plugin/BoolSettingsManager.pm view on Meta::CPAN
(in that order), where C<$t> is ZofCMS Template hashref,
C<$q> is query parameters hashref, and C<$config> is the
L<App::ZofCMS::Config> object. Possible keys/values for the hashref
are as follows:
=head3 C<settings>
plug_bool_settings_manager => {
settings => [
notice_forum => q|new forum posts|,
notice_flyers => q|new flyer uploads|,
notice_photo_library => q|new images added to Photo Library|,
],
...
plug_bool_settings_manager => {
settings => sub {
my ( $t, $q, $config ) = @_;
return $arrayref_to_assing_to_settings;
},
...
lib/App/ZofCMS/Plugin/BoolSettingsManager.pm view on Meta::CPAN
<tmpl_var name='plug_bool_settings_manager_form'>
=head1 HTML CODE GENERATED BY THE PLUGIN
The HTML code below was generated after saving settings in the form
generated using this plugin's C<settings> argument:
settings => [
notice_forum => q|new forum posts|,
notice_flyers => q|new flyer uploads|,
notice_photo_library => q|new images added to Photo Library|,
],
Notice the "keys" in the C<settings> arrayref are used to generate
C<id=""> attributes on the C<< <li> >> and C<< <input> >> elements
(and C<for=""> attribute on C<< <label> >>s). The value for C<page>
hidden C<< <input> >> is derived by the plugin automagically.
<p class="success-message">Successfully saved</p>
lib/App/ZofCMS/Plugin/BoolSettingsManager.pm view on Meta::CPAN
id="pbsm_notice_forum"
name="notice_forum"
><label for="pbsm_notice_forum"
class="checkbox_label"> new forum posts</label>
</li>
<li id="pbsm_container_notice_flyers">
<input type="checkbox"
id="pbsm_notice_flyers"
name="notice_flyers"
><label for="pbsm_notice_flyers"
class="checkbox_label"> new flyer uploads</label>
</li>
<li id="pbsm_container_notice_photo_library">
<input type="checkbox"
id="pbsm_notice_photo_library"
name="notice_photo_library"
checked
><label for="pbsm_notice_photo_library"
class="checkbox_label"> new images added to Photo Library</label>
</li>
</ul>
lib/App/ZofCMS/Plugin/CRUD.pm view on Meta::CPAN
if ( @errors ) {
$self->{FORM_ERRORS} = \@errors;
return;
}
for my $item (
grep $_->{el_file}
&& defined $self->{Q}{ $_->{name} }
&& length $self->{Q}{ $_->{name} }, @{ $self->{ITEMS} || [] }
) {
push @errors, grep defined, $self->_process_file_upload( $item );
}
if ( @errors ) {
$self->{FORM_ERRORS} = \@errors;
return;
}
$self->_insert_CREATE_into_db;
}
sub _process_file_upload {
my $self = shift;
my $item = shift;
my $cgi = $self->{CONFIG}{cgi};
my $fh = $cgi->upload( $item->{name} );
if ( not $fh and $cgi->cgi_error ) {
return +{ error => 'File upload error: ' . $cgi->cgi_error };
}
return +{ error => q|File upload error (no error message available)| }
unless $fh;
( my $filename = $item->{value} ) =~ s/[^\w.-]/_/g;
while ( -e File::Spec->catdir( $self->{CONF}{file_dir}, $filename ) ) {
$filename = "_$filename";
}
$filename = File::Spec->catdir( $self->{CONF}{file_dir}, $filename );
return +{ error => "Failed to open local file $filename [$!]" }
lib/App/ZofCMS/Plugin/CRUD.pm view on Meta::CPAN
updating this record
=back
=head3 C<file_dir>
file_dir => 'files',
B<Optional.> B<Defaults to> C<files>. B<Takes> a string as a value that
specifies the directory (relative to C<index.pl>) where the plugin
will store files uploaded by the user (that is for any records for which
C<< <input type="file"> >> was used in the Create form).
=head3 C<can>
can => 'CRUDL',
can => 'RL',
can => 'CUL',
lib/App/ZofCMS/Plugin/FileUpload.pm view on Meta::CPAN
our $VERSION = '1.001010'; # VERSION
use File::Spec::Functions (qw/catfile splitpath/);
sub new { bless {}, shift }
sub process {
my ( $self, $template, $query, $config ) = @_;
return
unless $template->{file_upload};
my $uploads = delete $template->{file_upload};
$uploads = [ $uploads ]
unless ref $uploads eq 'ARRAY';
my $upload_counter = @$uploads == 1 ? '' : 0;
for my $upload ( @$uploads ) {
$self->_process_upload(
$template,
$query,
$config,
$upload_counter++,
$upload,
);
}
return 1;
}
sub _process_upload {
my ( $self, $template, $query, $config, $upload_counter, $upload ) = @_;
my $error_key = "upload_error$upload_counter";
my $filename_key = "upload_filename$upload_counter";
my $success_key = "upload_success$upload_counter";
$upload = {
query => 'zofcms_upload',
path => 'zofcms_upload',
name => '[rand]',
%$upload,
};
my $cgi = $config->cgi;
my $remote_filename = $cgi->param( $upload->{query} );
if ( not $remote_filename and $cgi->cgi_error ) {
$template->{t}{ $error_key } = $cgi->cgi_error;
return;
}
return
unless defined $remote_filename;
( $upload->{ext} ) = $remote_filename =~ /[^.]+([.].+)$/
unless defined $upload->{ext};
$upload->{ext} = ''
unless defined $upload->{ext};
if ( ref $upload->{name} eq 'CODE' ) {
$upload->{name} = $upload->{name}->( $template, $query, $config );
}
if ( $upload->{name} eq '[rand]' ) {
UNIQUE_NAME: {
$upload->{name} = catfile(
$upload->{path},
do { my $x = rand() . time(); $x =~ tr/.//d; $x }
. $upload->{ext}
);
redo UNIQUE_NAME if -e $upload->{name};
}
}
elsif ( ref $upload->{name} eq 'SCALAR' ) {
$upload->{name} = ( splitpath $remote_filename )[-1];
$upload->{name} =~ s/(?<=[^.])[.].+$//;
$upload->{name} = catfile(
$upload->{path},
$upload->{name} . $upload->{ext}
);
}
elsif ( ref $upload->{name} eq 'SCALAR' ) {
$upload->{name} = catfile(
$upload->{path},
( $remote_filename =~ /([^.]+)(?:[.].+)?$/)[0] . $upload->{ext}
);
}
else {
$upload->{name} = catfile(
$upload->{path},
$upload->{name} . $upload->{ext}
);
}
if ( $upload->{name_filter} ) {
$upload->{name} =~ s/$upload->{name_filter}//g;
}
my $upload_info = $cgi->uploadInfo( $remote_filename );
if ( defined $upload->{content_type} ) {
$upload->{content_type} = [ $upload->{content_type} ]
unless ref $upload->{content_type} eq 'ARRAY';
unless ( grep { $upload_info->{'Content-Type'} eq $_ }
@{ $upload->{content_type} }
) {
$template->{t}{ $error_key } = 'Invalid file type';
return;
}
}
my $fh = $cgi->upload( $upload->{query} );
if ( not $fh and $cgi->cgi_error ) {
$template->{t}{ $error_key } = $cgi->cgi_error;
return;
}
return
unless $fh;
my $fh_out;
unless ( open $fh_out, '>', $upload->{name} ) {
$template->{t}{ $error_key } = "Failed to open local file [$!]";
return;
}
seek $fh, 0, 0;
binmode $fh;
binmode $fh_out;
{
local $/ = \1024;
while ( <$fh> ) {
print $fh_out $_;
}
}
close $fh;
close $fh_out;
if ( ref $upload->{on_success} ) {
$upload->{on_success}->(
$upload->{name}, $template, $query, $config,
);
}
$template->{t}{ $success_key } = 1;
$template->{t}{ $filename_key } = $upload->{name};
return 1;
}
1;
__END__
=encoding utf8
=head1 NAME
App::ZofCMS::Plugin::FileUpload - ZofCMS plugin to handle file uploads
=head1 SYNOPSIS
In your ZofCMS template:
file_upload => {
query => 'uploaded_file',
},
plugins => [ qw/FileUpload/ ],
In your L<HTML::Template> template:
<tmpl_if name="upload_error">
<p class="error">Upload failed: <tmpl_var name="upload_error">
</tmpl_if>
<tmpl_if name="upload_success">
<p>Upload succeeded: <tmpl_var name="upload_filename"></p>
</tmpl_if>
<form action="" method="POST" enctype="multipart/form-data">
<div>
<input type="file" name="uploaded_file">
<input type="submit" value="Upload">
</div>
</form>
=head1 DESCRIPTION
The module is a ZofCMS plugin which provides means to easily handle file
uploads.
This documentation assumes you've read
L<App::ZofCMS>, L<App::ZofCMS::Config> and L<App::ZofCMS::Template>
=head1 FIRST-LEVEL ZofCMS TEMPLATE KEYS
=head2 C<plugins>
plugins => [ qw/FileUpload/ ],
First and obvious, you need to stick C<FileUpload> in the list of your
plugins.
=head2 C<file_upload>
file_upload => {
query => 'upload',
path => 'zofcms_upload',
name => 'foos',
ext => '.html',
content_type => 'text/html',
on_success => sub {
my ( $uploaded_file_name, $template, $query, $conf ) = @_;
# do something useful
}
},
# or
file_upload => [
{ query => 'upload1', },
{ query => 'upload2', },
{}, # all the defaults
{
query => 'upload4',
name => 'foos',
ext => '.html',
content_type => 'text/html',
on_success => sub {
my ( $uploaded_file_name, $template, $query, $conf ) = @_;
# do something useful
}
},
],
Plugin takes input from C<file_upload> first level ZofCMS template key which
takes an arrayref or a hashref as a value. Passing a hashref as a value
is the same as passing an arrayref with just that hashref as an element.
Each element of the given arrayref is a hashref which
represents one file upload. The possible keys/values of those hashrefs
are as follows:
=head3 C<query>
{ query => 'zofcms_upload' },
B<Optional>. Specifies the query parameter which is the file being uploaded,
in other words, this is the value of the C<name=""> attribute of the
C<< <input type="file"... >>. B<Defaults to:> C<zofcms_upload>
=head3 C<path>
{ path => 'zofcms_upload', }
B<Optional>. Specifies directory (relative to C<index.pl>) into which
the plugin will store uploaded files. B<Defaults to:> C<zofcms_upload>
=head3 C<name>
{ name => 'foos', }
{ name => '[rand]', }
{ name => \1 } # any scalar ref
{
name => sub {
my ( $t, $q, $config ) = @_;
return 'file_name.png';
},
}
B<Optional>. Specifies the name (without the extension)
of the local file into which save the uploaded file. Special value of
C<[rand]> specifies that the name should be random, in which case it
will be created by calling C<rand()> and C<time()> and removing any dots
from the concatenation of those two. If a I<scalarref> is specified
(irrelevant of its value), the plugin will use the filename that the
browser gave it (relying on L<File::Spec::Functions>'s
C<splitpath> here; also, note that extension will be obtained
using C<ext> argument (see below). The C<name> parameter can also take
a subref, if that's the case, then the C<name> parameter will obtain
its value from the return value of that subref. The subref's C<@_> will
contain the following (in that order): ZofCMS Template hashref, hashref
of query parameters and L<App::ZofCMS::Config> object.
B<Defaults to:> C<[rand]>
=head3 C<ext>
{ ext => '.html', }
B<Optional>. Specifies the extension to use for the name of local file
into which the upload will be stored. B<By default> is not specified
and therefore the extension will be obtained from the name of the remote
file.
=head3 C<name_filter>
{ name_filter => qr/Z(?!ofcms)/i, }
B<Optional>. Takes a regex ref (C<qr//>) as a value. Anything
in the C<path> + C<name> + C<ext> final string (regardles of how each
of those is obtained) that matches this regex
lib/App/ZofCMS/Plugin/FileUpload.pm view on Meta::CPAN
=head3 C<content_type>
{ content_type => 'text/html', }
{ content_type => [ 'text/html', 'image/jpeg' ], }
B<Optional>. Takes either a scalar string or an arrayref of strings.
Specifying a string is equivalent to specifying an arrayref with just that
string as an element. Each element of the given arrayref indicates the
allowed Content-Type of the uploaded files. If the Content-Type does
not match allowed types the error will be shown (see HTML TEMPLATE VARS
section below). B<By default> all Content-Types are allowed.
=head3 C<on_success>
on_success => sub {
my ( $uploaded_file_name, $template, $query, $config ) = @_;
# do something useful
}
B<Optional>. Takes a subref as a value. The specified sub will be
executed upon a successful upload. The C<@_> will contain the following
elements: C<$uploaded_file_name, $template, $query, $config> where
C<$uploaded_file_name> is the directory + name + extension of the local
file into which the upload was stored, C<$template> is a hashref of
your ZofCMS template, C<$query> is a hashref of query parameters and
C<$config> is the L<App::ZofCMS::Config> object. B<By default> is not
specified.
=head1 HTML TEMPLATE VARS
Single upload:
<tmpl_if name="upload_error">
<p class="error">Upload failed: <tmpl_var name="upload_error">
</tmpl_if>
<tmpl_if name="upload_success">
<p>Upload succeeded: <tmpl_var name="upload_filename"></p>
</tmpl_if>
<form action="" method="POST" enctype="multipart/form-data">
<div>
<input type="file" name="upload">
<input type="submit" value="Upload">
</div>
</form>
Multi upload:
<tmpl_if name="upload_error0">
<p class="error">Upload 1 failed: <tmpl_var name="upload_error0">
</tmpl_if>
<tmpl_if name="upload_success0">
<p>Upload 1 succeeded: <tmpl_var name="upload_filename0"></p>
</tmpl_if>
<tmpl_if name="upload_error1">
<p class="error">Upload 2 failed: <tmpl_var name="upload_error1">
</tmpl_if>
<tmpl_if name="upload_success1">
<p>Upload 2 succeeded: <tmpl_var name="upload_filename1"></p>
</tmpl_if>
<form action="" method="POST" enctype="multipart/form-data">
<div>
<input type="file" name="upload">
<input type="file" name="upload2">
<input type="submit" value="Upload">
</div>
</form>
B<NOTE:> upload of multiple files from a single C<< <input type="file"... >>
is currently not supported. Let me know if you need such functionality.
The folowing C<< <tmpl_var name=""> >>s will be set in your
L<HTML::Template> template.
=head2 SINGLE AND MULTI
If you are handling only one upload, i.e. you have only one hashref in
C<file_upload> ZofCMS template key and you have only one
C<< <input type="file"... >> then the HTML::Template variables described
below will B<NOT> have any trailing numbers, otherwise each of them
will have a trailing number indicating the number of the upload. This number
will starts from B<zero> and it will correspond to the index of hashref of
C<file_upload> arrayref.
=head2 C<upload_error>
# single
<tmpl_if name="upload_error">
<p class="error">Upload failed: <tmpl_var name="upload_error">
</tmpl_if>
# multi
<tmpl_if name="upload_error0">
<p class="error">Upload 1 failed: <tmpl_var name="upload_error0">
</tmpl_if>
The C<upload_error> will be set if some kind of an error occurred during
the upload of the file. This also includes if the user tried to upload
a file of type which is not listed in C<content_type> arrayref.
=head2 C<upload_success>
# single
<tmpl_if name="upload_success">
<p>Upload succeeded: <tmpl_var name="upload_filename"></p>
</tmpl_if>
# multi
<tmpl_if name="upload_success0">
<p>Upload 1 succeeded: <tmpl_var name="upload_filename0"></p>
</tmpl_if>
The C<upload_success> will be set to a true value upon successful upload.
=head2 C<upload_filename>
# single
<tmpl_if name="upload_success">
<p>Upload succeeded: <tmpl_var name="upload_filename"></p>
</tmpl_if>
# multi
<tmpl_if name="upload_success0">
<p>Upload 1 succeeded: <tmpl_var name="upload_filename0"></p>
</tmpl_if>
The C<upload_filename> will be set to directory + name + extension of the
local file into which the upload was saved.
=head1 REPOSITORY
Fork this module on GitHub:
L<https://github.com/zoffixznet/App-ZofCMS>
=head1 BUGS
To report bugs or request features, please use
L<https://github.com/zoffixznet/App-ZofCMS/issues>