# ==================================================================
# Plugins::UI - Auto Generated Program Module
#
#   Plugins::UI
#   Author  : Bao Phan
#   Version : 1.0
#   Updated : Tue Mar 15 12:59:20 2016
#
# ==================================================================
#

package Plugins::UI;
# ==================================================================

use strict;
use GT::Base;
use GT::Plugins qw/STOP CONTINUE/;
use Links qw/:objects :payment/;
use Links::Build;
use Links::SiteHTML;

use constants
  VIDEO   => 'video',
  PHOTO   => 'photo',
  ARTICLE => 'article'
;
# Inherit from base class for debug and error methods
@Plugins::UI::ISA = qw(GT::Base);

require Plugins::SlideShow;
require Plugins::ConvertVideo;
require GT::SQL::Condition;
require GT::Date;

sub cat_url {
    my $full_name = shift || return;

    return "$CFG->{build_root_url}/" . $DB->table('Category')->as_url($full_name) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
}

sub update_featured_links {
    my ($type, $id, $add) = @_;

    return if $USER->{Status} ne 'Administrator';

    return unless $id and $type;
    return if $type !~ /^(article|photo|video)$/i;

    my $name = $type eq 'article' ? 'featured_articles' : 'featured_photos';
    my $ids  = $CFG->{$name} || [];

    my (@ids, $changed);
    my %ids = map { $_ => 1 } @$ids;
    if ($add) {
        if ($ids{$id}) {
            @ids = @$ids;
        }
        else {
            @ids = ($id, @$ids);
        
            my $hits = scalar @ids;
            if ($type eq 'article' and $hits > 4) {
                pop @ids;
            }
            elsif ($type ne 'article' and $hits > 2) {
                pop @ids;
            }
            $changed = 1;
        }
    }
    elsif ($ids{$id}) {
        @ids = map $_, grep { $_ != $id } @$ids;
        $changed = 1;
    }

    return unless $changed;
    
    $CFG->{$name} = \@ids;
    $CFG->save;
    return;
}

sub is_featured {
    my ($type, $id) = @_;
    
    return unless $id and $type;
    return if $type !~ /^(article|photo|video)$/i;
    
    my $name = $type eq 'article' ? 'featured_articles' : 'featured_photos';
    my $ids  = $CFG->{$name} || [];
    my %ids  = map { $_ => 1 } @$ids;
    return $ids{$id} ? 1 : 0;
}

sub fetch_categories {
    my $ids = shift;

    $ids = ref $ids ? @$ids : [$ids] if $ids;

    my $tab = $DB->table('Category');
    $tab->select_options('ORDER BY Name');
    my $cond = GT::SQL::Condition->new( CatDepth => '=' => 0);
    $cond->add(ID => '=' => $ids) if $ids;

    my $cats = $tab->select($cond)->fetchall_hashref;
    foreach (@$cats) {
        $_->{URL} = "$CFG->{build_root_url}/" . $tab->as_url($_->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
    }
    return $cats;
}

sub fetch_links {
    my %args = @_;

    return unless %args;

    my $maxhits = $args{max_hits} || 1;
    my $cond    = new GT::SQL::Condition;
    my $url     = $CFG->{build_detail_url};
    my (@ids, %paging);

    my $tab_cat  = $DB->table('Category');
    my $tab_lnks = $DB->table('Links');
    my $tab_catlnks = $DB->table(qw/Links CatLinks Category/);
    if ($args{type}) {
        $cond->add(Type => '=' => $args{type});
        $tab_catlnks->select_options("GROUP BY LinkID", "ORDER BY Add_Date DESC", "LIMIT $maxhits");
    }
    elsif ($args{ids}) {
        if (ref $args{ids} eq 'ARRAY') {
            @ids = @{$args{ids}};
        }
        else {
            @ids = split(/\,|\r?\n/, $args{ids});
        }
        return unless scalar @ids;

        $cond->add(LinkID => '=' => \@ids);
        $tab_catlnks->select_options("ORDER BY Add_Date DESC");
        $tab_catlnks->select_options("LIMIT $maxhits") if $args{max_hits};
    }
    elsif ($args{category}) {
        my $category = $tab_cat->get($args{category});
        return unless $category;

        $cond->add(Full_Name => 'like' => $category->{Full_Name} . '%');
        $tab_catlnks->select_options("ORDER BY Add_Date DESC", "LIMIT $maxhits");
    }
    elsif ($args{tag} and $args{tag} =~ /^(?:swim|bike|run)$/i) {
        my $vars = GT::Template->vars;
        my $tag  = lc $args{tag};
        $url    .= "/$tag";

        $paging{max_hits}     = $args{max_hits} || 25;
        $paging{current_page} = $vars->{nh} || 1;
        $paging{page}         = $args{url} || ($tag . '/');

        my $offset = $paging{current_page} == 1 ? 0 : ($paging{current_page} - 1) * $paging{max_hits};

        $cond->add('tag_' . $tag => '=' => 1);
        $tab_catlnks->select_options("GROUP BY LinkID", "ORDER BY Add_Date DESC", "LIMIT $paging{max_hits} OFFSET $offset");
    }
    elsif ($args{link_type} and $args{link_type} =~ /^(?:photo|video)$/i) {
        my $vars = GT::Template->vars;
        $url    .= "/" . ($args{link_type} eq 'video' ? 'Videos' : 'Photos');

        $paging{max_hits}     = $args{max_hits} || 25;
        $paging{current_page} = $vars->{nh} || 1;
        $paging{page}         = ($args{link_type} eq 'video' ? 'Videos' : 'Photos') . '/';

        my $offset = $paging{current_page} == 1 ? 0 : ($paging{current_page} - 1) * $paging{max_hits};

        $cond->add(Link_Type => '=' => $args{link_type});
        $tab_catlnks->select_options("GROUP BY LinkID", "ORDER BY Add_Date DESC", "LIMIT $paging{max_hits} OFFSET $offset");
    }
    else {
        $tab_catlnks->select_options("GROUP BY LinkID", "ORDER BY Add_Date DESC", "LIMIT $maxhits");
    }

    my $links = ($tab_catlnks->select(qw/Links.* Name Full_Name/, $cond) or die $GT::SQL::error)->fetchall_hashref;
    return unless scalar @$links;

    foreach my $l (@$links) {
        $l->{detailed_url}  = "$url/" . $tab_lnks->detailed_url($l->{ID}) if $CFG->{build_detailed}; 
        $l->{URL}           = "$CFG->{build_root_url}/" . $tab_cat->as_url($l->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
        $l->{thumbnail_url} = fetch_thumbnail($l);
    }

    if (scalar @ids and !$args{db}) {
        my @links_output;
        my %links = map { $_->{ID} => $_ } @$links;

        foreach my $id (@ids) {
            push @links_output, $links{$id} if $links{$id};
        }

        return \@links_output;
    }
    elsif (
        ($args{link_type} and $args{link_type} =~ /^(?:photo|video)$/) or 
        ($args{tag} and $args{tag} =~ /^(?:swim|bike|run)$/) 
    ) {
        $paging{num_hits} = $tab_lnks->select('COUNT(*)', $cond)->fetchrow;

        my @features;
        if ($paging{current_page} == 1) {
            @features = splice @$links, 0, 3;
        }
        return { loop => $links, features => \@features, paging => \%paging };
    }
    else {
        return $links;
    }
}

sub fetch_category {
    my $catid = shift || return;
    return $DB->table('Category')->get($catid);
}

sub fetch_widget {
    my $id = shift || return;

    my $tab = $DB->table('Widgets');
    my $widget = $tab->get($id);

    return unless $widget;

    if ($widget->{Image}) {
        my $fh = $tab->file_info('Image', $widget->{ID});
        $widget->{Image_URL} = '/images/widgets/' . $fh->File_RelativeURL;
    }
    return $widget;
}

sub fetch_widgets {
    my ($page, $catid) = @_;
    
    return unless $page;

    my $tab_pgwidgets = $DB->table(qw/Widgets PageWidgets/);
    $tab_pgwidgets->select_options('ORDER BY Sort_Pos');

    my $widgets = [];
    if ($catid =~ /^\d+$/) {
        $widgets = $tab_pgwidgets->select(qw/Widgets.* Sort_Pos/, { Page => $catid })->fetchall_hashref;
    }

    unless (scalar @$widgets) {
        $widgets = $tab_pgwidgets->select(qw/Widgets.* Sort_Pos/, { Page => $page })->fetchall_hashref;
    }
    my $tab = $DB->table('Widgets');
    foreach my $w (@$widgets) {
        next unless $w->{Image};
        my $fh = $tab->file_info('Image', $w->{ID});
        next unless $fh;
        $w->{Image_URL} = '/images/widgets/' . $fh->File_RelativeURL;
    }
    return $widgets;
}

sub generate_widget {
    my $id = shift || return;
    
    my $widget = fetch_widget($id);

    return unless $widget;
    return Links::SiteHTML::display('include_single_widget', $widget);
}

sub friendly_date {
    my ($date, $timestmp, $format) = @_;
    
    return unless $date;

    my $days = GT::Date::date_diff(GT::Date::date_get(time, '%yyyy%-%mm%-%dd%'), $date);
    unless ($days) {
        my $time = GT::Date::timelocal(GT::Date::parse_format($timestmp, "%yyyy%-%mm%-%dd% %HH%:%MM%:%ss%"));
        my $secs = time - $time;
        return unless $secs;

        $date = $timestmp;
        return "$secs seconds ago" if $secs < 60;

        my $mins = int($secs / 60);
        return "$mins minutes ago" if $mins < 60;

        my $hours = int($secs / 3600); 
        return $hours . ($hours > 1 ? " hours ago" : " hour ago");
    }
    else {
        my $time = GT::Date::timelocal(GT::Date::parse_format($date, "%yyyy%-%mm%-%dd%")); 
        $date = $time;

        return ($format or $days > 7)
          ? GT::Date::date_get($date, $format || "%mmm% %dd%, %yyyy%")
          : $days . ($days > 1 ? " days ago" : " day ago");
    }
}

sub retrieve_param {
    my ($field, $count) = @_;

    return unless $field;

    $count ||= 1;
    my $vars = GT::Template->vars; 

    if ($field eq 'Image_description') {
        return $vars->{"Image${count}_description"};
    }
    elsif ($field =~ /^Image_(.*)$/) {
        return $vars->{"Image${count}_$1"} || $vars->{"Image${count}_path"};
    }
    else {
        return $vars->{"$field$count"};
    }
}

sub fetch_thumbnail {
    my $link = shift || return;

    if ($link->{Link_Type} eq VIDEO) {
        if ($link->{Thumbnail_URL} and $link->{Thumbnail_URL} ne 'http://') {
            return { small => $link->{Thumbnail_URL} };
        }
        else {
            my $field = Plugins::ConvertVideo::get_file_path($link->{ID}, "thumbnail_file_field");
            return $field->{thumbnail_file_field_path}
                ? {
                        small   => $field->{thumbnail_file_field_path},
                        medium  => $field->{thumbnail_file_field_path},
                        large   => $field->{thumbnail_file_field_path},
                        largest => $field->{thumbnail_file_field_path},
                  }
                : undef;
        }
    }
    else {
        my $slideshow = Plugins::SlideShow::generate_paths($link->{ID});
        if ($slideshow and $slideshow->{image_loop}) {
            return {
                small   => $slideshow->{image_loop}->[0]{_thumbnail_path},
                medium  => $slideshow->{image_loop}->[0]{_medium_path},
                large   => $slideshow->{image_loop}->[0]{_large_path},
                largest => $slideshow->{image_loop}->[0]{_largest_path}
            };
        }
    }
}

sub slideshow_url {
    my $url = shift || return;

    $url =~ s,^$CFG->{build_detail_url},$CFG->{build_detail_url}/Photos,;
    return $url;
}

sub rewrite_breadcrumbs {
    my ($title_loop, $mode) = @_;
    
    return unless ref $title_loop;

    my @loop;
    foreach my $i (0 .. scalar @$title_loop - 1) {
        my $item = $title_loop->[$i];
        if ($i == 1) {
            if ($item->{Name} =~ /rd\s*aids/i) {
                push @loop, {
                    Name => "Races",
                    URL  => "$CFG->{build_root_url}/Races/index.html"
                };
            }
            elsif ($mode) {
                push @loop, {
                    Name => $mode eq 'photo' ? 'Photo Galleries' : 'Videos',
                    URL  => "$CFG->{build_root_url}/" . ($mode eq 'photo' ? 'Photos' : 'Videos') . "/index.html"
                };
            }
            elsif ($item->{Name} !~ /home|bike\s*fit|products|races|articles|photos|videos|coaching|podcast|privacy|about|agreement/i) {
                push @loop, {
                    Name => "Articles",
                    URL  => "$CFG->{build_root_url}/Articles/index.html"
                };
            }
        }
        push @loop, $item;
    }
    return \@loop;
}

sub build_category {
    my $opts = shift;
    $opts->{id} ||= $IN->param('ID');

    return @_ unless $opts->{id};

    my $cfg = Links::Plugins->get_plugin_user_cfg('UI');
    return @_ unless $cfg and $cfg->{merge_categories};

    my %ids = map { $_ => 1 } split(/\s*,\s*/, $cfg->{merge_categories});
    return @_ unless $ids{$opts->{id}};

    GT::Plugins->action( STOP );

    my $cat_db     = $DB->table('Category');
    my $link_db    = $DB->table('Links');
    my $catlink_db = $DB->table('Links', 'CatLinks');
    my $related_db = $DB->table('CatRelations');
    $Links::Build::GRAND_TOTAL ||= Links::Build::_grand_total();

    if (ref $opts ne 'HASH') {
        Links::debug("Invalid argument passed to build_category: $opts") if $Links::DEBUG;
        return @_;
    }

# Load our category info.
    my $category;
    if ($opts->{id}) {
        $category = $cat_db->get($opts->{id}, 'HASH');
        if (! $category) {
            Links::debug("Invalid category id passed to build_category: $opts->{id}") if $Links::DEBUG;
            return;
        }
    }

# Get our options.
    $opts->{mh} = exists $opts->{mh} ? $opts->{mh} : $CFG->{build_span_pages} ? $CFG->{build_links_per_page} : 5000;
    $opts->{nh} = exists $opts->{nh} ? $opts->{nh} : 1;
    $opts->{sb} = exists $opts->{sb} ? $opts->{sb} : $CFG->{build_sort_order_category};
    $opts->{so} = exists $opts->{so} ? $opts->{so} : '';
    if ($opts->{sb} =~ /\b(?:asc|desc)\b/i) {
        $opts->{so} = '';
    }
    $opts->{cat_sb} = exists $opts->{cat_sb} ? $opts->{cat_sb} : $CFG->{build_category_sort};
    $opts->{cat_so} = exists $opts->{cat_so} ? $opts->{cat_so} : '';
    if ($opts->{cat_sb} =~ /\b(?:asc|desc)\b/i) {
        $opts->{cat_so} = '';
    }

# Figure out the template set to use.
    $category->{Category_Template} ||= $cat_db->template_set($category->{ID});

# Get our output vars.
    my %tplvars = (
        %$category,
        category_id   => $category->{ID},
        category_name => $category->{Full_Name},
        header        => $category->{Header},
        footer        => $category->{Footer},
        meta_name     => $category->{Meta_Description},
        meta_keywords => $category->{Meta_Keywords},
        description   => $category->{Description},
        random        => int rand 10000,
        random1       => int rand 10000,
        random2       => int rand 10000,
        random3       => int rand 10000
    );

# Clean up the name.
    my $clean_name = $cat_db->as_url($category->{Full_Name});
    my $build_title = $category->{Full_Name};
    $build_title .= '/' . Links::language('LINKS_PAGE', $opts->{nh}) if $opts->{nh} and $opts->{nh} > 1;

    $tplvars{title_loop}   = Links::Build::build('title', $build_title);
    $tplvars{title_linked} = sub { Links::Build::build('title_linked', $build_title) };
    $tplvars{title}        = sub { Links::Build::build('title_unlinked', $build_title) };

    $tplvars{category_name_escaped} = GT::CGI->escape($category->{Full_Name});
    $tplvars{category_clean}   = $tplvars{title};
    ($tplvars{category_short}) = $tplvars{category_name} =~ m|([^/]+)$|;

    # CUSTOMIZED: show all links in a category as well as subcategories
    my $categories = $cat_db->children($category->{ID});
    push @$categories, $category->{ID};

    my $cond = GT::SQL::Condition->new(
        CategoryID  => '=' => $categories,
        isValidated => '=' => 'Yes'
    );

# "Optional" payment categories are a hassle, as we have to do two selects,
# then balance out the mh/nh variables between the two.
    my ($optional_sth, $sth);
    my @select_options;
    push @select_options, "ORDER BY $opts->{sb} $opts->{so}" if $opts->{sb};

# Load payment info if payment is enabled. Change sort order by paid links
# first then free links if payment for this category is optional. If payment
# is required, we need to remove unpaid links
    if ($CFG->{payment}->{enabled}) {
        require Links::Payment;
        my $payment_info = Links::Payment::cat_payment_info($opts->{id});

        if ($payment_info->{mode} == OPTIONAL and $CFG->{build_sort_paid_first}) {
            my $paycond = GT::SQL::Condition->new($cond);
            $paycond->add(ExpiryDate => '>=' => time, ExpiryDate => '<=' => UNLIMITED);

            my $offset = ($opts->{nh} - 1) * $opts->{mh};
            $catlink_db->select_options(@select_options);
            $catlink_db->select_options("LIMIT $opts->{mh} OFFSET $offset");
            $optional_sth = $catlink_db->select('Links.*', $paycond);

            $cond->add(ExpiryDate => '=' => FREE);
        }
        else {
            # 1) This is an else (instead of elsif ($payment_info->{mode} == REQUIRED)) because the
            # run-time count updating code cannot efficiently take category settings into account
            # as doing so requires either subselects (which older MySQL doesn't support), or a fair
            # bit of Perl code; a single fast count to determine whether the check is necessary
            # won't work.  The end result is that counts would be off.
            # 2) Even if this was an elsif, we can't include ExpiryDate <= UNLIMITED (to exclude
            # free links) because links being free is the default for imported, upgraded, and
            # admin-added links, which we don't want to exclude from REQUIRED categories.
            $cond->add(ExpiryDate => '>=' => time);
        }
    }

    my @results;
    my ($paid_hits, $paid_rows, $offset, $max_hits) = (0, 0, ($opts->{nh} - 1) * $opts->{mh}, $opts->{mh});
    if ($optional_sth) {
        push @results, @{$optional_sth->fetchall_hashref};
        $paid_rows = $optional_sth->rows;
        $paid_hits = $catlink_db->hits;
        if ($paid_rows == $opts->{mh}) {
            $offset = $max_hits = 0;
        }
        elsif ($paid_rows > 0) {
            $offset = 0;
            $max_hits = $opts->{mh} - $paid_rows;
        }
        else {
            $offset -= $paid_hits;
        }
    }
    my $hits;
# Select links from required categories, not-accepted categories, and optional
# categories whose paid hits haven't filled the page
    if ($max_hits) { # $max_hits will be 0 when mh paid links are already listed
        $catlink_db->select_options(@select_options);
        $catlink_db->select_options("LIMIT $max_hits OFFSET $offset");
        my @ids = map $_->[0], @{$catlink_db->select('DISTINCT LinkID', $cond)->fetchall_arrayref};

        $link_db->select_options(@select_options);
        my $sth = $link_db->select({ ID => \@ids });

        push @results, @{$sth->fetchall_hashref};
        $hits = $catlink_db->hits;
    }
    else {
        $hits = $catlink_db->count($cond);
    }

    my $numlinks = $tplvars{total} = $hits + $paid_hits;
    $tplvars{total_optional_paid} = $paid_hits;

# Get the links.
    $link_db->add_reviews(\@results);
    my @links_loop = map Links::SiteHTML::tags('link', $_, undef) => @results;
    $tplvars{links_loop} = \@links_loop;
    $tplvars{links_count} = @links_loop;
    my $links;
    $tplvars{links} = sub {
        return $links if defined $links;
        $links = '';
        for my $link (@results) {
            $link->{Category_Template} = $category->{Category_Template} if $category->{Category_Template};
            $links .= Links::SiteHTML::display('link', $link);
        }
        return $links;
    };
# Get the subcategories and related categories as either Yahoo style (integrated) or
# separated into two outputs..
    my @cat_loop;
    $tplvars{category_loop} = \@cat_loop;
    if ($CFG->{build_category_yahoo}) {
        my @subcat_ids = $cat_db->select(ID => { FatherID => $category->{ID} })->fetchall_list;
        my %related_ids = $related_db->select(qw/RelatedID RelationName/ => { CategoryID => $category->{ID} })->fetchall_list;
        if (@subcat_ids or keys %related_ids) {
            $cat_db->select_options("ORDER BY $opts->{cat_sb} $opts->{cat_so}") if $opts->{cat_sb};
            my $sth = $cat_db->select({ ID => [@subcat_ids, keys %related_ids] });
            my @rel_loop;
            while (my $cat = $sth->fetchrow_hashref) {
                $cat->{URL} = "$CFG->{build_root_url}/" . $cat_db->as_url($cat->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
                $cat->{RelationName} = '';
                if (exists $related_ids{$cat->{ID}}) {
                    $cat->{Related} = 1;
                    $cat->{RelationName} = $related_ids{$cat->{ID}};
# Relations with a custom name need to be re-sorted
                    if ($cat->{RelationName}) {
                        push @rel_loop, $cat;
                        next;
                    }
                }
                push @cat_loop, $cat;
            }
# Re-sort related categories using their RelationName rather than the related
# category's name
            RELATION: while (my $cat = pop @rel_loop) {
                for (my $i = 0; $i < @cat_loop; $i++) {
                    my $name = $cat_loop[$i]->{RelationName} ? $cat_loop[$i]->{RelationName} : $cat_loop[$i]->{Name};
                    if (lc $cat->{RelationName} lt lc $name) {
                        splice @cat_loop, $i, 0, $cat;
                        next RELATION;
                    }
                }
                push @cat_loop, $cat;
            }
            my $print_cat;
            $tplvars{category} = sub {
                return $print_cat if defined $print_cat;
                return $print_cat = Links::SiteHTML::display('print_cat', [$category, @cat_loop]);
            };
        }
        else {
            $tplvars{category} = '';
        }
    }
    else {
# Separate the output.
        $cat_db->select_options("ORDER BY $opts->{cat_sb} $opts->{cat_so}") if $opts->{cat_sb};
        $sth = $cat_db->select({ FatherID => $category->{ID} });
        while (my $cat = $sth->fetchrow_hashref) {
            $cat->{URL} = "$CFG->{build_root_url}/" . $cat_db->as_url($cat->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
            push @cat_loop, $cat;
        }
        if (@cat_loop) {
            my $print_cat;
            $tplvars{category} = sub {
                return $print_cat if defined $print_cat;
                return $print_cat = Links::SiteHTML::display('print_cat', [$category, @cat_loop]);
            };
        }
        else {
            $tplvars{category} = '';
        }
        $tplvars{related}  = '';
        $tplvars{related_loop} = [];

        my %related_ids = $related_db->select(qw/RelatedID RelationName/ => { CategoryID => $category->{ID} })->fetchall_list;
        if (keys %related_ids) {
            $cat_db->select_options("ORDER BY $opts->{cat_sb} $opts->{cat_so}") if $opts->{cat_sb};
            my $sth = $cat_db->select({ ID => [keys %related_ids] });
            while (my $cat = $sth->fetchrow_hashref) {
                my $url = $CFG->{build_root_url} . "/" . $cat_db->as_url($cat->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
                $cat->{URL} = $url;
                $cat->{RelationName} = $related_ids{$cat->{ID}};
                push @{$tplvars{related_loop}}, $cat;
                $tplvars{related} .= qq|<li><a href="$url">| . ($related_ids{$cat->{ID}} || $cat->{Full_Name}) . "</a></li>";
            }
        }
    }

    # Plugins can use the build_category_loop hook to change the category
    # results before they are returned to the template.
    $PLG->dispatch(build_category_loop => sub { } => \@cat_loop);

# Get the header and footer from file if it exists, otherwise assume it is html.
    if ($tplvars{header} and $tplvars{header} =~ /^\S{1,20}$/ and -e "$CFG->{admin_root_path}/headers/$tplvars{header}") {
        local (@ARGV, $/) = "$CFG->{admin_root_path}/headers/$tplvars{header}";
        $tplvars{header} = <>;
    }
    if ($tplvars{footer} and $tplvars{footer} =~ /^\S{1,20}$/ and -e "$CFG->{admin_root_path}/footers/$tplvars{footer}") {
        local (@ARGV, $/) = "$CFG->{admin_root_path}/footers/$tplvars{footer}";
        $tplvars{footer} = <>;
    }

# If we are spanning pages, figure out toolbars and such.
    if ($CFG->{build_span_pages}) {
        my $lpp = $CFG->{build_links_per_page};
        my $nh  = $opts->{nh};
        my $url = $CFG->{build_root_url}  . "/" . $clean_name;
        $tplvars{next} = $tplvars{prev} = "";
        if ($numlinks > ($nh * $lpp)) {
            $tplvars{next} = "$url/$CFG->{build_more}" . ($nh + 1) . "$CFG->{build_extension}";
        }
        if ($nh == 2) {
            $tplvars{prev} = "$url/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
        }
        elsif ($nh > 2) {
            $tplvars{prev} = "$url/$CFG->{build_more}" . ($nh - 1) . "$CFG->{build_extension}";
        }
        if ($tplvars{next} or $tplvars{prev}) {
            $tplvars{next_span} = Links::Build::build('toolbar', { url => $url, numlinks => $numlinks, nh => $nh });
            $tplvars{paging} = {
                page         => "$clean_name/",
                page_format  => 1,
                num_hits     => $numlinks,
                max_hits     => $opts->{mh},
                current_page => $opts->{nh}
            };
        }
    }

    return Links::SiteHTML::display('category', \%tplvars);
}

sub fetch_widget_external_links {
    my $id = shift || return;

    return $DB->table('WidgetLinks')->select({ WidgetID => $id })->fetchall_hashref;
}
1;
