418 lines
14 KiB
Perl
418 lines
14 KiB
Perl
# ====================================================================
|
|
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
|
#
|
|
# GT::Template::Editor
|
|
# Author: Alex Krohn
|
|
# CVS Info :
|
|
# $Id: Editor.pm,v 2.19 2004/10/19 23:34:44 jagerman Exp $
|
|
#
|
|
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
|
# ====================================================================
|
|
#
|
|
# Description:
|
|
# A module for editing templates via an HTML browser.
|
|
#
|
|
|
|
package GT::Template::Editor;
|
|
# ===============================================================
|
|
use strict;
|
|
use GT::Base;
|
|
use vars qw(@ISA $VERSION $DEBUG $ATTRIBS $ERRORS);
|
|
@ISA = qw/GT::Base/;
|
|
$VERSION = sprintf "%d.%03d", q$Revision: 2.19 $ =~ /(\d+)\.(\d+)/;
|
|
$DEBUG = 0;
|
|
$ATTRIBS = {
|
|
cgi => undef,
|
|
root => undef,
|
|
backup => undef,
|
|
default_dir => '',
|
|
default_file => '',
|
|
date_format => '',
|
|
class => undef,
|
|
skip_dir => undef,
|
|
skip_file => undef,
|
|
select_dir => 'tpl_dir',
|
|
demo => undef
|
|
};
|
|
$ERRORS = {
|
|
CANTOVERWRITE => "Unable to overwrite file: %s (Permission Denied). Please set permissions properly and save again.",
|
|
CANTCREATE => "Unable to create new files in directory %s. Please set permissions properly and save again.",
|
|
CANTMOVE => "Unable to move file %s to %s: %s",
|
|
CANTMOVE => "Unable to copy file %s to %s: %s",
|
|
FILECOPY => "File::Copy is required in order to make backups.",
|
|
};
|
|
|
|
sub process {
|
|
# ------------------------------------------------------------------
|
|
# Loads the template editor.
|
|
#
|
|
my $self = shift;
|
|
|
|
my $sel_tpl_dir = $self->{select_dir};
|
|
my $selected_dir = $self->{cgi}->param($sel_tpl_dir) || $self->{default_dir} || 'default';
|
|
my $selected_file = $self->{cgi}->param('tpl_file') || '';
|
|
my $tpl_text = '';
|
|
my $error_msg = '';
|
|
my $success_msg = '';
|
|
my ($local, $restore) = (0, 0);
|
|
|
|
# Check the template directory and file
|
|
if ($selected_dir =~ m[[\\/\x00-\x1f]] or $selected_dir eq '..') {
|
|
$error_msg = "Invalid template directory $selected_dir";
|
|
$selected_dir = '';
|
|
$selected_file = '';
|
|
}
|
|
if ($selected_file =~ m[[\\/\x00-\x1f]]) {
|
|
$error_msg = "Invalid template $selected_file";
|
|
$selected_dir = '';
|
|
$selected_file = '';
|
|
}
|
|
|
|
# Create the local directory if it doesn't exist.
|
|
my $tpl_dir = $self->{root} . '/' . $selected_dir;
|
|
my $local_dir = $tpl_dir . "/local";
|
|
if ($selected_dir and ! -d $local_dir) {
|
|
mkdir($local_dir, 0777) or return $self->error('MKDIR', 'FATAL', $local_dir, "$!");
|
|
chmod(0777, $local_dir);
|
|
}
|
|
my $dir = $local_dir;
|
|
|
|
my $save = $self->{cgi}->param('tpl_name') || $self->{cgi}->param('tpl_file');
|
|
# Perform a save if requested.
|
|
if ($self->{cgi}->param('saveas') and $save and !$self->{demo}) {
|
|
$tpl_text = $self->{cgi}->param('tpl_text');
|
|
if (-e "$dir/$save" and ! -w _) {
|
|
$error_msg = sprintf($ERRORS->{CANTOVERWRITE}, $save);
|
|
}
|
|
elsif (! -e _ and ! -w $dir) {
|
|
$error_msg = sprintf($ERRORS->{CANTCREATE}, $dir);
|
|
}
|
|
else {
|
|
if ($self->{backup} and -e "$dir/$save") {
|
|
$self->copy("$dir/$save", "$dir/$save.bak");
|
|
}
|
|
local *FILE;
|
|
open (FILE, "> $dir/$save") or return $self->error(CANTOPEN => FATAL => "$dir/$save", "$!");
|
|
$tpl_text =~ s/\r\n/\n/g;
|
|
print FILE $tpl_text;
|
|
close FILE;
|
|
chmod 0666, "$dir/$save";
|
|
$success_msg = "File has been successfully saved.";
|
|
$local = 1;
|
|
$restore = 1 if -e "$self->{root}/$selected_dir/$save";
|
|
$selected_file = $save;
|
|
$tpl_text = '';
|
|
}
|
|
}
|
|
# Delete a local template (thereby restoring the system template)
|
|
elsif (my $restore = $self->{cgi}->param("restore") and !$self->{demo}) {
|
|
if ($self->{backup}) {
|
|
if ($self->move("$dir/$restore", "$dir/$restore.bak")) {
|
|
$success_msg = "System template '$restore' restored";
|
|
}
|
|
else {
|
|
$error_msg = "Unable to restore system template '$restore': Cannot move '$dir/$restore': $!";
|
|
}
|
|
}
|
|
else {
|
|
if (unlink "$dir/$restore") {
|
|
$success_msg = "System template '$restore' restored";
|
|
}
|
|
else {
|
|
$error_msg = "Unable to remove $dir/$restore: $!";
|
|
}
|
|
}
|
|
}
|
|
# Delete a local template (This is like restore, but happens when there is no system template)
|
|
elsif (my $delete = $self->{cgi}->param("delete") and !$self->{demo}) {
|
|
if ($self->{backup}) {
|
|
if ($self->move("$dir/$delete", "$dir/$delete.bak")) {
|
|
$success_msg = "Template '$delete' deleted";
|
|
}
|
|
else {
|
|
$error_msg = "Unable to delete template '$delete': Cannot move '$dir/$delete': $!";
|
|
}
|
|
}
|
|
else {
|
|
if (unlink "$dir/$delete") {
|
|
$success_msg = "Template '$delete' deleted";
|
|
}
|
|
else {
|
|
$error_msg = "Unable to remove $dir/$delete: $!";
|
|
}
|
|
}
|
|
}
|
|
|
|
# Load any selected template file.
|
|
if ($selected_file and ! $tpl_text) {
|
|
if (-f "$dir/$selected_file") {
|
|
local (*FILE, $/);
|
|
open FILE, "$dir/$selected_file" or die "Unable to open file $dir/$selected_file: $!";
|
|
$tpl_text = <FILE>;
|
|
close FILE;
|
|
$local = 1;
|
|
$restore = 1 if -e "$self->{root}/$selected_dir/$selected_file";
|
|
}
|
|
elsif (-f "$self->{root}/$selected_dir/$selected_file") {
|
|
local (*FILE, $/);
|
|
open FILE, "$self->{root}/$selected_dir/$selected_file" or die "Unable to open file $self->{root}/$selected_dir/$selected_file: $!";
|
|
$tpl_text = <FILE>;
|
|
close FILE;
|
|
}
|
|
else {
|
|
$selected_file = '';
|
|
}
|
|
}
|
|
|
|
# Load a README if it exists.
|
|
my $readme;
|
|
if (-e "$dir/README") {
|
|
local (*FILE, $/);
|
|
open FILE, "$dir/README" or die "unable to open readme: $dir/README ($!)";
|
|
$readme = <FILE>;
|
|
close FILE;
|
|
}
|
|
|
|
# Set the textarea width and height.
|
|
my $editor_rows = $self->{cgi}->param('cookie-editor_rows') || $self->{cgi}->cookie('editor_rows') || 15;
|
|
my $editor_cols = $self->{cgi}->param('cookie-editor_cols') || $self->{cgi}->cookie('editor_cols') || 55;
|
|
my $file_select = $self->template_file_select;
|
|
my $dir_select = $self->template_dir_select;
|
|
$tpl_text = $self->{cgi}->html_escape($tpl_text);
|
|
my $stats = $selected_file ? $self->template_file_stats($selected_file) : {};
|
|
|
|
if ($self->{demo} and ($self->{cgi}->param('saveas') or $self->{cgi}->param("delete") or $self->{cgi}->param("restore"))) {
|
|
$error_msg = 'This feature has been disabled in the demo!';
|
|
}
|
|
return {
|
|
tpl_name => $selected_file,
|
|
tpl_file => $selected_file,
|
|
local => $local,
|
|
restore => $restore,
|
|
tpl_text => \$tpl_text,
|
|
error_message => $error_msg,
|
|
success_message => $success_msg,
|
|
tpl_dir => $selected_dir,
|
|
readme => $readme,
|
|
editor_rows => $editor_rows,
|
|
editor_cols => $editor_cols,
|
|
dir_select => $dir_select,
|
|
file_select => $file_select,
|
|
%$stats
|
|
};
|
|
}
|
|
|
|
sub _skip_files {
|
|
my ($skip, $file) = @_;
|
|
return 1 if $skip->{$file}
|
|
or substr($file, 0, 1) eq '.' # skip dotfiles
|
|
or substr($file, -4) eq '.bak'; # skip .bak files
|
|
foreach my $f (keys %$skip) {
|
|
my $match = quotemeta $f;
|
|
$match =~ s/\\\*/.*/g;
|
|
$match =~ s/\\\?/./g;
|
|
return 1 if $file =~ /^$match$/;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub template_file_select {
|
|
# ------------------------------------------------------------------
|
|
# Returns a select list of templates in a given dir.
|
|
#
|
|
my $self = shift;
|
|
my $path = $self->{root};
|
|
my %files;
|
|
my $sel_tpl_dir = $self->{select_dir};
|
|
my $selected_dir = $self->{cgi}->param($sel_tpl_dir) || $self->{default_dir} || 'default';
|
|
my $selected_file = $self->{cgi}->param('tpl_file') || $self->{default_file} || 'default';
|
|
$selected_file = $self->{cgi}->param('tpl_name') if $self->{cgi}->param('saveas');
|
|
my %skip;
|
|
if ($self->{skip_file}) {
|
|
for (@{$self->{skip_file}}) {
|
|
$skip{$_}++;
|
|
}
|
|
}
|
|
else {
|
|
$skip{README} = $skip{'language.txt'} = $skip{'globals.txt'} = 1;
|
|
}
|
|
|
|
# Check the template directory
|
|
return if $selected_dir =~ m[[\\/\x00-\x1f]] or $selected_dir eq '..';
|
|
|
|
my $system_dir = $path . "/" . $selected_dir;
|
|
my $local_dir = $path . "/" . $selected_dir . '/local';
|
|
foreach my $dir ($system_dir, $local_dir) {
|
|
opendir (TPL, $dir) or next;
|
|
while (defined(my $file = readdir TPL)) {
|
|
next unless -f "$dir/$file" and -r _;
|
|
next if _skip_files(\%skip, $file);
|
|
|
|
$files{$file} = 1;
|
|
}
|
|
closedir TPL;
|
|
}
|
|
my $f_select_list = '<select name="tpl_file"';
|
|
$f_select_list .= qq' class="$self->{class}"' if $self->{class};
|
|
$f_select_list .= ">\n";
|
|
|
|
foreach (sort keys %files) {
|
|
my $system = -e $path . '/' . $selected_dir . '/' . $_;
|
|
my $local = -e $path . '/' . $selected_dir . '/local/' . $_;
|
|
my $changed = $system && $local ? ' *' : $local ? ' +' : '';
|
|
$f_select_list .= qq' <option value="$_"';
|
|
$f_select_list .= ' selected' if $_ eq $selected_file;
|
|
$f_select_list .= ">$_$changed</option>\n";
|
|
}
|
|
$f_select_list .= "</select>";
|
|
|
|
return $f_select_list;
|
|
}
|
|
|
|
sub template_dir_select {
|
|
# ------------------------------------------------------------------
|
|
# Returns a select list of template directories.
|
|
#
|
|
my $self = shift;
|
|
my ($dir, $file, @dirs);
|
|
my $name = $self->{select_dir};
|
|
my $selected_dir = $self->{cgi}->param($name) || $self->{default_dir} || 'default';
|
|
|
|
$dir = $self->{root};
|
|
|
|
my %skip = ('..' => 1, '.' => 1);
|
|
if ($self->{skip_dir}) {
|
|
for (@{$self->{skip_dir}}) { $skip{$_}++ }
|
|
}
|
|
else {
|
|
$skip{admin} = $skip{help} = $skip{CVS} = 1;
|
|
}
|
|
|
|
opendir (TPL, $dir) or die "unable to open directory: '$dir' ($!)";
|
|
while (defined($file = readdir TPL)) {
|
|
next if $skip{$file};
|
|
next unless (-d "$dir/$file");
|
|
push @dirs, $file;
|
|
}
|
|
closedir TPL;
|
|
|
|
my $d_select_list = qq'<select name="$name"';
|
|
$d_select_list .= qq' class="$self->{class}"' if $self->{class};
|
|
$d_select_list .= ">\n";
|
|
foreach (sort @dirs) {
|
|
$d_select_list .= qq' <option value="$_"';
|
|
$d_select_list .= ' selected' if $_ eq $selected_dir;
|
|
$d_select_list .= ">$_</option>\n";
|
|
}
|
|
$d_select_list .= "</select>";
|
|
return $d_select_list;
|
|
}
|
|
|
|
sub template_file_stats {
|
|
# ------------------------------------------------------------------
|
|
# Returns information about a file. Takes the following arguments:
|
|
# - filename
|
|
# - template set
|
|
# The following tags are returned:
|
|
# - file_path - the full path to the file, relative to the admin root directory
|
|
# - file_size - the size of the file in bytes
|
|
# - file_local - 1 or 0 - true if it is a local file
|
|
# - file_restore - 1 or 0 - true if it is a local file and a non-local file of the same name exists (The non-local can be restored)
|
|
# - file_mod_time - the date the file was last modified
|
|
#
|
|
require GT::Date;
|
|
my ($self, $file) = @_;
|
|
my $sel_tpl_dir = $self->{select_dir};
|
|
my $tpl_dir = $self->{cgi}->param($sel_tpl_dir) || $self->{default_dir} || 'default';
|
|
my $return = { file_local => 1, file_restore => 1 };
|
|
my $dir = "$self->{root}/$tpl_dir";
|
|
if (-f "$dir/local/$file" and -r _) {
|
|
$return->{file_path} = "templates/$tpl_dir/local/$file";
|
|
$return->{file_size} = -s _;
|
|
$return->{file_local} = 1;
|
|
my $mod_time = (stat _)[9];
|
|
$return->{file_restore} = (-f "$dir/$file" and -r _) ? 1 : 0;
|
|
if ($self->{date_format}) {
|
|
require GT::Date;
|
|
$return->{file_mod_time} = GT::Date::date_get($mod_time, $self->{date_format});
|
|
}
|
|
else {
|
|
$return->{file_mod_time} = localtime($mod_time);
|
|
}
|
|
}
|
|
else {
|
|
$return->{file_path} = "templates/$tpl_dir/$file";
|
|
$return->{file_size} = -s "$dir/$file";
|
|
$return->{file_local} = 0;
|
|
$return->{file_restore} = 0;
|
|
my $mod_time = (stat _)[9];
|
|
if ($self->{date_format}) {
|
|
require GT::Date;
|
|
$return->{file_mod_time} = GT::Date::date_get($mod_time, $self->{date_format});
|
|
}
|
|
else {
|
|
$return->{file_mod_time} = localtime($mod_time);
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
sub move {
|
|
# -------------------------------------------------------------------
|
|
# Uses File::Copy to move a file.
|
|
#
|
|
my $self = shift;
|
|
my ($from, $to) = @_;
|
|
eval { require File::Copy; };
|
|
if ($@) {
|
|
return $self->error('FILECOPY', $@);
|
|
}
|
|
File::Copy::mv($from, $to) or return $self->error('CANTMOVE', $from, $to, "$!");
|
|
}
|
|
|
|
sub copy {
|
|
# -------------------------------------------------------------------
|
|
# Uses File::Copy to move a file.
|
|
#
|
|
my $self = shift;
|
|
my ($from, $to) = @_;
|
|
eval { require File::Copy; };
|
|
if ($@) {
|
|
return $self->error('FILECOPY', $@);
|
|
}
|
|
File::Copy::cp($from, $to) or return $self->error('CANTCOPY', $from, $to, "$!");
|
|
}
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
GT::Template::Editor - This module provides an easy way to edit templates.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
Should be called like:
|
|
|
|
require GT::Template::Editor;
|
|
my $editor = new GT::Template::Editor (
|
|
root => $CFG->{admin_root_path} . '/templates',
|
|
default_dir => $CFG->{build_default_tpl},
|
|
backup => 1,
|
|
cgi => $IN
|
|
);
|
|
return $editor->process;
|
|
|
|
and it returns a hsah ref of variables used for displaying a template editor page.
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
|
http://www.gossamer-threads.com/
|
|
|
|
=head1 VERSION
|
|
|
|
Revision: $Id: Editor.pm,v 2.19 2004/10/19 23:34:44 jagerman Exp $
|
|
|
|
=cut
|
|
|