#!/usr/bin/perl -U
## Salonify by Adam Rosi-Kessel Copyright 2002-2005
## Permission granted to modify and redistribute under the terms of the GPL v2.0 or later
# You should change this to reflect the name and location of your config file
$::config_file = "/etc/salonify";
use strict;
sub SafeRequire {
my $mod = shift @_;
unless (eval "require $mod") {
die "Couldn't load required module $mod: $@\nPlease install this module and try again, or reconfigure salonify to disable this feature.\n";
}
}
$::program_title = "salonify";
$::program_version = "0.82 (2005/09/15)";
$SIG{__DIE__} = \&FatalError;
use CGI qw(param :newstyle_urls cookie :standard :html4 start_ul -nosticky -no_xhtml);
use POSIX;
use File::Basename;
use File::Glob qw(:globally :nocase);
use File::Copy;
use vars qw/%url_prefs %param/;
use vars qw/$salonify_default_thumbnails_columns $salonify_default_thumbnails_rows $salonify_default_show_thumbnail_captions $salonify_default_index_columns/;
use vars qw/$salonify_script_url $salonify_files_url $salonify_cascading_style_sheet_url $salonify_image_filename_extensions $salonify_top_level_url $salonify_files_url $salonify_use_rcs $salonify_collapsible_lists $salonify_start_collapsed $salonify_allow_user_to_edit_descriptions/;
use vars qw/$salonify_up_image $salonify_up_image_alt $salonify_down_image $salonify_down_image_alt $salonify_right_image $salonify_right_image_alt/;
use vars qw/$salonify_folders_file $salonify_names_filename $salonify_top_of_page_file $salonify_top_level_directory $salonify_alternate_index_file/;
use vars qw/$salonify_allow_user_to_change_layout $salonify_allow_user_to_change_captions/;
use vars qw/$salonify_verbose_url/;
use vars qw/$salonify_default_DirectoryNavigationTop $salonify_default_DirectoryNavigationBottom $salonify_default_SlideShowNavigationTop $salonify_default_SlideShowNavigationBottom $salonify_default_SlideShowButtonsTop $salonify_default_SlideShowButtonsBottom $salonify_default_SlideShowCaptionTop $salonify_default_SlideShowCaptionBottom $salonify_default_SlideShowSizeTop $salonify_default_SlideShowSizeBottom $salonify_default_SlideShowRotateTop $salonify_default_SlideShowRotateBottom $salonify_default_ShowPrefs $salonify_default_ShowCreditsTop $salonify_default_ShowCreditsBottom/;
use vars qw/$salonify_show_error_messages/;
use vars qw/$form_method/;
use vars qw/$url_folder/;
use vars qw/$random_string/; # added onto URL if image is rotated--this is a bit of a hack, but I don't know any other way to do it.
use vars qw/$salonify_generate_static_archive_file $salonify_generate_dynamic_archive_file/;
my $query = new CGI;
my $rcs;
%param = $query->Vars;
foreach (keys %param) {
if ( (length $_) > 1 and not /^w\d/ ) {
while (s/^(.)//) {
$param{$1} = 1;
}
}
}
&DefaultSettings;
if ($salonify_show_error_messages) {
SafeRequire "CGI::Carp";
CGI::Carp->import(qw(fatalsToBrowser));
}
&ProcessConfig;
if ($salonify_use_rcs) {
SafeRequire "Rcs";
Rcs->import(qw(nonFatal));
if (-x '/usr/bin/co') {
Rcs->bindir('/usr/bin');
} elsif (-x '/usr/local/bin/co') {
Rcs->bindir('/usr/local/bin');
} elsif (my $rcs_path = `which co`) {
$rcs_path =~ s{^(.*)/.*$}{$1};
Rcs->bindir($rcs_path);
} else {
die 'RCS not found in path. Set $salonify_use_rcs to 0 in the configuration, or install RCS.\n';
}
}
my $packed_cookie;
my $url_keep_cookie = $param{'q'};
$url_keep_cookie and $packed_cookie = cookie ( -name => 'salonify', -value => ShowPrefsNoForm('smnodpbz'), -expires => '+1y');
print ($packed_cookie ?
header( -cookie => $packed_cookie)
: header()
);
$form_method = ($salonify_verbose_url ? "POST" : "GET");
if ($salonify_allow_user_to_change_layout) {
%url_prefs = &ProcessPrefs(keys %param);
%url_prefs = &ProcessPrefs(CookiePrefs(cookie('salonify'))) unless (scalar (keys %url_prefs));
%url_prefs = &ProcessPrefs(DefaultPrefs()) unless (scalar (keys %url_prefs));
} else {
%url_prefs = &ProcessPrefs(DefaultPrefs());
}
if ($param{'s'}) {
$random_string = "?s=" . &CleanParam('s');
} else {
$random_string = "";
}
my $url_thumbs = &CleanParam('u') or &CleanParam('t');
my $url_index = &CleanParam('i');
my $url_download_album = &CleanParam('L');
my $url_edit_descriptions = &CleanParam('l');
$url_index = 1 if $url_edit_descriptions;
my $url_current = &CleanParam('x') || 0;
my $url_cols = &CleanParam('m') || 0;
my $url_rows = &CleanParam('n') || 0;
my $url_start_slideshow = &CleanParam('v');
$url_current = 0 if $url_start_slideshow and not $url_current;
$url_cols > 0 and $url_cols < 15 and $salonify_default_thumbnails_columns = $url_cols;
$url_rows > 0 and $url_rows < 15 and $salonify_default_thumbnails_rows = $url_rows;
# I'm not really happy with this logic, it's complicated right now
# What we're trying to do is figure out where we're coming to the thumbnails from
# If it's from the slideshow, and we were deep in, we want to back to the page of thumbnails
# that corresponds to where we were in the slideshow
# But if we're coming from another directory entirely, we want to go to the first page
# Probably needs more testing
my $url_current_thumb = defined &CleanParam('t') ? &CleanParam('t') : (&CleanParam('u') ne 2 ? ThumbNailPage($url_current) : 0);
$url_folder = &CleanParam('y') || "";
my $url_size = &CleanParam('z') || "";
my $url_title = $param{'k'} || "";
my $url_rotate;
my $url_which_rotate = $param{'r'} || '';
$salonify_default_show_thumbnail_captions = defined $param{'o'};
($url_which_rotate =~ /Clockwise/) and $url_rotate = 1 or
($url_which_rotate =~ /Counterclockwise/) and $url_rotate = -1 or
$url_rotate = 0;
my $size = ".me";
$url_size eq "3" and $size = ".li" or
$url_size eq "2" and $size = ".me" or
$url_size eq "1" and $size = "";
################################################################################
# Show fatal errors to the web user to make debugging easier
# (can be disabled by setting $salonify_show_error_messages = 0)
sub FatalError {
print start_html() .
h2('Salonify Fatal Error!') .
p(join('
',@_)) .
p('Contact the site administrator to report this problem.') .
end_html() if $salonify_show_error_messages;
die @_;
}
# These are all the default settings if nothing is specified in the config file
sub DefaultSettings {
$salonify_script_url = "/cgi-bin/salonify";
$salonify_folders_file = "/photo/folders.txt";
$salonify_names_filename = "names.txt";
$salonify_top_of_page_file = "/photo/index.txt";
$salonify_top_level_directory = "/var/www/photo";
$salonify_top_level_url = "/photo";
$salonify_cascading_style_sheet_url = "style.css";
$salonify_image_filename_extensions = "jpg gif";
$salonify_collapsible_lists = 1;
$salonify_start_collapsed = 1;
$salonify_files_url = "/photo";
$salonify_allow_user_to_edit_descriptions = 1;
$salonify_use_rcs = 1;
$salonify_verbose_url = 0;
$salonify_allow_user_to_change_captions = 1;
$salonify_allow_user_to_change_layout = 1;
$salonify_default_thumbnails_columns = 4;
$salonify_default_thumbnails_rows = 4;
$salonify_default_show_thumbnail_captions = 0;
$salonify_default_index_columns = 2;
$salonify_default_DirectoryNavigationTop = 0;
$salonify_default_DirectoryNavigationBottom = 1;
$salonify_default_SlideShowNavigationTop = 0;
$salonify_default_SlideShowNavigationBottom = 1;
$salonify_default_SlideShowButtonsTop = 1;
$salonify_default_SlideShowButtonsBottom = 0;
$salonify_default_SlideShowCaptionTop = 0;
$salonify_default_SlideShowCaptionBottom = 1;
$salonify_default_SlideShowSizeTop = 0;
$salonify_default_SlideShowSizeBottom = 1;
$salonify_default_SlideShowRotateTop = 0;
$salonify_default_SlideShowRotateBottom = 1;
$salonify_default_ShowPrefs = 1;
$salonify_default_ShowCreditsTop = 0;
$salonify_default_ShowCreditsBottom = 1;
$salonify_show_error_messages = 1;
$salonify_generate_static_archive_file = 0;
$salonify_generate_dynamic_archive_file = 1;
}
sub ProcessConfig {
# try to load config file and give user helpful debugging output if we can't
die "Could not read config file $::config_file--check path." unless ( -e $::config_file);
die "Could not read config file $::config_file--check permissions." unless ( -r $::config_file);
die "Could not find or load config file--perhaps you deleted a semicolon at the end of a line or the number 1 is missing from the end of the coonfig file.\n" unless (do $::config_file);
# add trailing slashes unless there already is one on the path files
foreach ($salonify_top_level_directory, $salonify_top_level_url, $salonify_files_url) {
$_ .= '/' unless m{/$};
}
# set up the outline arrow images if they are not already defined
$salonify_up_image = $salonify_files_url . "up.png" unless defined $salonify_up_image;
$salonify_up_image_alt = "+" unless defined $salonify_up_image_alt;
$salonify_down_image = $salonify_files_url . "down.png" unless defined $salonify_down_image;
$salonify_down_image_alt = "-" unless defined $salonify_down_image_alt;
$salonify_right_image = $salonify_files_url . "right.png" unless defined $salonify_right_image;
$salonify_right_image_alt = ">" unless defined $salonify_right_image_alt;
# set up the stylesheet URL unless it is fully defined in the config file
unless ($salonify_cascading_style_sheet_url =~ m{/}) {
$salonify_cascading_style_sheet_url = $salonify_files_url . $salonify_cascading_style_sheet_url;
}
# the list of accepted filename extensions is turned into a regexp for future matching
for ($salonify_image_filename_extensions) {
s/^ *| *$//g;
s/ /\$\|\\./g;
s/$/\$/g;
s/^/\\./g;
}
}
sub SlideShowSetupJava {
my $index = shift;
my $path = shift;
my $titles = shift;
my %titles = %$titles;
$index > 0 or $index = '0';
my $slideshow= <
";
return $slideshow;
}
sub SlideShowImages {
my $current = shift or 0;
my $path = shift || "";
my $image = $_[$current] || "";
my $imagename = StripSize($image);
return
p(
{-align=>'center'},
img{
-src => $path . $image . $random_string,
-name => 'photo',
-class => 'photo',
-alt => $imagename
}
);
}
sub SlideShowCaption {
return
start_form(-action=>$salonify_script_url,-method=>'GET',-name=>'caption'),
p({-align=>'center'},
textfield(-name=>'k',
-default=>shift,
-size=>60,
-maxlength=>60) .
ShowPrefsForm('sxyzmnopdb') .
submit(-value=>'Change')),
end_form;
}
# StripSize: Removes the leading /.li, /.me, /.tn or just plain /
# This is needed because files are hashed by 'full sized' filename, not the filename for reduced versions
sub StripSize {
$_ = shift;
s{/\.li|/\.me|/\.tn|/}{};
return $_;
}
# SlideShowButtonsJava: Returns the JavaScript used to paint the buttons to move around in the slideshow
# This all has to be done with document.write so it doesn't show up when a non-JavaScript browser is looking
# at the page.
sub SlideShowButtonsJava {
my $thumbs_url = $salonify_script_url . "?u" . ShowPrefsNoForm('sxyzmnopdb');
my $index_url = $salonify_script_url . "?i" . ShowPrefsNoForm('syzmnopdb');
my $perma_url = $salonify_script_url . "?" . ShowPrefsNoForm('syzmnopdb');
return <
EOF
}
# SlideShowButtonsNoJava: Returns the links for page for non-JavaScript Browser
sub SlideShowButtonsNoJava {
my $current = shift || 0;
my $path = shift || "";
my @images = @_;
my $big_photo = $images[$current] || "";
$big_photo =~ s/^\.li|\.me//g;
$big_photo = $path . $big_photo;
my $number_of_photos = scalar @images;
my $first = "$salonify_script_url?x=0" . ShowPrefsNoForm('syzmnopdb');
my $previous = "$salonify_script_url?x=" . ( (($current-1) >= 0) ? ($current - 1) : ($number_of_photos - 1)) . ShowPrefsNoForm('syzmnopdb');
my $next = "$salonify_script_url?x=" . ( (($current+1) < $number_of_photos) ? ($current + 1) : ("0") ) . ShowPrefsNoForm('syzmnopdb');
my $last = "$salonify_script_url?x=" . ($number_of_photos-1) . ShowPrefsNoForm('syzmnopdb');
my $thumbs = "$salonify_script_url?u" . ShowPrefsNoForm('sxyzmnopdb');
my $index = "$salonify_script_url?i" . ShowPrefsNoForm('szmnopdb');
my $printsave = $big_photo;
""
}
# ShowPrefsNoForm: This ensures that settings and location are preserved from one click to the next
# Give it a bunch of CGI parameters, and it returns a string to be appended to the URL
# e.g., ShowPrefsNoForm('sabc') returns ;a=1;b=2;c=3 (if a, b, and c, equal 1, 2, and 3 respectively)
sub ShowPrefsNoForm {
my $params = shift;
my $string = "";
while ($params =~ s/^(.)//) {
my $val = $1;
if ($1 eq 'p') {
$string .= ';';
foreach my $x ('A','a','B','b','C','c','D','d','E','e','F','f','H','h','J','j') {
$string .= $x if $param{$x};
}
} else {
if ($val eq 'x' and not $param{$val}) {
$string .= ";x=0" if $val eq 'x' and not $param{$val};
} else {
$string .= ";" . $val if defined $param{$val};
$string .= "=" . &CleanParam($val) if $param{$val};
}
}
}
return $string;
}
# ShowPrefsForm: Save as above, but this returns hidden fields for submit button controls
# Inserts newlines between fields for better readability in the resulting HTML
sub ShowPrefsForm {
my $params = shift;
my $string = "";
while ($params =~ s/^(.)//) {
my $val = $1;
if ($val eq 'p') {
my $hidden_value .= 'p';
foreach my $x ('A','a','B','b','C','c','D','d','E','e','F','f','H','h','j','J') {
$hidden_value .= $x if $param{$x};
}
$string .= hidden( -name => $hidden_value );
} else {
$string .= hidden(
-name => $val,
-value => &CleanParam($val)
) if defined $param{$val};
}
}
return $string;
}
sub SlideShowNavigation {
my $current = (shift || 0) + 1;
my $path = shift || "";
my $titles = shift || "";
my %titles = %$titles;
my $max_pages = scalar @_ - 1;
my @images = @_;
my (@values, %labels);
my $counter = 0;
while ($counter <= $max_pages) {
push @values, $counter;
$labels{$counter} = ($counter+1) . " <" . $titles{StripSize($images[$counter])} . ">";
$counter++;
}
# These buttons have been put in a centered table to compensate for a Netscape 4.7 bug, which apparently inserts
# a linebreak before any popup menu. Even if a break does appear here, it won't look as messed up. Hopefully
# this will still look fine in other browsers as well. This same problem occurs with all of the various
# buttons which are supposed to be centered, so you'll see this table code repeated.
return start_form(-action=>$salonify_script_url,-method=>'GET',-name=>'nav'),
table({ -border => 0, -width => "70%", -align=> 'center' },
Tr({-align=>"CENTER",-valign=>"TOP"} ,
td(
({-align=>'CENTER'}, "Image " .
popup_menu(-name=>'x',-values=>\@values,-labels=>\%labels,-onchange=>'updateIndex(document.nav.x.value);')
. " of " . ($max_pages+1) . ' ' .
ShowPrefsForm('syzmnopdb') .
submit(-value=>'Go'))
))) .
end_form;
}
# RotateImage: Uses jpegtran to rotate the image; puts the result in a temporary file and then
# copies it over the original. This function may be somewhat dangerous; perhaps we want to make
# some backup of the original image? Although it seems fairly innocuous at this point.
# For consistency, it will rotate the small, medium, large, and thumbnail version of the image
sub RotateImage {
my $direction = shift;
my $current = shift || 0;
my $path = shift || "";
my @images = @_;
my $x = $path . '/' . StripSize($images[$current]);
my $tmp = tmpnam();
$direction == 1 and $direction = 90 or
$direction = 270;
if (system("which jpegtran > /dev/null")) {
print p("Sorry, JPEG rotation is not installed; you need libjpeg-progs.");
} else {
$tmp = tmpnam();
$x =~ m/^(.*)$/;
my $untaint = $1;
system("jpegtran -rotate $direction -outfile $tmp $x");
( -e $tmp and move($tmp,$untaint));
$x =~ s{(.*)/(.*)}
{$1/.li$2};
$x =~ m/^(.*)$/;
$untaint = $1;
system("jpegtran -rotate $direction -outfile $tmp $x");
( -e $tmp and move($tmp,$untaint));
$x =~ s{(.*)/.li(.*)}
{$1/.me$2};
$x =~ m/^(.*)$/;
$untaint = $1;
system("jpegtran -rotate $direction -outfile $tmp $x");
( -e $tmp and move($tmp,$untaint));
$x =~ s{(.*)/.me(.*)}
{$1/.tn$2};
$x =~ m/^(.*)$/;
$untaint = $1;
system("jpegtran -rotate $direction -outfile $tmp $x");
( -e $tmp and move($tmp,$untaint));
}
$random_string = "?s=" . &CleanParam('s');
1;
}
sub ThumbNailButtons {
my $current = shift || 0;
my $max_pages = floor ( (scalar @_ -1) / ($salonify_default_thumbnails_columns * $salonify_default_thumbnails_rows) );
my @nav_cols;
push @nav_cols,
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?u;t=0" . ShowPrefsNoForm('syzmnopdb') } , "First" )
),
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?u;t=" . ($current > 0 ? $current - 1 : 0) . ShowPrefsNoForm('syzmnopdb') }, "Previous" )
),
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?u;t=" . ($current < $max_pages ? $current + 1 : $max_pages) . ShowPrefsNoForm('syzmnopdb') }, "Next" )
),
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?u;t=$max_pages" . ShowPrefsNoForm('syzmnopdb') }, "Last" )
)
if $max_pages; # if there is more than one page, put up page navigation buttons
push @nav_cols,
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?i" . ShowPrefsNoForm('szmnopdb') }, "Index" )
),
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?v;x=0" . ShowPrefsNoForm('syzmnopdb') }, "Slideshow" )
); # always include buttons for index and slideshow
push @nav_cols,
td( { -class => 'navigation' },
a ( { href=> $salonify_script_url . "?L" . ShowPrefsNoForm('styzmnopdb') }, "Download Album" )
)
if $salonify_generate_dynamic_archive_file or $salonify_generate_static_archive_file;
table(
{ -class => 'navigation' },
Tr( { -class => 'navigation' }, @nav_cols )
);
}
sub ThumbNailPage {
my $current_image = shift or 0;
my $counter = 0;
while ($current_image > $salonify_default_thumbnails_columns * $salonify_default_thumbnails_rows - 1) { $current_image -= $salonify_default_thumbnails_columns * $salonify_default_thumbnails_rows; $counter++;}
return $counter;
}
sub ThumbNailNavigation {
my $current = (shift || 0) + 1;
my $max_pages = floor ( (scalar @_ -1) / ($salonify_default_thumbnails_columns * $salonify_default_thumbnails_rows) );
my (@values, %labels);
my $counter = 0;
while ($counter <= $max_pages) {
push @values, $counter;
$labels{$counter} = $counter+1;
$counter++;
}
$max_pages and
return start_form(-action=>$salonify_script_url,-method=>'GET'),
table({ -border => 0, -width => "35%", -align=> 'center' },
Tr({-align=>"CENTER",-valign=>"TOP"} ,
td(
({-align=>'CENTER'}, "Page " .
popup_menu(-name=>'t',-values=>\@values,-default=>$current-1,-labels=>\%labels,-onchange=>'submit()')
. " of " . ($max_pages+1) .
hidden(-name=>'u',-values=>1) . " " .
ShowPrefsForm('syzmnopdb') .
submit(-value=>'Go'))))),
end_form
or "";
}
sub ThumbNailImages {
my $current = shift || 0;
my $path = shift || "";
my $titles = shift;
my %titles = %$titles;
my @thumbnails = @_;
my (@rows, @cols);
my $row_counter = 0;
my $col_counter = 0;
my $title_width = floor (120/$salonify_default_thumbnails_columns);
$title_width > 60 and $title_width=60;
my $counter = $current * $salonify_default_thumbnails_columns * $salonify_default_thumbnails_rows;
$counter > scalar @thumbnails and $counter = scalar @thumbnails;
while ($row_counter++ < $salonify_default_thumbnails_rows and $thumbnails[$counter]) {
while ($col_counter++ < $salonify_default_thumbnails_columns and $thumbnails[$counter]) {
push (@cols, a(
{ -href => $salonify_script_url . "?x=$counter" . ShowPrefsNoForm('syzmnopdb') },
img{
-src => $path . $thumbnails[$counter] . $random_string,
-alt => $titles{StripSize($thumbnails[$counter])},
-class => 'thumbnail',
-name => 'thumbnail'
}
)
.
( $salonify_default_show_thumbnail_captions ?
br()
.
textfield(
-name => "w$counter",
-default => $titles{StripSize($thumbnails[$counter])},
-size => $title_width,
-maxlength => 60
)
:
''
)
);
$counter++;
}
while ($col_counter++ <= $salonify_default_thumbnails_columns) {
push (@cols, p(" ")) if $row_counter > 1;
}
push (@rows, td({-class=>'thumbnail'},\@cols));
@cols = ();
$col_counter = 0;
}
start_form(-action=>$salonify_script_url, -method=>$form_method,-name=>'thumbname')
.
ShowPrefsForm('syzmnoptub')
.
table(
{ -class => 'thumbnail' },
Tr(
{ -class => 'thumbnail' },
\@rows
)
.
( $salonify_default_show_thumbnail_captions ?
Tr(
{ -class => 'thumbnail' },
td(
{ -colspan => $salonify_default_thumbnails_columns,
-class => 'thumbnail'
},
submit(-value=>"Change Titles")
)
)
:
''
)
)
.
end_form;
}
sub ThumbNailPreferences {
return start_form(-action=>$salonify_script_url, -method=>'GET',-name=>'u'),
(
$salonify_allow_user_to_change_layout
?
table(
{ -border => 2, -align=>"center", -cellspacing=>0, -frame=>'box', -cellpadding=>5},
Tr( {align=>"CENTER",-valign=>"TOP"},
td({-colspan=>"2"},
checkbox( -name => 'o',
-label => 'Show Titles',
-checked => $salonify_default_show_thumbnail_captions,
-value => 1
))),
Tr( {-align=>"CENTER",-valign=>"TOP"} ,
td({-width=>"50%"}, 'Columns:' .
textfield(-name=>'m',
-default=>$salonify_default_thumbnails_columns,
-size=>2,
-maxlength=>2)
),
td({-width=>"50%"}, 'Rows:' .
textfield(-name=>'n',
-default=>$salonify_default_thumbnails_rows,
-size=>2,
-maxlength=>2)
)),
Tr( {align=>"CENTER",-valign=>"TOP"},
td({-colspan=>"2"},
ShowPrefsForm('suyzpd') .
checkbox(
-name => 'q',
-label => 'Save these settings in a cookie on your computer',
-value => 1
)
)
)
.
Tr( {align=>"CENTER",-valign=>"TOP"},
td({-colspan=>"2"},
submit(-value=>'Change'))
))
:
""
),
end_form;
}
sub DirectoryIndex {
my $index = "";
my $counter = 0;
my ($id, $path, $images, $desc, $children, $level, $lastlevel, $title, $trimpath);
my $first_item = 1;
my $list_id = 1;
my %folders;
my $lines = 0;
my $lastpath = "";
open IN, $salonify_folders_file || return p("Sorry, no index could be found--check permissions!");
while () {
/^#/ and next; # skip comment lines
($id, $path, $images, $desc) = /^(.*)\t(.*)\t(.*)\t(.*)/; # parse folder line
$desc =~ s/&/\&/g; # sanitize HTML
$folders{$path} = { id => $id, images => $images, desc => $desc, children => 0};
$trimpath = $path;
$trimpath =~ s{^(.*)/.*$}{$1}; # trim last part of path to see if this is a child of previous
$folders{$trimpath}{children} = 1 if ($trimpath eq $lastpath and $lastpath ne "");
$lastpath = $path;
$lines++;
}
close IN;
$index = ($url_edit_descriptions ?
start_form(-action=>$salonify_script_url,-method=>'POST',-name=>'description')
:""
)
.
($salonify_allow_user_to_edit_descriptions ?
($url_edit_descriptions ?
"
\n";
$lastlevel = 0;
my $lastid = 0;
my $total_columns = 1;
my @ids_to_collapse;
foreach (sort keys %folders) {
($id, $path, $images, $desc, $children) = ($folders{$_}{id}, $_, $folders{$_}{images}, $folders{$_}{desc}, $folders{$_}{children});
$level = ($path =~ tr/\//\//) - 1; # Count /'s for subdirectory nesting
# if we are less nested than the last cycle, close list the necessary number of levels
if ($level < $lastlevel) {
$index .= "\n" . ((end_ul() . "\n") x ($lastlevel-$level));
$first_item = 1;
}
# check if we're at the bottom of the column and unindented; if so start a new column
if (($counter++ > ($lines / $salonify_default_index_columns)) and not $level) {
$counter = 0;
$total_columns++;
$index .= end_ul() . "
\n
\n";
$first_item = 1;
}
if ($level > $lastlevel) { # if we are more deeply nested,
$index .= "\n
\n" .
# This next block creates (with JavaScript) the "open and closed" buttons if
# collapsible lists are enabled.
($children && $salonify_collapsible_lists ? <
document.writeln('');
HERE
: ($salonify_collapsible_lists ?
<
document.writeln('');
HERE
:
""
)
);
if ($url_edit_descriptions) {
$index .=
ShowPrefsForm('szmnopdb') .
textfield(
-name=>"l$id",
-default=>$desc,
-size=>30,
-maxlength=>60
);
} else {
$index .= ($images ? "$desc" :
$salonify_collapsible_lists ? "$desc"
:
"$desc"
);
}
$lastid = $id if $children;
$first_item = 0;
$lastlevel = $level;
}
# if we're still indented, unindent back to 0
($level > 0) and $index .= ((end_ul() . "
\n") x ($level));
$index .= end_ul() .
"
\n
\n
\n" .
"
HERE
;
}
$index .= (submit(-value=>'Change Descriptions') . end_form) if $url_edit_descriptions;
return $index;
}
sub DownloadAlbum {
my ($image_url, $local_path) = @_;
my $zip_base = MakeZipBase($local_path);
my $full_url = $image_url . $zip_base . "full.zip";
my $full_file = $local_path . $zip_base . "full.zip";
my $medium_url = $image_url . $zip_base . "medium.zip";
my $medium_file = $local_path . $zip_base . "medium.zip";
my $small_url = $image_url . $zip_base . "small.zip";
my $small_file = $local_path . $zip_base . "small.zip";
my $full_size = 0;
my $medium_size = 0;
my $small_size = 0;
my $full_exists = ( -r $full_file);
my $medium_exists = ( -r $medium_file);
my $small_exists = ( -r $small_file);
my $x;
$x = h2("Download entire album");
if ($full_exists) {
$full_size = (stat($full_file))[7];
} else {
while (<$local_path/*>) {
next unless (-f and /$salonify_image_filename_extensions/i);
$full_size += (stat($_))[7];
}
}
if ($full_size > 1000000) {
$full_size = sprintf("%.2fM",$full_size/1000000);
} else {
$full_size = sprintf("%.2fk",$full_size/1000);
}
if ($medium_exists) {
$medium_size = (stat($medium_file))[7];
} else {
while (<$local_path/.me*>) {
next unless (-f and /$salonify_image_filename_extensions/i);
$medium_size += (stat($_))[7];
}
}
if ($medium_size > 1000000) {
$medium_size = sprintf("%.2fM",$medium_size/1000000);
} else {
$medium_size = sprintf("%.2fk",$medium_size/1000);
}
if ($small_exists) {
$small_size = (stat($small_file))[7];
} else {
while (<$local_path/.li*>) {
next unless (-f and /$salonify_image_filename_extensions/i);
$small_size += (stat($_))[7];
}
}
if ($small_size > 1000000) {
$small_size = sprintf("%.2fM",$small_size/1000000);
} else {
$small_size = sprintf("%.2fk",$small_size/1000);
}
$x .=
p (
{ -align => 'left' },
( $full_exists ?
a ( { href => ( $full_url ) },
"Full-sized Images (${full_size})" )
:
a ( { href => ( $salonify_script_url . "?L=2" . ShowPrefsNoForm('styzmnopdb') ),
target => "download" },
"Full-sized Images (${full_size})" )
)
) .
p (
{ -align => 'left' },
( $medium_exists ?
a ( { href => ( $medium_url ) },
"Medium-sized Images (${medium_size})" )
:
a ( { href => ( $salonify_script_url . "?L=3" . ShowPrefsNoForm('styzmnopdb') ),
target => "download" },
"Medium-sized Images (${medium_size})" )
)
) .
p (
{ -align => 'left' },
( $small_exists ?
a ( { href => ( $small_url ) },
"Small-sized Images (${small_size})" )
:
a ( { href => ( $salonify_script_url . "?L=4" . ShowPrefsNoForm('styzmnopdb') ),
target => "download" },
"Small-sized Images (${small_size})" )
)
)
;
$x .= a ( { href=> $salonify_script_url . "?u" . ShowPrefsNoForm('styzmnopdb') }, "Return to Thumbntails" );
return $x;
}
sub MakeZipBase {
$_ = shift @_;
s{/*$}{};
s{^/*}{};
s{^.*/}{};
return "/${_}_";
}
sub GenerateArchive {
my ($path, $url, $size) = @_;
$path =~ s{//*}{/}g;
my $dirname = $path;
$dirname =~ s{/*$}{}g;
$dirname =~ s{^.*/}{}g;
my $zip_base = MakeZipBase($path);
my $zip;
my $target;
print h2("Generating archive file");
$target = $zip_base . "full.zip" if $size == 2;
$target = $zip_base . "medium.zip" if $size == 3;
$target = $zip_base . "small.zip" if $size == 4;
SafeRequire "Archive::Zip";
Archive::Zip->import(qw( :ERROR_CODES :CONSTANTS ));
if ($size == 2 &! -e $path . $target) {
$zip = Archive::Zip->new();
print "
Generating archive file. This may take a while... ";
while (<$path/*>) {
next unless (-f and /$salonify_image_filename_extensions/i);
my $file = basename($_);
$zip->addFile( $_ , $dirname . "/" . $file);
print " . ";
}
foreach my $member ($zip->members()) {
$member->desiredCompressionMethod( 0 );
}
die "Could not write ZIP file ${path}${target}. Check permissions.\n" if ($zip->writeToFileNamed( $path . $target ));
print "done.
Generating archive file. This may take a while... ";
while (<$path/.*>) {
next unless (-f and /$salonify_image_filename_extensions/i);
my $file = basename($_);
next unless $file =~ m/^.me/;
$file =~ s/^.me//g;
$zip->addFile( $_ , $dirname . "/" . $file);
print " . ";
}
foreach my $member ($zip->members()) {
$member->desiredCompressionMethod( 0 );
}
die "Could not write ZIP file ${path}${target}. Check permissions.\n" if ($zip->writeToFileNamed( $path . $target ));
print "done.
Generating archive file. This may take a while... ";
while (<$path/.*>) {
next unless (-f and /$salonify_image_filename_extensions/i);
my $file = basename($_);
next unless $file =~ m/^.li/;
$file =~ s/^.li//g;
$zip->addFile( $_ , $dirname . "/" . $file);
print " . ";
}
foreach my $member ($zip->members()) {
$member->desiredCompressionMethod( 0 );
}
die "Could not write ZIP file ${path}${target}. Check permissions.\n" if ($zip->writeToFileNamed( $path . $target ));
print "done.
";
}
print p (
a (
{ href=> $url . $target },
"Click here if you are not automatically redirected."
)
.
"
"
);
}
sub DirectoryNavigation {
my %labels;
my @values;
my ($id, $path, $images, $desc, $level, $lastlevel, $title);
open IN, $salonify_folders_file or return p("Sorry, no index could be found!");
while () {
($id, $path, $images, $desc) = /^(.*)\t(.*)\t(.*)\t(.*)/ or next;
$level = ($path =~ tr/\//\//); # Count /'s for subdirectory nesting
$images and push @values, $id or push @values, $url_folder . "-$id";
$images and $labels{$id} = "- " x $level . ' <' . $desc . '>' or
$labels{$url_folder . "-" . $id} = "- " x $level . ' ' . $desc;
# This is basically a kludge, but I couldn't figure out any other way to do it without relying on JavaScript.
# Essentially, if you select an item from the menu that doesn't actually have any images in it (i.e., it's a
# "parent directory"), the value submitted is the current page followed by a dash followed by the empty
# page number. Later on, the script strips out anything after a dash.
# Partially this is because CGI.pm requires a hash for the labels, so you can't have two different labels
# point to the same ID. If I had wanted to hard code the popup-menu, that probably would have worked fine,
# but I'm trying to keep to the CGI.pm functions.
}
close IN;
return start_form(-action=>$salonify_script_url, -method=>'GET',-name=>'i'),
table({ -border => 0, -width => "100%", -align=> 'center' },
Tr({-align=>"CENTER",-valign=>"TOP"} ,
td(
({-align=>'CENTER'},
"Photo Album: " .
popup_menu(-name=>'y',
-values=>\@values,
-labels=>\%labels,
-default=>$url_folder,
-onchange=>'submit()') . ' ' .
hidden(-name=>'u',-values=>2) . " " . # This is 2 rather than 1 so the script knows to use the x
ShowPrefsForm('sxzmnopdb') . # value if we end up back on the same page
submit(-value=>'Go'))))),
end_form;
}
sub DirectoryRead {
my $target = shift;
my %id;
my ($counter, $path, $contents, $description);
die "Folders file $salonify_folders_file does not exist.\n" unless (-e $salonify_folders_file);
open IN, "$salonify_folders_file" or die "Tree file $salonify_folders_file exists but I could not open it--check file permissions!\n";
while () {
($counter, $path, $contents, $description) = m{^(.*)\t/*(.*)\t(.*)\t(.*)$}; # eliminate initial slashes if present in path
$id{$counter} = $path if $contents;
}
close IN;
return $id{$target};
}
sub SlideShowSize {
return start_form(-action=>$salonify_script_url, -method=>'GET',-name=>'size'),
table({ -border => 0, -width => "45%", -align=> 'center' },
Tr({-align=>"CENTER",-valign=>"TOP"} ,
td(
({-align=>'CENTER'},
"Image Size: " .
popup_menu(-name=>'z',
-values=>[qw/1 2 3/],
-labels=>{"1"=>"Full","2"=>"Medium","3"=>"Small"},
-default=>"2",
-onchange=>'submit()') . ' ' .
ShowPrefsForm('sxymnopdb') .
submit(-value=>'Go'))))),
end_form;
}
# SlideShowRotate: returns the buttons for rotating the current image
# This one is 'post' rather than 'get' so the rotation action doesn't get reloaded or bookmarked
sub SlideShowRotate {
return start_form(-action=>$salonify_script_url,-method=>$form_method,-name=>'rotate'),
table({ -border => 0, -width => "65%", -align=> 'center' },
Tr({-align=>"CENTER",-valign=>"TOP"} ,
td(
({-align=>'CENTER'},
ShowPrefsForm('xyzmnopdb') .
hidden(-name=>'s', -value=> $param{'s'}+1, -override=> 1) .
submit(-name=>'r', -value=>'Rotate Clockwise') .
submit(-name=>'r', -value=>'Rotate Counterclockwise')),
))),
end_form;
}
sub ReadTitles {
my $filename = shift || "";
my @images = @_;
my ($name, $description);
my %hash;
open IN, $filename || return %hash;
while () {
($name, $description) = m/^(.*)\t(.*)$/;
$hash{$name} = $description;
}
close IN;
foreach (@images) {
s/\/.tn|\/.li|\/.me|\///;
defined $hash{$_} or $hash{$_} = "Give $_ a name!";
}
return %hash;
}
sub RcsOpen {
return unless $salonify_use_rcs;
$< = $>;
$( = $);
my $filename = shift;
$rcs = Rcs->new;
$rcs->rcsdir(dirname($filename) . "/RCS");
$rcs->workdir(dirname($filename));
$rcs->file(basename($filename));
unless (-d dirname($filename) . "/RCS") {
die "Unable to create directory" . dirname($filename) . "/RCS! Check permissions on directories or make the salonify script setuid.\n" unless mkdir dirname($filename) . "/RCS";
}
unless (-e dirname($filename) . "/RCS/" . basename($filename) . ",v") {
die "Unable to perform initial check-in on file $filename with RCS! Check permissions on data files or make the salonify script setuid.\n" unless $rcs->ci('-u','-t-none'); # initial checkin
}
die "Unable to check out and lock $filename with RCS! Check permissions on data files or make the salonify script setuid.\n" unless $rcs->co('-l');
}
sub RcsClose {
$< = $>;
$( = $);
$rcs->ci('-u','-mnone') if ($salonify_use_rcs);
}
sub WriteTitles {
my $filename = shift;
my $hash = shift;
my ($name, $description);
my %hash = %$hash;
return 0 unless $salonify_allow_user_to_change_captions;
RcsOpen($filename);
open OUT, ">$filename";
while (($name, $description) = each %hash) {
print OUT $name . "\t" . $description . "\n";
}
close OUT;
RcsClose();
}
sub WriteFolders {
my $filename = shift;
my $hash = shift;
my ($name, $description);
my %hash = %$hash;
my @output;
return 0 unless $salonify_allow_user_to_edit_descriptions;
open IN, $filename || die "Sorry, no index could be found--check permissions!\n";
while () {
my ($id, $path, $images, $desc) = /^(.*)\t(.*)\t(.*)\t(.*)/;
if ($id) {
$desc = $hash{$id} if ($hash{$id});
push @output, "$id\t$path\t$images\t$desc";
} else {
chomp;
push @output, $_;
}
}
close IN;
RcsOpen($filename);
open OUT, ">$filename" || die "Couldn't open output file for writing $filename--check permissions!\n";
print OUT $_ . "\n" foreach (@output);
close OUT;
RcsClose($filename);
}
# CleanParam: This attempts to make CGI input more secure, by turning form or URL parameters into numbers-only
sub CleanParam {
my $parameter = shift;
return undef unless defined $param{$parameter};
return '1' unless $param{$parameter} ne "";
# Return one if parameter is defined but has no value--this means 'on'
$_ = $param{$parameter};
s/\0.*//; # Remove everything after null (if parameter appears multiple times)
s/(.*)-.*/$1/gi; # This is to take care of confusion from selected an album with no photos from the list
s/change|go/1/gi; # Change and go are labels for 'do it' buttons--we want that to show up as 'true'
s/\D//g; # Remove everything left that's not numbers
$_;
}
sub CookiePrefs {
my $cookie = shift || "";
my @prefs = split(/;|&/,$cookie);
my %prefs_keys;
foreach (@prefs) {
if ( (length $_) > 1 and not /^w\d|=.+/ ) {
while (s/^(.)//) {
$param{$1} = 1;
$prefs_keys{$1} = defined;
}
} elsif (/[;&]*([^=]*)(=?)(.*)/) {
$param{$1} = $3;
$prefs_keys{$1} = defined;
}
}
keys %prefs_keys;
}
sub ProcessPrefs {
my %prefs;
foreach (@_) {
/j/ and $prefs{"SlideShowPrefs"} = 1;
/J/ and $prefs{"SlideShowPrefs"} = 0;
/a/ and $prefs{"DirectoryNavigationTop"} = 1;
/A/ and $prefs{"DirectoryNavigationBottom"} = 1;
/b/ and $prefs{"SlideShowNavigationTop"} = 1;
/B/ and $prefs{"SlideShowNavigationBottom"} = 1;
/c/ and $prefs{"SlideShowButtonsTop"} = 1;
/C/ and $prefs{"SlideShowButtonsBottom"} = 1;
/d/ and $prefs{"SlideShowCaptionTop"} = 1;
/D/ and $prefs{"SlideShowCaptionBottom"} = 1;
/e/ and $prefs{"SlideShowSizeTop"} = 1;
/E/ and $prefs{"SlideShowSizeBottom"} = 1;
/f/ and $prefs{"SlideShowRotateTop"} = 1;
/F/ and $prefs{"SlideShowRotateBottom"} = 1;
/g/ and $prefs{"ShowPrefs"} = 1;
/h/ and $prefs{"ShowCreditsTop"} = 1;
/H/ and $prefs{"ShowCreditsBottom"} = 1;
}
return %prefs;
}
# DefaultPrefs: if no preferences are given in the URL or a cookie, return these
# Shows everything with the buttons on top and everything else below
# This way the user knows what features are available, and can hide them at will with the preferences
sub DefaultPrefs {
$_ = "";
$_ .= "j" if $salonify_allow_user_to_change_layout;
$_ .= "a" if $salonify_default_DirectoryNavigationTop;
$_ .= "A" if $salonify_default_DirectoryNavigationBottom;
$_ .= "b" if $salonify_default_SlideShowNavigationTop;
$_ .= "B" if $salonify_default_SlideShowNavigationBottom;
$_ .= "c" if $salonify_default_SlideShowButtonsTop;
$_ .= "C" if $salonify_default_SlideShowButtonsBottom;
$_ .= "d" if $salonify_default_SlideShowCaptionTop;
$_ .= "D" if $salonify_default_SlideShowCaptionBottom;
$_ .= "e" if $salonify_default_SlideShowSizeTop;
$_ .= "E" if $salonify_default_SlideShowSizeBottom;
$_ .= "f" if $salonify_default_SlideShowRotateTop;
$_ .= "F" if $salonify_default_SlideShowRotateBottom;
$_ .= "j" if $salonify_default_ShowPrefs;
$_ .= "h" if $salonify_default_ShowCreditsTop;
$_ .= "H" if $salonify_default_ShowCreditsBottom;
return CookiePrefs($_);
}
sub SlideShowPrefsButton {
p(
{ -align => "center"},
a(
{ -href => $salonify_script_url . "?j=Show Preferences" . ShowPrefsNoForm('sxyzmnopdb')},
"Show Preferences"
)
);
}
sub SlideShowPrefs {
return
start_form(-action=>$salonify_script_url, -method=>'GET',-name=>'prefs') .
table({ -border => 1, -cellspacing=>0, -width => "100%" },
Tr({-align=>"CENTER",-valign=>"TOP"} ,
[
th([' ','Photo Album List','Photo List','Navigation Buttons','Photo Captions','Photo Size','Photo Rotate','Credits']),
th('Above').td([
checkbox(-name=>'a',-value=>1,-label=>'',-checked=>$url_prefs{"DirectoryNavigationTop"} ),
checkbox(-name=>'b',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowNavigationTop"} ),
checkbox(-name=>'c',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowButtonsTop"} ),
checkbox(-name=>'d',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowCaptionTop"} ),
checkbox(-name=>'e',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowSizeTop"} ),
checkbox(-name=>'f',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowRotateTop"} ),
checkbox(-name=>'h',-value=>1,-label=>'',-checked=>$url_prefs{"ShowCreditsTop"} )
]),
th('Below').td([
checkbox(-name=>'A',-value=>1,-label=>'',-checked=>$url_prefs{"DirectoryNavigationBottom"} ),
checkbox(-name=>'B',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowNavigationBottom"} ),
checkbox(-name=>'C',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowButtonsBottom"} ),
checkbox(-name=>'D',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowCaptionBottom"} ),
checkbox(-name=>'E',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowSizeBottom"} ),
checkbox(-name=>'F',-value=>1,-label=>'',-checked=>$url_prefs{"SlideShowRotateBottom"} ),
checkbox(-name=>'H',-value=>1,-label=>'',-checked=>$url_prefs{"ShowCreditsBottom"} )
]),
th(' ').td({-colspan=>7},
checkbox(-name=>'q',-label=>'Save these settings in a cookie on your computer',-value=>1))
,
th(' ').td({-colspan=>7},ShowPrefsForm('sxyzmnob') . submit(-name=>'j', -value=>'Change')),
th(' ').td({-colspan=>7},submit(-name=>'J', -value=>'Hide Preferences'))
]
)) .
end_form;
}
sub ShowCredits {
return p(
{ -align => 'left' },
a( { -href => 'http://adam.rosi-kessel.org/salonify' } , $::program_title) . " $::program_version by " .
a( { -href => 'http://adam.rosi-kessel.org' },
"Adam Rosi-Kessel") . " / please " .
a( { -href => 'mailto:ajkessel@debian.org' },
"report any bugs!") . br .
" Copyright 2002-2005 permission granted to modify and redistribute under " .
a( { -href => 'http://www.gnu.org/licenses/gpl.html' },
'General Public License') . " v2.0 or later.");
}
sub ShowFile {
my $string;
$_ = shift;
open IN, $_ || (print STDERR "Could not open header file $_!\n" and return '');
$string = join('',);
close IN;
return $string;
}
print start_html(
-meta => {
'Info' => "Slideshow created with salonify, see http://adam.rosi-kessel.org/salonify for your own free copy."
},
-title => 'Photos',
-dtd => '-//W3C//DTD HTML 4.01 Transitional//EN',
-style => {
'src' => $salonify_cascading_style_sheet_url
}
);
my $directory_contents = DirectoryRead($url_folder);
my $local_path = $salonify_top_level_directory . $directory_contents;
my $image_url = $salonify_top_level_url . $directory_contents;
my %folders;
my %titles;
if ($url_download_album > 1) {
GenerateArchive($local_path, $image_url, $url_download_album);
} elsif ($url_index or not $url_folder or not $directory_contents) {
if ($salonify_allow_user_to_edit_descriptions and $url_edit_descriptions) {
foreach (keys %param) {
if (/l(\d{1,4})/ and $param{$_}) {
$folders{$1} = $param{$_};
}
}
WriteFolders($salonify_folders_file, \%folders) if keys %folders;
}
if ($salonify_alternate_index_file) {
print ShowFile($salonify_alternate_index_file);
} else {
print
ShowFile($salonify_top_of_page_file),
DirectoryIndex,
($url_prefs{"ShowCreditsBottom"} ? hr . ShowCredits : '');
}
} elsif ($url_download_album and ($salonify_generate_static_archive_file or $salonify_generate_dynamic_archive_file)) {
print DownloadAlbum($image_url, $local_path);
print ($url_prefs{"ShowCreditsBottom"} ? hr . ShowCredits : ''),
} else {
if ($url_thumbs) {
my @thumbnailfiles = <$local_path/.tn*>;
my @thumbnails;
foreach (@thumbnailfiles) {
push @thumbnails, "/" . basename($_) if /$salonify_image_filename_extensions/i;
}
(-e $local_path . "/" . $salonify_names_filename) and %titles = ReadTitles($local_path . "/" . $salonify_names_filename, @thumbnails);
foreach (keys %param) {
if (/w(\d{1,4})/ and $param{$_}){
$titles{StripSize($thumbnails[$1])} = $param{$_};
WriteTitles($local_path . "/" . $salonify_names_filename, \%titles);
}
}
print
ThumbNailButtons ($url_current_thumb, @thumbnails),
DirectoryNavigation,
ThumbNailNavigation ($url_current_thumb, @thumbnails),
ThumbNailImages ($url_current_thumb, $image_url, \%titles, @thumbnails),
ThumbNailPreferences,
($url_prefs{"ShowCreditsBottom"} ? hr . ShowCredits : '');
} else {
my @imagefiles = <$local_path/$size*>;
my @images;
foreach (@imagefiles) {
push @images, "/" . basename($_) if /$salonify_image_filename_extensions/i;
}
(-e $local_path . "/" . $salonify_names_filename) and %titles = ReadTitles($local_path . "/" . $salonify_names_filename, @images);
$url_current = 0 if $url_current >= @images;
$url_title and $titles{StripSize($images[$url_current])} = $url_title and WriteTitles($local_path . "/" . $salonify_names_filename, \%titles);
$url_rotate and RotateImage ($url_rotate, $url_current, $local_path, @images);
print
SlideShowSetupJava ($url_current, $image_url, \%titles, @images),
($url_prefs{"ShowCreditsTop"} ? ShowCredits . hr : ''),
($url_prefs{"SlideShowNavigationTop"} ? SlideShowNavigation ($url_current, $image_url, \%titles, @images) : ''),
($url_prefs{"DirectoryNavigationTop"} ? DirectoryNavigation : ''),
($url_prefs{"SlideShowSizeTop"} ? SlideShowSize : ''),
($url_prefs{"SlideShowRotateTop"} ? SlideShowRotate : '' ),
($url_prefs{"SlideShowButtonsTop"} ? SlideShowButtonsJava : ''),
($url_prefs{"SlideShowButtonsTop"} ? SlideShowButtonsNoJava ($url_current, $image_url, @images) : ''),
($url_prefs{"SlideShowCaptionTop"} ? SlideShowCaption($titles{StripSize($images[$url_current])}) : ''),
SlideShowImages ($url_current, $image_url, @images),
($url_prefs{"SlideShowCaptionBottom"} ? SlideShowCaption($titles{StripSize($images[$url_current])}) : ''),
($url_prefs{"SlideShowButtonsBottom"} ? SlideShowButtonsJava : ''),
($url_prefs{"SlideShowButtonsBottom"} ? SlideShowButtonsNoJava ($url_current, $image_url, @images) : ''),
($url_prefs{"SlideShowNavigationBottom"} ? SlideShowNavigation ($url_current, $image_url, \%titles, @images) : ''),
($url_prefs{"DirectoryNavigationBottom"} ? DirectoryNavigation : ''),
($url_prefs{"SlideShowSizeBottom"} ? SlideShowSize : ''),
($url_prefs{"SlideShowRotateBottom"} ? SlideShowRotate : '' ),
($salonify_allow_user_to_change_layout
?
($url_prefs{"SlideShowPrefs"}
?
SlideShowPrefs
:
SlideShowPrefsButton
)
:
''
),
($url_prefs{"ShowCreditsBottom"} ? hr . ShowCredits : ''),
($url_start_slideshow ?
'
'
:
''
)
}
}
print end_html;
1;
# Query parameters -- all one letter to make for shorter URLs
# a boolean -- show album navigation list above photos
# A boolean -- show album navigation list below photos
# b boolean -- show slideshow navigation list above photos
# B boolean -- show slideshow navigation list below photos
# c boolean -- show slideshow buttons above photos
# C boolean -- show slideshow buttons below photos
# d boolean -- show slideshow captions above photos
# D boolean -- show slideshow captions below photos
# e boolean -- show slideshow size buttons above photos
# E boolean -- show slideshow size buttons below photos
# f boolean -- show slideshow rotate buttons above photos
# F boolean -- show slideshow rotate buttons below photos
# h boolean -- show program credits above photos
# H boolean -- show program credits below photos
# i boolean -- show index page
# j boolean -- show preferences box
# J boolean -- hide preferences box
# k slideshow caption text string
# l edit folder descriptions (if permitted by configuration)
# L download entire album
# m columns in thumbnail chart
# n rows in thumbnail chart
# o boolean -- show titles on thumbnails page
# p reserved -- starts preferences string (Aa-Jj above)
# q boolean -- save preferences in a cookie
# r rotate image (clockwise or counterclockwise)
# s "random string" -- used to force reload of cached images when rotated
# t page within thumbnails
# u boolean -- show thumbnails or not
# v boolean -- auto start slideshow at beginning
# w# caption titles from thumbnails page -- # corresponds to slide number to recaption
# x photo number within album
# y album number
# z size (1=full, 2=medium, 3=small)