# ==================================================================
# Gossamer Links - enhanced directory management system
#
#   Website  : http://gossamer-threads.com/
#   Support  : http://gossamer-threads.com/scripts/support/
#   CVS Info : 087,071,086,086,085      
#   Revision : $Id: Utils.pm,v 1.61 2008/07/15 19:50:11 brewt Exp $
# 
# Copyright (c) 2001 Gossamer Threads Inc.  All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================

package Links::Utils;
# ==================================================================
# This package contains some builtin functions useful in your templates.
#
use strict;
use Links qw/$IN $DB $CFG $USER/;

sub is_editor {
# -------------------------------------------------------------------
# Returns true if the current user is an editor.
#
    return unless $USER and $USER->{Status} ne 'Not Validated';
    return $DB->table('Editors')->count({ Username => $USER->{Username} });
}

sub load_editors {
# -------------------------------------------------------------------
# You call this tag by placing <%Links::Utils::load_editors%> in your
# category.html template. It will then make available an <%editors%>
# tag that you can use in your template. For example:
#       <%Links::Utils::load_editors%>
#       <%if editors%>
#           The following users are editors in this category: <%editors%>
#       <%endif%>
#
    my $vars    = GT::Template->vars;
    my $cat_id  = $vars->{category_id} or return "No category_id tag found! This tag can only be used on category.html template";
    my $cat_db  = $DB->table('Category');
    my @parents = @{$cat_db->parents($cat_id)};
    push @parents, $cat_id;

    my $ed_db   = $DB->table('Editors', 'Users');
    my $sth     = $ed_db->select(GT::SQL::Condition->new('CategoryID', 'IN', \@parents));
    return {} unless ($sth->rows);

    # Make any formatting changes you need here.
    my $output  = '<ul>';
    my @editors;
    my %seen;
    while (my $user = $sth->fetchrow_hashref) {
        next if ($seen{$user->{Username}}++);
        $output .= qq|<li>$user->{Username}</li>|;
        push @editors, $user;
    }
    $output .= "</ul>";
    return { editors => $output, editors_loop => \@editors };
}

sub load_user {
# -------------------------------------------------------------------
# You call this tag in your link.html or detailed.html template. It will
# provide all the information about the user who owns the link, and also
# create a Contact_Name and Contact_Email tag for backwards compatibility.
# So you would put: 
#       <%Links::Utils::load_user%>
#       This link is owned by <%Username%>, whose email is <%Email%>
#       and password is <%Password%>. They are a <%Status%> user.
#
    my $vars     = GT::Template->vars;
    my $username = $vars->{LinkOwner} or return "No LinkOwner tag found! This tag can only be used on link.html or detailed.html templates.";
    require Links::Authenticate;
    my $user_r = Links::Authenticate->auth('get_user', { Username => $username } );
    return $user_r;
}

sub load_reviews {
# -------------------------------------------------------------------
# You call this tag in link.html or detailed.html template. It will 
# load all the reviews associated with this link.
# So you would put:
#       <%Links::Utils::load_reviews($ID, $max_reviews)%>
#       This link has <%Review_Total%> reviews.
#       <%loop Reviews_Loop%><%Review_Subject%> - <%Review_ByLine%><%endloop%>
# Review_Count is a deprecated backwards compatible variable
#
    my ($id, $max) = @_;
    unless ($id) {
        my $vars = GT::Template->vars;
        $id = $vars->{ID};
    }
    my $reviews = $DB->table('Reviews');
    if ($CFG->{review_sort_by}) {
        my $order = $CFG->{review_sort_order} || 'DESC';
        $reviews->select_options("ORDER BY $CFG->{review_sort_by} $order");
    }
    if ($max and $max =~ /^\d+$/) {
        $reviews->select_options("LIMIT $max");
    }
    my $review_total = $reviews->count({ Review_LinkID => $id, Review_Validated => 'Yes' });
    my $sth = $reviews->select({ Review_LinkID => $id, Review_Validated => 'Yes' });
    my @reviews;
    Links::init_date();
    require Links::User::Review;
    my $today = GT::Date::date_get();
    while (my $rev = $sth->fetchrow_hashref) {
        $rev->{Review_IsNew} = (GT::Date::date_diff($today, $rev->{Review_Date}) < $CFG->{review_days_old});
        $rev->{Review_CanModify} = 0;
        if ($CFG->{review_allow_modify} and $USER->{Username} eq $rev->{Review_Owner}) {
            if ($CFG->{review_modify_timeout}) {
                my $oldfmt = GT::Date::date_get_format();
                GT::Date::date_set_format(GT::Date::FORMAT_DATETIME);
                my $timeout = GT::Date::date_get(time - $CFG->{review_modify_timeout} * 60);
                my $date = $rev->{Review_ModifyDate} =~ /^0000-00-00 00:00:00/ ? $rev->{Review_Date} : $rev->{Review_ModifyDate};
                if (GT::Date::date_is_greater($date, $timeout)) {
                    $rev->{Review_CanModify} = 1;
                }
                GT::Date::date_set_format($oldfmt);
            }
            else {
                $rev->{Review_CanModify} = 1;
            }
        }
        if ($rev->{Review_ModifyDate} ne $rev->{Review_Date} and $rev->{Review_ModifyDate} !~ /^0000-00-00 00:00:00/) {
            $rev->{Review_ModifyDate} = GT::Date::date_transform($rev->{Review_ModifyDate}, GT::Date::FORMAT_DATETIME, $CFG->{date_review_format});
        }
        else {
            delete $rev->{Review_ModifyDate};
        }
        $rev->{Review_Date} = GT::Date::date_transform($rev->{Review_Date}, GT::Date::FORMAT_DATETIME, $CFG->{date_review_format});
        $rev->{Num} = $rev->{Review_WasHelpful} + $rev->{Review_WasNotHelpful};
        $CFG->{review_convert_br_tags} and $rev->{Review_Contents} = Links::User::Review::_translate_html($rev->{Review_Contents});
        push @reviews, $rev;
    }

    return { Review_Total => $review_total, Review_Count => scalar @reviews, Review_Loop => \@reviews };
}

sub load_link {
# -------------------------------------------------------------------
# This will return a fully formatted link.  Deprecated in favour of
# using load_link_info() + <%include link.html%>
#
    my %vars = %{GT::Template->vars};
    if ($Links::GLOBALS) {
        delete @vars{keys %$Links::GLOBALS};
    }
    return Links::SiteHTML::display('link', \%vars);
}

sub load_link_info {
# -------------------------------------------------------------------
# This will return the vars needed to display a fully formatted link (i.e. by
# including link.html)
#
    return Links::SiteHTML::tags(link => GT::Template->vars);
}

sub paging {
# -------------------------------------------------------------------
# Generate the html needed for a paging toolbar
#
# The paging hash (retrieved from vars) should contain:
#     url
#     page
#         Only one of url or page should be included.
#         url is used when the generated url will be <%url%>;nh=<%page_number%>
#         page is used when the generated url will be <%build_root_url%>/<%page%>...
#     page_format
#         1: <%build_root_url%>/<%page%>{index,more<%current_page%>}.html
#             Used in category, cool, new pages
#         2: <%build_root_url%>/<%page%>{,_<%current_page%>}.html
#             Used in new page
#     num_hits
#     max_hits
#     current_page
#
# Options:
#     max_pages
#         The maximum number of pages to display (excluding boundary pages)
#     boundary_pages
#         When there are more pages than max_pages, this number of boundary
#         pages are added to the paging toolbar
#     style
#         1: |< < [1 of 20] > >|
#         2: [1 of 20] < >
#         3: |< < 1 2 3 4 5 6 7 8 9 ... 20 > >|
#     style_next
#     style_prev
#     style_first
#     style_last
#     style_nonext
#     style_noprev
#     style_nofirst
#     style_nolast
#         These options allow you to change what's shown for the next/prev/etc
#         actions
#     lang_of
#         For styles 1 and 2, they use the format of "<page> <lang_of> <page>".
#         This option allows you to change the english text of "of".
#     lang_button
#         For styles 1 and 2, a "Go" button is used for users which do not have
#         javascript support.  This option allows you to change the button's
#         label.
#     button_id
#         If you've got two paging toolbars on a page, then you will need to
#         change the button_id so that the javascript can remove the button.
#     paging_pre
#     paging_post
#         This text or html is added before and after the paging html.
#
# There are two ways of setting the above options:
#     1) Pass them in as arguments
#     2) Create a global code ref named 'paging_options' and return the options
#        as a hash reference
# Options passed as arguments override all options passed in via other methods,
# followed by the global options and lastly the defaults contained in this
# function.
#
# Note 1: You can override this function by creating a paging_override global
# Note 2: The arguments to paging_override are slightly different.  To keep
#     duplicated code to a minimum, %paging with the paging calculations done
#     is passed as the first argument (it also contains a few helper code
#     refs), and the second argument contains the options with defaults set.
#     The left over arguments are the passed in options (shouldn't be needed
#     since they have been merged into the options already).
#
    my $vars = GT::Template->vars;

    return unless ref $vars->{paging} eq 'HASH';
    my %paging = %{$vars->{paging}};
    return if not $paging{num_hits} or $paging{num_hits} < $paging{max_hits};

    %paging = (
        page_format  => 1,
        current_page => 1,
        form_hidden  => '',
        %paging
    );

# Setup the default options
    my %paging_options;
    %paging_options = %{$vars->{paging_options}->()} if ref $vars->{paging_options} eq 'CODE';
    my %options = (
        max_pages      => 10,
        boundary_pages => 1,
        style          => 1,
        style_next     => '<img src="' . image_url('paging-next.gif') . '" alt="&gt;" title="Next Page" />',
        style_prev     => '<img src="' . image_url('paging-prev.gif') . '" alt="&lt;" title="Previous Page" />',
        style_first    => '<img src="' . image_url('paging-first.gif') . '" alt="|&lt;" title="First Page" />',
        style_last     => '<img src="' . image_url('paging-last.gif') . '" alt="&gt;|" title="Last Page" />',
        style_nonext   => '<img src="' . image_url('paging-nonext.gif') . '" alt="" />',
        style_noprev   => '<img src="' . image_url('paging-noprev.gif') . '" alt="" />',
        style_nofirst  => '<img src="' . image_url('paging-nofirst.gif') . '" alt="" />',
        style_nolast   => '<img src="' . image_url('paging-nolast.gif') . '" alt="" />',
        lang_of        => 'of',
        lang_button    => 'Go',
        button_id      => 'paging_button',
        paging_pre     => '',
        paging_post    => '',
        %paging_options,
        @_
    );

# Make all the page calculations
    $paging{num_pages} = int($paging{num_hits} / $paging{max_hits});
    $paging{num_pages}++ if $paging{num_hits} % $paging{max_hits};
    my ($start, $end);
    if ($paging{num_pages} <= $options{max_pages}) {
        $start = 1;
        $end = $paging{num_pages};
    }
    elsif ($paging{current_page} >= $paging{num_pages} - $options{max_pages} / 2) {
        $end = $paging{num_pages};
        $start = $end - $options{max_pages} + 1;
    }
    elsif ($paging{current_page} <= $options{max_pages} / 2) {
        $start = 1;
        $end = $options{max_pages};
    }
    else {
        $start = $paging{current_page} - int($options{max_pages} / 2) + 1;
        $start-- if $options{max_pages} % 2;
        $end = $paging{current_page} + int($options{max_pages} / 2);
    }

    my ($left_boundary, $right_boundary);
    if ($end >= $paging{num_pages} - $options{boundary_pages} - 1) {
        $end = $paging{num_pages};
    }
    else {
        $right_boundary = 1;
    }

    if ($start <= $options{boundary_pages} + 2) {
        $start = 1;
    }
    else {
        $left_boundary = 1;
    }

    my @pages;
    push @pages, 1 .. $options{boundary_pages}, '...' if $left_boundary;
    push @pages, $start .. $end;
    push @pages, '...', $paging{num_pages} - $options{boundary_pages} + 1 .. $paging{num_pages} if $right_boundary;
    $paging{pages} = \@pages;

    $paging{create_link} = sub {
        my ($page, $disp) = @_;
        my $ret = '';
        $ret .= qq|<a href="|;

        if ($paging{url}) {
            (my $url = $paging{url}) =~ s/([;&?]?)nh=(\d+)/($1 and $1 eq '?') ? '?' : ''/eg;
            $ret .= $url;
            $ret .= index($url, '?') != -1 ? ';' : '?';
            $ret .= "nh=$page";
        }
        else {
            $ret .= "$CFG->{build_root_url}/$paging{page}";
            if ($paging{page_format} == 1) {
                $ret .= $page == 1 ? ($CFG->{build_index_include} ? $CFG->{build_index} : '') : "$CFG->{build_more}$page$CFG->{build_extension}";
            }
            elsif ($paging{page_format} == 2) {
                $ret .= "_$page" if $page > 1;
                $ret .= $CFG->{build_extension};
            }
        }
        $ret .= qq|">$disp</a>|;
        return $ret;
    };

    $paging{select_value} = sub {
        my $page = shift;
        if ($paging{url}) {
            return $page;
        }
        else {
            my $ret = $paging{page};
            if ($paging{page_format} == 1) {
                $ret .= $page == 1 ? ($CFG->{build_index_include} ? $CFG->{build_index} : '') : "$CFG->{build_more}$page$CFG->{build_extension}";
            }
            elsif ($paging{page_format} == 2) {
                $ret .= "_$page" if $page > 1;
                $ret .= $CFG->{build_extension};
            }
            return $ret;
        }
    };

    if ($paging{url}) {
# Figure out what needs to be submitted with the form (it *should* have ? in it
# since with these queries, it *will* have other arguments)
        ($paging{form_action}, my $args) = $paging{url} =~ /^(.*?)\?(.*)$/;
        NV: for (split /[;&]/, $args) {
            my ($name, $val) = /([^=]+)=(.*)/ or next;
            $name = $IN->unescape($name);
            $val = $IN->unescape($val);

# Skip these since Links::clean_output will put them in automatically
            for (@{$CFG->{dynamic_preserve}}, 'nh') {
                next NV if $name eq $_;
            }
            $paging{form_hidden} .= qq|<input type="hidden" name="| . $IN->html_escape($name) . qq|" value="| . $IN->html_escape($val) . qq|" />|;
        }
        $paging{select_name} = 'nh';
    }
    else {
        $paging{form_action} = "$CFG->{db_cgi_url}/page.cgi";
        $paging{select_name} = 'g';
    }

# Override this function.  Pass in the updated %paging and %options hashes so
# the calculations don't have to be duplicated in the override.
    if (ref $vars->{paging_override} eq 'CODE') {
        return $vars->{paging_override}->(\%paging, \%options, @_);
    }

    my $html;
    if ($options{style} == 1) {
# |< < [1 of 20] > >|
        $html .= qq|<form action="$paging{form_action}">$paging{form_hidden}$options{paging_pre}|;
        if ($paging{current_page} != 1) {
            $html .= $paging{create_link}->(1, $options{style_first}) . ' ' . $paging{create_link}->($paging{current_page} - 1, $options{style_prev}) . ' ';
        }
        else {
            $html .= "$options{style_nofirst} $options{style_noprev} ";
        }

        $html .= qq|<select name="$paging{select_name}" onchange="if (this.options[this.selectedIndex].innerHTML != '...' &amp;&amp; !this.options[this.selectedIndex].defaultSelected) |;
        $html .= $IN->param('d') || $paging{url} ? qq|this.form.submit()| : qq|window.location = '$CFG->{build_root_url}/' + this.value|;
        $html .= qq|">|;
        for (@{$paging{pages}}) {
            if ($_ eq '...') {
                $html .= qq|<option value="" disabled="disabled">...</option>|;
            }
            else {
                $html .= qq|<option value="| . $paging{select_value}->($_) . '"';
                $html .= qq| selected="selected"| if $_ == $paging{current_page};
                $html .= qq|>$_ $options{lang_of} $paging{num_pages}</option>|;
            }
        }
        $html .= qq|</select><noscript><input type="submit" id="$options{button_id}" value="$options{lang_button}" class="submit" /></noscript> |;

        if ($paging{current_page} != $paging{num_pages}) {
            $html .= $paging{create_link}->($paging{current_page} + 1, $options{style_next}) . ' ' . $paging{create_link}->($paging{num_pages}, $options{style_last});
        }
        else {
            $html .= "$options{style_nonext} $options{style_nolast}";
        }
        $html .= qq|$options{paging_post}</form>|;
    }
    elsif ($options{style} == 2) {
# [1 of 20] < >
        $html .= qq|<form action="$paging{form_action}">$paging{form_hidden}$options{paging_pre}<select name="$paging{select_name}" onchange="if (this.options[this.selectedIndex].innerHTML != '...' &amp;&amp; !this.options[this.selectedIndex].defaultSelected) |;
        $html .= $IN->param('d') || $paging{url} ? qq|this.form.submit()| : qq|window.location = '$CFG->{build_root_url}/' + this.value|;
        $html .= qq|">|;
        for (@{$paging{pages}}) {
            if ($_ eq '...') {
                $html .= qq|<option value="" disabled="disabled">...</option>|;
            }
            else {
                $html .= qq|<option value="| . $paging{select_value}->($_) . '"';
                $html .= qq| selected="selected"| if $_ == $paging{current_page};
                $html .= qq|>$_ $options{lang_of} $paging{num_pages}</option>|;
            }
        }
        $html .= qq|</select><noscript><input type="submit" id="$options{button_id}" value="$options{lang_button}" class="submit" /></noscript> |;

        if ($paging{current_page} != 1) {
            $html .= $paging{create_link}->($paging{current_page} - 1, $options{style_prev}) . ' ';
        }
        else {
            $html .= "$options{style_noprev} ";
        }

        if ($paging{current_page} != $paging{num_pages}) {
            $html .= $paging{create_link}->($paging{current_page} + 1, $options{style_next});
        }
        else {
            $html .= $options{style_nonext};
        }
        $html .= qq|$options{paging_post}</form>|;
    }
    elsif ($options{style} == 3) {
# |< < 1 2 3 4 5 6 7 8 9 ... 20 > >|
        $html .= $options{paging_pre};
        if ($paging{current_page} != 1) {
            $html .= $paging{create_link}->(1, $options{style_first}) . ' ' . $paging{create_link}->($paging{current_page} - 1, $options{style_prev}) . ' ';
        }
        else {
            $html .= "$options{style_nofirst} $options{style_noprev} ";
        }

        for (@{$paging{pages}}) {
            if ($_ eq '...') {
                $html .= "$_ ";
            }
            elsif ($_ == $paging{current_page}) {
                $html .= "<span>$_</span> ";
            }
            else {
                $html .= $paging{create_link}->($_, $_) . ' ';
            }
        }

        if ($paging{current_page} != $paging{num_pages}) {
            $html .= $paging{create_link}->($paging{current_page} + 1, $options{style_next}) . ' ' . $paging{create_link}->($paging{num_pages}, $options{style_last});
        }
        else {
            $html .= "$options{style_nonext} $options{style_nolast}";
        }
        $html .= $options{paging_post};
    }

    return \$html;
}

sub format_title {
# -------------------------------------------------------------------
# Format a title
#
# Options:
#     separator (required)
#         The separator used to join the items.
#     no_escape_separator
#         Set this to a true value if you do not wish to HTML escape the separator.
#     include_home
#         Whether or not to include Home as the first entry.  Default is no.
#     include_last
#         Whether or not to include the last entry.  Default is yes.
#     link_type
#         How the items should be linked:
#         0: No items linked
#         1: All items linked separately
#         2: All except the last item linked separately
#         3: All items linked as one single link (using the last item's URL)
#     no_span
#         Don't add the span tags around the last portion of the title.  Default is to include the span tags.
#
# Note: You can override this function by creating a format_title_override global
# 
    my ($title_loop, %options) = @_;
    return unless ref $title_loop eq 'ARRAY';

    my $vars = GT::Template->vars;
    if (exists $vars->{format_title_override}) {
        return $vars->{format_title_override}->(@_);
    }

    if (!exists $options{include_last}) {
        $options{include_last} = 1;
    }

    if (!$options{include_last}) {
        pop @$title_loop;
    }

    my $ret;
    $options{separator} = GT::CGI::html_escape($options{separator}) unless $options{no_escape_separator};
    for (0 .. $#$title_loop) {
        next unless $_ or $options{include_home};
        $ret .= '<span class="lasttitle">' if $_ == $#$title_loop and not $options{no_span} and $options{include_last};
        if ($options{link_type} == 1 or
            ($options{link_type} == 2 and $_ != $#$title_loop)) {
            $ret .= qq|<a href="| . $IN->html_escape($title_loop->[$_]->{URL}) . qq|">$title_loop->[$_]->{Name}</a>|;
        }
        else {
            $ret .= $title_loop->[$_]->{Name};
        }
        $ret .= $options{separator} unless $_ == $#$title_loop;
        $ret .= '</span>' if $_ == $#$title_loop and not $options{no_span} and $options{include_last};
    }
    if ($options{link_type} == 3) {
        $ret = qq|<a href="| . $IN->html_escape($title_loop->[-1]->{URL}) . qq|">$ret</a>|;
    }
    return \$ret;
}

sub column_split {
# -------------------------------------------------------------------
# Calculate where the columns should be
#
    my ($items, $columns) = @_;
    if ($items % $columns > 0) {
        $items += ($columns - $items % $columns);
    }
    return $items / $columns;
}

sub image_url {
# -------------------------------------------------------------------
# Takes an filename and using the current template set and theme, returns
# the url of the image.  It first checks if the file exists in the theme's
# image directory, checks the template's image directory, and then tries
# to check the template inheritance tree for more image directories.
#
    my $image = shift;
    my ($template, $theme) = Links::template_set();

    if (-e "$CFG->{build_static_path}/$template/images/$theme/$image") {
        return "$CFG->{build_static_url}/$template/images/$theme/$image";
    }

# Grab the inheritance tree of the template set and grab the basename of
# each template set path (making an assumption that they won't do anything
# crazy with their inheritance).
    require GT::File::Tools;
    require GT::Template::Inheritance;
    my @paths = GT::Template::Inheritance->tree(path => "$CFG->{admin_root_path}/templates/$template", local => 0);
    for (@paths) {
        my $tpl = GT::File::Tools::basename($_);
        next if $tpl eq 'browser';
        if (-e "$CFG->{build_static_path}/$tpl/images/$image") {
            return "$CFG->{build_static_url}/$tpl/images/$image";
        }
    }

# The image doesn't exist here, but return it anyway
    return "$CFG->{build_static_url}/$template/images/$image";
}

1;
