# ================================================================== # 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: Build.pm,v 1.136 2008/06/25 19:23:48 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::Build; # ================================================================== use strict; use Links qw/:objects :payment/; use Links::SiteHTML; use GT::AutoLoader; use vars qw/$GRAND_TOTAL/; sub build { # ----------------------------------------------------------------- # Returns a specified template parsed. # my $build = shift; my $code = exists $Links::Build::{"build_$build"} ? *{$Links::Build::{"build_$build"}}{CODE} : _compile("build_$build"); defined $code or die "Invalid method: build_$build called."; $PLG->dispatch("build_$build", $code, @_); } $COMPILE{build_home} = __LINE__ . <<'END_OF_SUB'; sub build_home { # ------------------------------------------------------------------ # Generate the home page. # $GRAND_TOTAL ||= _grand_total(); my $category = $DB->table('Category'); $category->select_options("ORDER BY $CFG->{build_category_sort}") if $CFG->{build_category_sort}; my $sth = $category->select({ FatherID => 0 }); my $root = []; while (my $cat = $sth->fetchrow_hashref) { $cat->{URL} = "$CFG->{build_root_url}/" . $category->as_url($cat->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : ''); push @$root, $cat; } # 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 { } => $root); my $print_cat; my $cat_list = sub { return $print_cat if defined $print_cat; return $print_cat = Links::SiteHTML::display('print_cat', [{}, @$root]) }; return Links::SiteHTML::display('home', { category => $cat_list, category_loop => $root, grand_total => $GRAND_TOTAL }); } END_OF_SUB $COMPILE{build_new} = __LINE__ . <<'END_OF_SUB'; sub build_new { # ------------------------------------------------------------------ # Generate the what's new page. Takes as options: # # mh => new links per page # nh => page number # sb => field to sort by # so => ascending or descending order # gb => 1/0 (group by category name). # my $opts = shift; if (ref $opts ne 'HASH') { Links::debug("Invalid option passed to build_new: $opts") if $Links::DEBUG; return; } if (! exists $opts->{sb}) { my $sort = $CFG->{build_sort_order_new} || ''; $opts->{sb} = $sort; $opts->{so} = ''; } $opts->{mh} = exists $opts->{mh} ? $opts->{mh} : 1000; $opts->{nh} = exists $opts->{nh} ? $opts->{nh} : 1; $opts->{sb} = exists $opts->{sb} ? $opts->{sb} : 'Add_Date'; $opts->{gb} = exists $opts->{gb} ? $opts->{gb} : $CFG->{build_new_gb}; my $cat_db = $DB->table('Category'); my $link_db = $DB->table('Links'); $GRAND_TOTAL ||= _grand_total(); my %filter = ( isValidated => 'Yes', isNew => 'Yes', mh => $opts->{mh}, nh => $opts->{nh}, sb => $opts->{sb}, so => $opts->{so}, ww => 1 ); # Should not select unpaid and expired links if payment is enabled $filter{ExpiryDate} = '>=' . time if $CFG->{payment}->{enabled}; my $sth = $link_db->query_sth(\%filter); my $total = $link_db->hits; my $cur_date = ''; Links::init_date(); my (@date_order, %grouped_output); # Get the links. my $results = $sth->fetchall_hashref; $link_db->add_reviews($results); # Get the category names. my $catlink = $DB->table('CatLinks','Category'); my %names = $catlink->select('LinkID', 'Full_Name', { LinkID => [map $_->{ID}, @$results] })->fetchall_list; for my $link (@$results) { my $date = $link->{Add_Date}; my $long_date = GT::Date::date_transform($link->{Add_Date}, GT::Date::FORMAT_DATE, $CFG->{date_long_format}); $date ne $cur_date and push @date_order, $long_date; if ($opts->{gb}) { push @{$grouped_output{$long_date}->{$names{$link->{ID}}}}, Links::SiteHTML::tags('link', $link); } else { push @{$grouped_output{$long_date}->{'none'}}, Links::SiteHTML::tags('link', $link); } $cur_date = $date; } my @dates; foreach my $date (@date_order) { my @links; if ($opts->{gb}) { foreach my $cat (sort keys %{$grouped_output{$date}}) { $grouped_output{$date}->{$cat}->[0]->{title_linked} = sub { build('title_linked', { name => $cat, complete => 1 }) }; $grouped_output{$date}->{$cat}->[0]->{title_loop} = build('title', $cat); push @links, @{$grouped_output{$date}->{$cat}}; } } else { push @links, @{$grouped_output{$date}->{'none'}}; } push @dates, { new_date => $date, links => \@links }; } my $ret; my $output = sub { return $ret if defined $ret; $ret = ''; for my $date (@date_order) { $ret .= "

" if $ret; $ret .= "

$date

"; my @links; if ($opts->{gb}) { for my $cat (sort keys %{$grouped_output{$date}}) { my $title = build('title_linked', { name => $cat, complete => 1 }); $ret .= $title; $ret .= join("", map { Links::SiteHTML::display('link', $_) } @{$grouped_output{$date}->{$cat}}); } } else { $ret .= join("", map { Links::SiteHTML::display('link', $_) } @{$grouped_output{$date}->{'none'}}); } } $ret .= "

"; return $ret; }; my $new = Links::language('LINKS_NEW'); return Links::SiteHTML::display(new => { total => $total, grand_total => $GRAND_TOTAL, link_results => $output, link_results_loop => $results, main_title_loop => build('title', $new), title => sub { build('title_unlinked', $new) }, main_title_linked => sub { build('title_linked', $new) }, title_linked_loop => \@dates }); } END_OF_SUB $COMPILE{build_new_index} = __LINE__ . <<'END_OF_SUB'; sub build_new_index { # ------------------------------------------------------------------ # Build a what's new index page grouped by long date. # Links::init_date(); require GT::SQL::Condition; my $cond = GT::SQL::Condition->new( isNew => '=' => 'Yes', VIEWABLE ); my $link_db = $DB->table('Links'); $link_db->select_options("GROUP BY Add_Date", "ORDER BY Add_Date DESC"); my $sth = $link_db->select(Add_Date => 'COUNT(*)' => $cond); my $total = 0; $GRAND_TOTAL ||= _grand_total(); my $results = ''; my @loop; while (my ($date, $count) = $sth->fetchrow_array) { my $long_date = GT::Date::date_transform($date, GT::Date::FORMAT_DATE, $CFG->{date_long_format}); $date =~ s/\s(.*)//; $results .= qq|
  • $long_date ($count)
  • |; $total += $count; push @loop, { date => $long_date, url => "$CFG->{build_new_url}/$date$CFG->{build_extension}", count => $count }; } my $new = Links::language('LINKS_NEW'); return Links::SiteHTML::display(new => { total => $total, grand_total => $GRAND_TOTAL, link_results => $results, link_results_loop => \@loop, main_title_loop => build('title', $new), title => sub { build(title_unlinked => $new) }, main_title_linked => sub { build(title_linked => $new) }, new_index => 1 }); } END_OF_SUB $COMPILE{build_new_subpage} = __LINE__ . <<'END_OF_SUB'; sub build_new_subpage { # ----------------------------------------------------------------------------- # Generate a what's new sub page. Takes a hash reference containing a date key # and value. # Links::init_date(); my $opts = shift; if (ref $opts ne 'HASH') { Links::debug("Invalid argument to build_new_subpage: $opts") if $Links::DEBUG; return; } my $date = $opts->{date}; if (!GT::Date::date_is_valid($date)) { Links::debug("Invalid date passed to build_new_subpage: $date") if $Links::DEBUG; return; } my $link_db = $DB->table('Links'); my $cat_db = $DB->table('Category'); my $mh = $opts->{mh}; my $nh = $opts->{nh}; $link_db->select_options("ORDER BY $CFG->{build_sort_order_new}") if $CFG->{build_sort_order_new}; if ($mh and $nh) { my $offset = ($nh-1) * $mh; $link_db->select_options("LIMIT $offset, $mh"); } my $sth = $link_db->select({ isNew => 'Yes', Add_Date => $date }, VIEWABLE); my $new_total = $link_db->hits; my $long_date = GT::Date::date_transform($date, GT::Date::FORMAT_DATE, $CFG->{date_long_format}); my $total = $link_db->hits; my $gb = $CFG->{build_new_gb}; $GRAND_TOTAL ||= _grand_total(); my $output = {}; my $out = ''; # Get the links. my $results = $sth->fetchall_hashref; $link_db->add_reviews($results); my @link_results_loop; @link_results_loop = map Links::SiteHTML::tags('link', $_) => @$results unless $gb; # Get the category names. my $catlink = $DB->table('CatLinks','Category'); my %names = $catlink->select('LinkID', 'Full_Name', { LinkID => [map $_->{ID}, @$results] })->fetchall_list; if ($gb) { for my $link (@$results) { push @{$output->{$names{$link->{ID}}}}, Links::SiteHTML::tags('link', $link); } } my ($toolbar, %paging); if ($mh and ($total > $mh)) { my $first_url = $CFG->{build_new_url} . "/" . $date . $CFG->{build_extension}; my $next_url = $CFG->{build_new_url} . "/" . $date . "_"; $toolbar = build('toolbar', { first_url => $first_url, next_url => $next_url, numlinks => $new_total, nh => $nh, mh => $mh }); my ($new_url) = $CFG->{build_new_url} =~ m|^\Q$CFG->{build_root_url}\E/(.+)|; %paging = ( page => "$new_url/$date", page_format => 2, num_hits => $new_total, max_hits => $mh, current_page => $nh ); } if ($gb) { foreach my $name (sort { lc $a cmp lc $b } keys %$output) { $output->{$name}->[0]->{title_linked} = sub { build('title_linked', { name => $name, complete => 1 }) }; $output->{$name}->[0]->{title_loop} = build('title', $name); push @link_results_loop, @{$output->{$name}}; } } my $links; $out = sub { return $links if defined $links; $gb or return $links = join "", map { Links::SiteHTML::display(link => $_) } @$results; for my $name (sort { lc $a cmp lc $b } keys %$output) { my $title = build(title_linked => { name => $name, complete => 1 }); $links .= "

    $title" . join "", map { Links::SiteHTML::display(link => $_) } @{$output->{$name}}; } return $links; }; my $new = Links::language('LINKS_NEW'); return Links::SiteHTML::display(new => { total => $total, grand_total => $GRAND_TOTAL, link_results => $out, link_results_loop => \@link_results_loop, main_title_loop => build('title', "$new/$long_date"), title => sub { build('title_unlinked', "$new/$long_date") }, main_title_linked => sub { build('title_linked', "$new/$long_date") }, next_span => $toolbar, paging => \%paging }); } END_OF_SUB $COMPILE{build_cool} = __LINE__ . <<'END_OF_SUB'; sub build_cool { # ------------------------------------------------------------------ # Generate the what's cool page. Takes as options: # # mh => new links per page # nh => page number # sb => field to sort by # so => ascending or descending order # gb => 1/0 (group by category name). # my $opts = shift; if (ref $opts ne 'HASH') { Links::debug("Invalid option passed to build_cool: $opts") if $Links::DEBUG; return; } if (! exists $opts->{sb}) { $opts->{sb} = $CFG->{build_sort_order_cool} || ''; $opts->{so} = ''; } $opts->{mh} ||= $CFG->{build_links_per_page} || 25; $opts->{nh} ||= 1; $opts->{gb} ||= $CFG->{build_cool_gb}; my $cat_db = $DB->table('Category'); my $link_db = $DB->table('Links'); $GRAND_TOTAL ||= _grand_total(); my %filter = ( isPopular => 'Yes', isValidated => 'Yes', mh => $opts->{mh}, nh => $opts->{nh}, sb => $opts->{sb}, so => $opts->{so}, ww => 1 ); # Should not select unpaid and expired links if payment is enabled $filter{ExpiryDate} = '>=' . time if $CFG->{payment}->{enabled}; my $sth = $link_db->query_sth(%filter); my $total = $link_db->hits; my $sub_total = 0; my $output = ''; my %grouped_output; # Get the links. my $results = $sth->fetchall_hashref; $link_db->add_reviews($results); my @link_results_loop; @link_results_loop = map Links::SiteHTML::tags('link', $_) => @$results unless $opts->{gb}; # Get the category names. my $catlink = $DB->table('CatLinks','Category'); my %names = $catlink->select(LinkID => Full_Name => { LinkID => [map $_->{ID}, @$results] })->fetchall_list; for my $link (@$results) { $opts->{gb} and push @{$grouped_output{$names{$link->{ID}}}}, Links::SiteHTML::tags('link', $link); $sub_total++; } if ($opts->{gb}) { foreach my $cat (sort { lc $a cmp lc $b } keys %grouped_output) { $grouped_output{$cat}->[0]->{title_linked} = sub { build(title_linked => { name => $cat, complete => 1 }) }; $grouped_output{$cat}->[0]->{title_loop} = build('title', $cat); push @link_results_loop, @{$grouped_output{$cat}}; } } my $links; $output = sub { return $links if defined $links; $opts->{gb} or return $links = join '', map { Links::SiteHTML::display('link', $_) } @$results; $links = ''; foreach my $cat (sort { lc $a cmp lc $b } keys %grouped_output) { my $title = build('title_linked', { name => $cat, complete => 1 }); $links .= "

    $title" . join '', map { Links::SiteHTML::display(link => $_) } @{$grouped_output{$cat}}; } return $links; }; my $percent = ($CFG->{build_pop_cutoff} < 1) ? $CFG->{build_pop_cutoff} * 100 . "%" : $CFG->{build_pop_cutoff}; my ($toolbar, %paging); if ($total > $opts->{mh}) { $toolbar = build(toolbar => { url => "$CFG->{build_cool_url}/", numlinks => $total, nh => $opts->{nh}, mh => $opts->{mh} }); my ($cool_url) = $CFG->{build_cool_url} =~ m|^\Q$CFG->{build_root_url}\E/(.+)|; %paging = ( page => "$cool_url/", page_format => 1, num_hits => $total, max_hits => $opts->{mh}, current_page => $opts->{nh} ); } my $cool = Links::language('LINKS_COOL'); return Links::SiteHTML::display(cool => { total => $total, grand_total => $GRAND_TOTAL, link_results => $output, main_title_loop => build('title', $cool), title => sub { build(title_unlinked => $cool) }, main_title_linked => sub { build(title_linked => $cool) }, percent => $percent, next_span => $toolbar, paging => \%paging, link_results_loop => \@link_results_loop }); } END_OF_SUB $COMPILE{build_rating} = __LINE__ . <<'END_OF_SUB'; sub build_rating { # ------------------------------------------------------------------ # Generate the rating pages. # my $links = $DB->table('Links'); require GT::SQL::Condition; my $cond = GT::SQL::Condition->new('Votes', '>=', 10, VIEWABLE); $GRAND_TOTAL ||= _grand_total(); $links->select_options("ORDER BY Rating DESC", "LIMIT 10"); my $rated = $links->select($cond); $links->select_options("ORDER BY Votes DESC", "LIMIT 10"); my $voted = $links->select($cond); # Now build the html. my ($top_rated, $top_votes) = ('', ''); my (@top_votes_loop, @top_rated_loop); while (my $link = $voted->fetchrow_hashref) { $link->{detailed_url} = $CFG->{build_detail_url} . '/' . $links->detailed_url($link->{ID}) if $CFG->{build_detailed}; push @top_votes_loop, $link; $top_votes .= qq~$link->{Rating}$link->{Votes}$link->{Title}\n~; } while (my $link = $rated->fetchrow_hashref) { $link->{detailed_url} = $CFG->{build_detail_url} . '/' . $links->detailed_url($link->{ID}) if $CFG->{build_detailed}; push @top_rated_loop, $link; $top_rated .= qq~$link->{Rating}$link->{Votes}$link->{Title}\n~; } # And write it to a file. return Links::SiteHTML::display(rate_top => { top_rated => $top_rated, top_rated_loop => \@top_rated_loop, top_votes_loop => \@top_votes_loop, top_votes => $top_votes, main_title_loop => build('title', Links::language('LINKS_TOPRATED'), $CFG->{build_ratings_url}) }); } END_OF_SUB $COMPILE{build_detailed} = __LINE__ . <<'END_OF_SUB'; sub build_detailed { # ------------------------------------------------------------------ # Builds a single detailed page, takes either a link hash ref, or # a link id. # if (! $CFG->{build_detailed}) { return Links::SiteHTML::display(error => { error => Links::language('BUILD_DETAILED_DISABLED') }); } my $link_db = $DB->table('Links'); $GRAND_TOTAL ||= _grand_total(); my $link = shift; if (ref $link ne 'HASH') { return Links::SiteHTML::display(error => { error => Links::language('BUILD_DETAILED_ARGS', $link) }); } $link->{ID} ||= $IN->param('ID'); if (exists $link->{ID}) { my $id = $link->{ID}; my $link_row = $link_db->get($id, 'HASH'); @$link{keys %$link_row} = values %$link_row; $link_db->add_reviews([ $link ]); if (! $link) { return Links::SiteHTML::display(error => { error => Links::language('BUILD_DETAILED_INVALIDID', $id) }); } elsif ($link->{isValidated} ne 'Yes') { return Links::SiteHTML::display(error => { error => Links::language('BUILD_DETAILED_UNVAL') }); } elsif ($CFG->{payment}->{enabled} and $link->{ExpiryDate} < time) { return Links::SiteHTML::display(error => { error => Links::language('BUILD_DETAILED_EXPIRED') }); } } my ($cat_id, $cat_name); if ($link->{CategoryID} and $link->{Full_Name}) { $cat_id = $link->{CategoryID}; $cat_name = $link->{Full_Name}; } else { ($cat_id, $cat_name) = each %{$link_db->get_categories($link->{ID})}; } # Figure out the next/prev links. my $catlnk_db = $DB->table('Links', 'CatLinks'); $catlnk_db->select_options("ORDER BY $CFG->{build_sort_order_category}") if $CFG->{build_sort_order_category}; my $sth = $catlnk_db->select('Links.ID' => { CategoryID => $cat_id }, VIEWABLE); my ($next, $prev); while (my $id = $sth->fetchrow) { if ($id == $link->{ID}) { $next = $sth->fetchrow; last; } else { $prev = $id; } } my ($next_url, $prev_url) = $DB->table('Links')->category_detailed_url($cat_id, $next, $prev); $link->{Category_Template} = $DB->table('Category')->template_set($cat_id); # build_title generates the title loop by using / as a category delimiter. # Since the link title can have /'s in it, we'll push that entry onto the loop # ourselves. Note that the deprecated title and title_linked variables are # still broken. my $title_loop = build('title', $cat_name); push @$title_loop, { Name => $link->{Title}, URL => $CFG->{build_detail_url} . '/' . $link_db->detailed_url($link->{ID}) }; return Links::SiteHTML::display('detailed', { %$link, title_loop => $title_loop, title => sub { build('title_unlinked', "$cat_name/$link->{Title}") }, title_linked => sub { build('title_linked', "$cat_name/$link->{Title}") }, grand_total => $GRAND_TOTAL, next => $next, prev => $prev, next_url => $next_url ? "$CFG->{build_detail_url}/$next_url" : '', prev_url => $prev_url ? "$CFG->{build_detail_url}/$prev_url" : '' }); } END_OF_SUB $COMPILE{build_category} = __LINE__ . <<'END_OF_SUB'; sub build_category { # ------------------------------------------------------------------ # Build a single category page. Takes as argument a category hash or id # number and an options hash. # 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'); $GRAND_TOTAL ||= _grand_total(); my $opts = shift; if (ref $opts ne 'HASH') { Links::debug("Invalid argument passed to build_category: $opts") if $Links::DEBUG; return; } # Load our category info. my $category; $opts->{id} ||= $IN->param('ID'); 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} = build('title', $build_title); $tplvars{title_linked} = sub { build('title_linked', $build_title) }; $tplvars{title} = sub { 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|([^/]+)$|; # Prepare the condition; don't add the ExpiryDate handling - it gets added later my $cond = GT::SQL::Condition->new( CategoryID => '=' => $category->{ID}, 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 $sth = $catlink_db->select('Links.*' => $cond); 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', $_, $category->{ID}) => @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|

  • | . ($related_ids{$cat->{ID}} || $cat->{Full_Name}) . "
  • "; } } } # 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} = 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); } END_OF_SUB $COMPILE{build_reset_hits} = __LINE__ . <<'END_OF_SUB'; sub build_reset_hits { # ------------------------------------------------------------------ # Remove old hit and rate tracking information. # Links::init_date(); my $delete_by = GT::Date::date_get(time - 2*24*60*60, '%yyyy%-%mm%-%dd%'); my $click = $DB->table('ClickTrack'); $click->delete(GT::SQL::Condition->new('Created', '<', $delete_by)); } END_OF_SUB $COMPILE{build_orphan_check} = __LINE__ . <<'END_OF_SUB'; sub build_orphan_check { # ------------------------------------------------------------------ # Checks for orphaned links and returns a list of ids. # my $opts = shift; my $sel = $opts->{select} || 'ID'; my $rel = $DB->table('Links', 'CatLinks'); my $sth = $rel->select('left_join', $sel, { LinkID => undef }); my @output; if ($opts->{select}) { while (my $link = $sth->fetchrow_hashref) { push @output, $link; } } else { while (my $id = $sth->fetchrow_array) { push @output, $id; } } return @output; } END_OF_SUB $COMPILE{build_catlinks_orphan_check} = __LINE__ . <<'END_OF_SUB'; sub build_catlinks_orphan_check { # ------------------------------------------------------------------ # Checks for orphaned CatLinks and returns a list of CategoryID/LinkIDs. # return (@{$DB->table('CatLinks', 'Links')->select('left_join', 'CategoryID', 'LinkID', { ID => undef })->fetchall_hashref}, @{$DB->table('CatLinks', 'Category')->select('left_join', 'CategoryID', 'LinkID', { ID => undef })->fetchall_hashref}); } END_OF_SUB $COMPILE{build_new_flags} = __LINE__ . <<'END_OF_SUB'; sub build_new_flags { # ------------------------------------------------------------------ # Update the isNew flag based on Add_Date. # Links::init_date(); my $opts = shift || {}; my $date = GT::Date::date_sub(GT::Date::date_get(), $CFG->{build_new_cutoff}); my $links = $DB->table('Links'); $links->indexing(0); my $cond = GT::SQL::Condition->new( isNew => '=' => 'Yes', Add_Date => '<' => $date, VIEWABLE ); # Unset isNew for links which aren't new anymore my $old_rows = $links->count($cond); if ($old_rows) { $links->update({ isNew => 'No' }, $cond, { GT_SQL_SKIP_CHECK => 1 }); } # Find links which need isNew set, but isn't set (the Links table subclass # already update's the Category table's Has_New_Links flag) $cond = GT::SQL::Condition->new( isNew => '=' => 'No', Add_Date => '>=' => $date, VIEWABLE ); my $new_rows = $links->count($cond); if ($new_rows) { $links->update({ isNew => 'Yes' }, $cond, { GT_SQL_SKIP_CHECK => 1 }); } # Update the category flags if ($new_rows or $old_rows or $opts->{reset}) { my $links_cl = $DB->table('Links', 'CatLinks'); my $category = $DB->table('Category'); $category->indexing(0); my %stalelinks = map { $_ => 1 } $category->select('ID', { Has_New_Links => 'Yes' })->fetchall_list; $links_cl->select_options('ORDER BY Add_Date ASC'); my $sth = $links_cl->select('CategoryID', 'Add_Date', { isNew => 'Yes' }, VIEWABLE); my %updated; while (my ($cat_id, $date) = $sth->fetchrow) { next if exists $updated{$cat_id}; my @ids = ($cat_id, @{$category->parents($cat_id)}); $category->update( { Newest_Link => $date, Has_New_Links => 'Yes' }, { ID => \@ids, Has_New_Links => 'No' }, { GT_SQL_SKIP_CHECK => 1 } ); for (@ids) { delete $stalelinks{$_}; $updated{$_}++; } } # Whatever's left in %stalelinks are the categories which don't have new links anymore $category->update({ Has_New_Links => 'No' }, { ID => [keys %stalelinks] }, { GT_SQL_SKIP_CHECK => 1 }); $category->indexing(1); } $links->indexing(1); return $new_rows + $old_rows; } END_OF_SUB $COMPILE{build_changed_flags} = __LINE__ . <<'END_OF_SUB'; sub build_changed_flags { # ------------------------------------------------------------------ # Update isChanged flags, based on Mod_Date. # Links::init_date(); my $opts = shift || {}; my $date = GT::Date::date_sub(GT::Date::date_get(), $CFG->{build_new_cutoff}); my $links = $DB->table('Links'); $links->indexing(0); my $cond = GT::SQL::Condition->new( isChanged => '=' => 'Yes', VIEWABLE, GT::SQL::Condition->new( Mod_Date => '<' => $date, isNew => '=' => 'Yes', 'OR' ) ); # Unset isChanged for links which aren't 'new' anymore my $old_rows = $links->count($cond); if ($old_rows) { $links->update({ isChanged => 'No' }, $cond, { GT_SQL_SKIP_CHECK => 1 }); } # Find links which need isChanged set $cond = GT::SQL::Condition->new( isChanged => '=' => 'No', Mod_Date => '>=' => $date, Add_Date => '<' => $date, VIEWABLE ); my $new_rows = $links->count($cond); if ($new_rows) { $links->update({ isChanged => 'Yes' }, $cond, { GT_SQL_SKIP_CHECK => 1 }); } # Update the category flags if ($new_rows or $old_rows or $opts->{reset}) { my $category = $DB->table('Category'); $category->indexing(0); my %stalelinks = map { $_ => 1 } $category->select('ID', { Has_Changed_Links => 'Yes' })->fetchall_list; my $sth = $DB->table('Links', 'CatLinks')->select('CategoryID', 'Add_Date', { isChanged => 'Yes' }); my %updated; while (my ($cat_id, $date) = $sth->fetchrow_array) { next if exists $updated{$cat_id}; my @ids = ($cat_id, @{$category->parents($cat_id)}); $category->update( { Has_Changed_Links => 'Yes' }, { ID => \@ids, Has_Changed_Links => 'No' }, { GT_SQL_SKIP_CHECK => 1 } ); for (@ids) { delete $stalelinks{$_}; $updated{$_}++; } } # Whatever's left in %stalelinks are the categories which don't have changed links anymore $category->update({ Has_Changed_Links => 'No' }, { ID => [keys %stalelinks] }, { GT_SQL_SKIP_CHECK => 1 }); $category->indexing(1); } $links->indexing(1); return $new_rows + $old_rows; } END_OF_SUB $COMPILE{build_cool_flags} = __LINE__ . <<'END_OF_SUB'; sub build_cool_flags { # ------------------------------------------------------------------ # Update the isPopular flag determined on number of Hits. # # Work out based either on percentage, or top x links. my $link_db = $DB->table('Links'); $GRAND_TOTAL ||= _grand_total(); my $limit = int($CFG->{build_pop_cutoff} > 1 ? $CFG->{build_pop_cutoff} : $CFG->{build_pop_cutoff} * $GRAND_TOTAL) || 1; # Select the popular links and update the flags if neccessary. $link_db->indexing(0); $link_db->select_options("ORDER BY Hits DESC", "LIMIT $limit"); my $sth = $link_db->select( 'ID', 'isPopular', GT::SQL::Condition->new( Hits => '>=' => 2, VIEWABLE ) ); my (@ids, @update_pop); while (my ($id, $ispop) = $sth->fetchrow) { push @ids, $id; push @update_pop, $id unless $ispop eq 'Yes'; } $link_db->update({ isPopular => 'Yes' }, { ID => \@update_pop }, { GT_SQL_SKIP_CHECK => 1 }) if @update_pop; # Change any links that aren't popular back. my $cond = GT::SQL::Condition->new('isPopular', '=', 'Yes'); $cond->add(ID => '!=' => \@ids) if @ids; $link_db->update({ isPopular => 'No' }, $cond); $link_db->indexing(1); } END_OF_SUB $COMPILE{build_title} = __LINE__ . <<'END_OF_SUB'; sub build_title { # ------------------------------------------------------------------ # Generate a title. Unlike build_title_{un,}linked, returns something more # useful: an array ref of hashes of Name and URL. # The 2nd argument is an optional override URL for the last item. # my ($input, $url) = @_; my $cat = $DB->table('Category'); my $top = Links::language('LINKS_TOP'); my @cats = ({ Name => $top, URL => "$CFG->{build_root_url}/" . ($CFG->{build_home} || ($CFG->{build_index_include} ? $CFG->{build_index} : '')) }); my @si = split /\//, $input; for (0 .. $#si) { my $curl; if ($_ == 0 and $si[$_] eq Links::language('LINKS_NEW')) { $curl = "$CFG->{build_new_url}/"; } elsif ($_ == $#si and $url) { $curl = $url; } else { $curl = "$CFG->{build_root_url}/" . $cat->as_url(join "/", @si[0 .. $_]) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : ''); } push @cats, { Name => $si[$_], URL => $curl }; } return \@cats; } END_OF_SUB $COMPILE{build_title_linked} = __LINE__ . <<'END_OF_SUB'; sub build_title_linked { # ------------------------------------------------------------------ # Generate a linked title. # my $input = shift; my $complete = 0; my $home = 1; if (ref $input) { $complete = $input->{complete} || 0; $home = defined $input->{home} ? $input->{home} : 1; $input = $input->{name}; } my $db = $DB->table('Category'); my $top = Links::language('LINKS_TOP'); my @dirs = split /\//, $input; my $last; $last = pop @dirs unless $complete; my @paths; push @paths, qq{$top} if $home; for (0 .. $#dirs) { my $path = "/" . $db->as_url(join "/", @dirs[0 .. $_]); push @paths, qq{$dirs[$_]}; } push @paths, $last unless $complete; return join ': ', @paths; } END_OF_SUB $COMPILE{build_title_unlinked} = __LINE__ . <<'END_OF_SUB'; sub build_title_unlinked { # -------------------------------------------------------- # Returns a string of the current category broken up by section. # Useful for printing in the title. # my $input = shift; my $output = join ': ', split /\//, $input; return $output; } END_OF_SUB $COMPILE{build_toolbar} = __LINE__ . <<'END_OF_SUB'; sub build_toolbar { # -------------------------------------------------------- # Create an Altavista style toolbar for the next and previous pages. # my $opts = shift; my $root_url = $opts->{url}; my $first_url = $opts->{first_url} || "$root_url/" . ($CFG->{build_index_include} ? $CFG->{build_index} : ''); my $next_url = $opts->{next_url} || "$root_url/$CFG->{build_more}"; my $numhits = $opts->{numlinks}; my $nh = $opts->{nh}; my $maxhits = $opts->{mh} || $CFG->{build_links_per_page}; my ($next_hit, $prev_hit) = ($nh + 1, $nh - 1); # First, set how many pages we have on the left and the right. my ($left, $right) = ($nh, int($numhits / $maxhits) - $nh); # Then work out what page number we can go above and below. my $lower = $left > 7 ? $left - 7 : 1; my $upper = $right > 7 ? $nh + 7 : int($numhits/$maxhits) + 1; # Finally, adjust those page numbers if we are near an endpoint. $upper += 8 - $nh if 7 - $nh >= 0; $lower -= $nh - int($numhits/$maxhits - 7) - 1 if $nh > $numhits / $maxhits - 7; my $url = ""; # Then let's go through the pages and build the HTML. $url .= $nh == 2 ? qq~[<<] ~ : qq~[<<] ~ if $nh > 1; for (my $i = 1; $i <= int($numhits/$maxhits) + 1; $i++) { if ($i < $lower) { $url .= " ... "; $i = $lower - 1; next } if ($i > $upper) { $url .= " ... "; last } $url .= $i == $nh ? "$i " : $i == 1 ? qq~$i ~ : qq~$i ~; if ($i * $maxhits == $numhits) { $next_hit = $i if $nh == $i; last } } $url .= qq~[>>] ~ unless $next_hit == $nh or $nh * $maxhits > $numhits; $url; } END_OF_SUB $COMPILE{build_search_toolbar} = __LINE__ . <<'END_OF_SUB'; sub build_search_toolbar { # -------------------------------------------------------- # Create an Altavista style toolbar for the next and previous pages. # my $opts = shift; my $root_url = $opts->{url}; my $numhits = $opts->{numlinks}; my $nh = $opts->{nh}; my $maxhits = $opts->{mh} || $CFG->{build_links_per_page}; my ($next_hit, $prev_hit) = ($nh + 1, $nh - 1); # First, set how many pages we have on the left and the right. my ($left, $right) = ($nh, int($numhits / $maxhits) - $nh); # Then work out what page number we can go above and below. my $lower = $left > 7 ? $left - 7 : 1; my $upper = $right > 7 ? $nh + 7 : int($numhits/$maxhits) + 1; # Finally, adjust those page numbers if we are near an endpoint. $upper += 8 - $nh if 7 - $nh >= 0; $lower -= $nh - int($numhits/$maxhits - 7) - 1 if $nh > $numhits / $maxhits - 7; my $url = ""; # Then let's go through the pages and build the HTML. $url .= qq~[<<] ~ if $nh > 1; for (my $i = 1; $i <= int($numhits/$maxhits) + ($numhits % $maxhits ? 1 : 0); $i++) { if ($i < $lower) { $url .= " ... "; $i = $lower - 1; next } if ($i > $upper) { $url .= " ... "; last } $url .= $i == $nh ? "$i " : qq~$i ~; if ($i * $maxhits == $numhits) { $next_hit = $i if $nh == $i; last } } $url .= qq~[>>] ~ unless $next_hit == $nh or $nh * $maxhits > $numhits; $url; } END_OF_SUB sub _compile { # ------------------------------------------------------------------- # Compile will check %COMPILE for the subroutine, and if it exists compile # it (with an eval). Returns a code reference. # my $sub = shift; if (exists $COMPILE{$sub}) { GT::AutoLoader::_compile(\%COMPILE, __PACKAGE__, $sub) if $COMPILE{$sub}; return *{$Links::Build::{$sub}}{CODE}; } else { die "Invalid method: $sub"; } } sub _grand_total { # ------------------------------------------------------------------- # Calculates the total in three queries as it can be significantly faster # on large db's. # # Create a category object just to make sure the db maintenance code has run: $DB->table('Category'); my $total = $DB->table('CatLinks')->count - $DB->table('Links')->count({ isValidated => 'No' }); if ($CFG->{payment}->{enabled}) { $total -= $DB->table('Links')->count( GT::SQL::Condition->new( ExpiryDate => '<' => time, isValidated => '=' => 'Yes' ) ); } return $total; } 1;