First pass at adding key files

This commit is contained in:
dsainty
2024-06-17 21:49:12 +10:00
commit aa25e9347f
1274 changed files with 392549 additions and 0 deletions

View File

@ -0,0 +1,437 @@
# ==================================================================
# 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: Admin.pm,v 1.16 2005/03/05 01:29:09 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::Admin;
# ==================================================================
use strict;
use GT::SQL::Admin;
use Links qw/$DB/;
use GT::AutoLoader;
use vars qw/@ISA $ERROR_MESSAGE $FONT $DEBUG/;
@ISA = qw/GT::SQL::Admin/;
$DEBUG = 0;
$ERROR_MESSAGE = 'GT::SQL';
$FONT = $GT::SQL::Admin::FONT;
# Make sure AUTOLOAD does not catch destroyed objects.
sub DESTROY {}
$COMPILE{modify_multi_records} = __LINE__ . <<'END_OF_SUB';
sub modify_multi_records {
# -------------------------------------------------------------------
# Overrides the Links table to format the category name properly.
#
my $self = shift;
my $name = $self->{table}->name;
my $prefix = $DB->prefix;
return $self->SUPER::modify_multi_records(@_) unless ( $name eq $prefix . 'Links');
if (! exists $self->{cgi}->{modify}) {
return $self->modify_error("Please select a record to modify before continuing.");
}
# If they selected only one record to modify we still need an array ref
ref $self->{cgi}->{modify} eq 'ARRAY' or $self->{cgi}->{modify} = [$self->{cgi}->{modify}];
# Format the cgi for inserting
$self->format_insert_cgi;
# Hash to handle errors if there are any errors.
my $errors = {};
my $errcode = {};
# Need to know the names of the columns for this Table.
my @columns = keys %{$self->{table}->cols};
push @columns, 'CatLinks.CategoryID';
# Need to know the number of records modified
my $rec_modified = 0;
# For through the record numbers. These are the values of the
# check boxes
foreach my $rec_num (@{$self->{cgi}->{modify}}) {
my $change = {};
foreach my $column (@columns) {
$change->{$column} = $self->{cgi}->{"$rec_num-$column"} if exists $self->{cgi}->{"$rec_num-$column"};
}
# Make the changes and capture any errors.
my $ret = $self->{table}->modify($change);
if (defined $ret) {
$rec_modified++;
}
else {
if ($self->{table}->error) {
my $error = $self->{table}->error;
$error =~ s/\n/<br>\n<li>/g;
$errors->{$rec_num} = "<li>$error";
}
$errcode->{$rec_num} = $GT::SQL::errcode if ($GT::SQL::errcode);
}
}
# Return the results page with the proper arguments depending on if we got an error or not.
return (keys %$errors) ? $self->modify_multi_results($rec_modified, $errors, $errcode) : $self->modify_multi_results($rec_modified);
}
END_OF_SUB
$COMPILE{editor_import_data_form} = __LINE__ . <<'END_OF_SUB';
sub editor_import_data_form {
# -------------------------------------------------------------------
# Allow the import to import category/link data. Only used if called with the
# Links database.
#
my $self = shift;
my $name = $self->{table}->name;
my $prefix = $DB->prefix;
return $self->SUPER::editor_import_data_form(@_) unless ( $name eq $prefix . 'Links');
my $msg = shift;
print $self->{in}->header;
$msg &&= qq|<FONT COLOR="red"><B>$msg</B></FONT>|;
my $table = $self->{record};
print $self->_start_html ( { title => "Links Table Editor: $table" });
print $self->_header ("Links Table Editor", $msg || "Import Data to $table.");
print $self->_start_form ( { do => 'editor_import_data', db => $self->{cgi}->{db} }, { name => 'ImportForm'});
print qq~
<table border=0 width=500><tr><td>
<p><font $FONT>You can either import from a file or you can cut and paste the contents into a textarea box. If you
have a large number of records, you should really import from a file. The first row of your input should be the
fully qualified column names. You must also include the Category ID and Category Name of the category the link
will be imported to.<br>
&nbsp;
</td></tr></table>
<br>
<table border=0 width=500><tr><td>
<p><font $FONT>
Import data from file: <input type=text name="import-file" size=10> or from textarea box:<br>
<textarea name="import-text" rows=3 cols=40></textarea><br>
Use <input type=text name="import-delim" value="|" size=1> as delimiter.
<input type=checkbox name="import-delete" value=1> Delete old data first
<br>&nbsp;
</tr></td></table>
~;
print $self->_buttons ("Import Data into");
print "<P>";
print $self->_end_form;
print $self->_prop_navbar;
print "<P>";
print $self->_footer;
print $self->_end_html;
}
END_OF_SUB
$COMPILE{editor_import_data} = __LINE__ . <<'END_OF_SUB';
sub editor_import_data {
# -------------------------------------------------------------------
# Allow the import to import category/link data. Only used if called with the
# Links database.
#
my $self = shift;
my $name = $self->{table}->name;
my $prefix = $DB->prefix;
return $self->SUPER::editor_import_data(@_) unless ( $name eq $prefix . 'Links');
my $delim = $self->{cgi}->{'import-delim'} or return $self->editor_import_data_form ("No import delimiter specified!");
my $file = $self->{cgi}->{'import-file'};
my $text = $self->{cgi}->{'import-text'};
# Make sure there is some data to import
$file or $text or return $self->editor_import_data_form ("You must enter in at least a filename or data in the textarea box.");
$file and $text and return $self->editor_import_data_form ("Please only enter either a filename or data in the textarea box, not both.");
$delim =~ s/\\t/\t/g;
$delim =~ /%/ and $self->editor_import_data_form("% may not be used as a delimited.");
# Store the lines to import in @lines and the header in $header.
my ($good_cnt, $err_cnt, $line, $line_num, @lines, @data, $error, %record, $i);
if ($file) {
local *FILE;
open (FILE, "<$file") or return $self->editor_import_data_form ("Unable to open file: '$file'. Reason: $!");
local $/;
@lines = split /[\r\n]+/, scalar <FILE>;
close FILE;
}
else {
@lines = split /[\r\n]+/, $text;
}
# Fetch the header.
my @header = split /\Q$delim\E/, shift @lines;
unless (grep { $_ eq ($prefix . 'Category.ID')} @header) {
return $self->editor_import_data_form ("Unable to find Category ID column in header!");
}
# Remove Links table prefix.
my $full_name = $prefix . 'Links';
@header = map { s/\Q$full_name\E\.//; $_; } @header;
my $Links = $DB->table('Links');
my $CatLinks = $DB->table('CatLinks');
my $Category = $DB->table('Category');
# Remove old data if requested.
my $delete = $self->{cgi}->{'import-delete'};
if ($delete) {
$Links->delete_all or die $GT::SQL::error;
$CatLinks->delete_all or die $GT::SQL::error;
}
# Do the import.
$good_cnt = $err_cnt = 0;
my %link_map;
LINE: for my $line_num (0 .. $#lines) {
($err_cnt > 10) and last LINE;
$line = $lines[$line_num];
@data = split /\Q$delim/, $line, -1;
if ($#data != $#header) {
$error .= "<li>" . ($line_num+2) . ": Row count: " . ($#data+1) .
" does not match header count: (@data) (@header)" . ($#header+1) . "\n";
$err_cnt++;
next LINE;
}
for (@data) {
s/%([0-9a-fA-F]{2})/chr hex $1/eg;
}
$i = 0;
%record = map { $data[$i] =~ s,^"|"$,,g; $header[$i] => $data[$i++] } @data;
my $cat_id = delete $record{$prefix . 'Category.ID'};
my $cat_name = delete $record{$prefix . 'Category.Name'};
unless ($Category->count({ ID => $cat_id })) {
$cat_id = $Category->insert({ Name => $cat_name })->insert_id;
}
my $link_id = delete $record{ID};
if ($link_id and $link_map{$link_id}) {
$link_id = $link_map{$link_id};
}
else {
my $count = $Links->count({ ID => $link_id });
if ($link_id and $count) {
if ($count) {
unless ($Links->update(\%record, { ID => $link_id })) {
$error .= "<li>" . ($line_num+2) . ": Failed to update record. Error <ul>$GT::SQL::error</ul>\n";
$err_cnt++;
next LINE;
}
$link_map{$link_id} = $link_id;
}
else {
my $old_id = $link_id;
my $sth = $Links->insert(\%record);
unless ($sth and ($link_id = $sth->insert_id)) {
$error .= "<li>" . ($line_num+2) . ": Failed to add new record. Error <ul>$GT::SQL::error</ul>\n";
$err_cnt++;
next LINE;
}
$link_map{$old_id} = $link_id;
}
}
else {
my $old_id = $link_id;
my $sth = $Links->insert(\%record);
unless ($sth and ($link_id = $sth->insert_id)) {
$error .= "<li>" . ($line_num+2) . ": Failed to add new record. Error <ul>$GT::SQL::error</ul>\n";
$err_cnt++;
next LINE;
}
$link_map{$old_id} = $link_id;
}
}
unless ($CatLinks->count ( { LinkID => $link_id, CategoryID => $cat_id })) {
$CatLinks->insert({ LinkID => $link_id, CategoryID => $cat_id });
}
$good_cnt++;
last if ($err_cnt > 100);
}
# Return the results.
if ($error) {
return $self->editor_import_data_form (($err_cnt >= 100) ?
"Aborting, too many errors!<br><br>Rows imported: $good_cnt<br>Errors with the following rows:
<font size=-1><ul>$error</ul></font><br>" :
"Rows imported: $good_cnt<br>Errors with the following rows: <font size=-1><ul>$error</ul></font><br>");
}
return $self->editor_import_data_form ("Rows imported: $good_cnt.");
}
END_OF_SUB
$COMPILE{editor_export_data_form} = __LINE__ . <<'END_OF_SUB';
sub editor_export_data_form {
# -------------------------------------------------------------------
# Allow the export to export category/link data. Only used if called with the
# Links database.
#
my $self = shift;
my $name = $self->{table}->name;
my $prefix = $DB->prefix;
return $self->SUPER::editor_export_data_form(@_) unless ( $name eq $prefix . 'Links');
my $msg = shift;
print $self->{in}->header;
$msg &&= qq|<FONT COLOR="red"><B>$msg</B></FONT>|;
my $table = $self->{record};
print $self->_start_html ( { title => "Table Editor: $table" });
print $self->_header ("Table Editor", $msg || "Export Data from $table.");
print $self->_start_form ( { do => 'editor_export_data', db => $self->{cgi}->{db} }, {name => 'ExportForm'});
print qq~
<table border=0 width=500><tr><td>
<p><font $FONT>From here you can export your Links to either the screen or to
a file on your server. The first line of the export will be a list of the column
headers. The last two fields is the Category ID the link is in, and the Category Name.
If a link is in more then one category, you will get one row for each occurrence.
</font>
</td></tr></table>
<br>
<table border=0 width=500><tr><td><font $FONT>
Export data to: <select name="export-mode"><option>file<option>screen</select>
filename: <input type=text name="export-file" size=10><br>
Use <input type=text name="export-delim" value="|" size=1> as delimiter.
</font>
</td></tr></table>
<br>
~;
print $self->_buttons ("Export Data from");
print "<P>";
print $self->_end_form;
print $self->_prop_navbar;
print "<P>";
print $self->_footer;
print $self->_end_html;
}
END_OF_SUB
$COMPILE{editor_export_data} = __LINE__ . <<'END_OF_SUB';
sub editor_export_data {
# -------------------------------------------------------------------
# Allow the export to export category/link data. Only used if called with the
# Links database.
#
my $self = shift;
my $name = $self->{table}->name;
my $prefix = $DB->prefix;
return $self->SUPER::editor_export_data(@_) unless ( $name eq $prefix . 'Links');
print $self->{in}->header;
ref $self->{cgi}->{db} and return $self->error('BADARGS','FATAL', "Editor can only be called with one table, not a relation.");
my @order = $self->{table}->ordered_columns;
@order or return $self->editor_export_data_form("No fields selected to export (@order).");
# Add on the prefix.
for (@order) { $_ = $prefix . 'Links.' . $_; }
# Add the ID and Category Name.
push @order, $prefix . 'Category.ID', $prefix .'Category.Name';
my $delim = $self->{cgi}->{'export-delim'};
$delim = "\t" if $delim eq '\t';
length $delim or $self->editor_export_data_form("No delimiter entered.");
$delim =~ /%/ and $self->editor_export_data_form("% may not be used as a delimited.");
my $screen = $self->{cgi}->{'export-mode'} ne 'file';
local *FILE;
if ($screen) {
open FILE, ">&STDOUT";
print FILE $self->{in}->header(); # print FILE to avoid STDOUT vs. FILE buffering issues
print FILE "<html><head><title>Links Export</title></head><body><pre>";
}
else {
my $filename = $self->{cgi}->{'export-file'} or return $self->editor_export_data_form("Please enter a file name!");
open FILE, "> $filename" or return $self->editor_export_data_form("Unable to open file '$filename': $!");
}
# Print the row header.
print FILE join ($delim, @order), "\n";
# Print the data.
my $db = $DB->table(qw/Links CatLinks Category/);
my $sth = $db->select(\@order) or return $self->editor_export_data_form($GT::SQL::error);
my $delim_re = quotemeta $delim;
my $delim_str = join '', map sprintf("%%%02x", ord($_)), split '', $delim;
{
local $, = $delim;
local $\ = "\n";
while (my $row = $sth->fetchrow_arrayref) {
for (@$row) {
s{$delim_re}{$delim_str}g;
s{%} {%25}g;
s{\r} {}g;
s{\n} {%0a}g;
$_ = $self->{in}->html_escape($_) if $screen;
}
print FILE @$row;
}
}
print FILE "</pre></body></html>" if $screen;
return $self->editor_export_data_form("Data has been exported to: $self->{cgi}->{'export-file'}") unless $screen;
return;
}
END_OF_SUB
sub _check_opts {
# -------------------------------------------------------------------
# Need to override this so searching for categories works.
#
my $self = shift;
my $sel = 0;
# Relation does not plat fare :(
my $cols = $self->{table}->cols;
for (keys %{$self->{cgi}}) { $sel = 1 if (($self->{cgi}->{$_} =~ /\S/) and exists $cols->{$_}) }
if ((exists $self->{cgi}->{query} and $self->{cgi}->{query} =~ /\S/) or
(exists $self->{cgi}->{keyword} and $self->{cgi}->{keyword} =~ /\S/)) {
$sel = 1;
}
my $prefix = $DB->prefix;
if (! $sel and ($self->{table}->name eq $prefix . 'Links') and (exists $self->{cgi}->{'CatLinks.CategoryID'})) {
$sel = 1;
}
$sel or return;
return 1;
}
sub _buttons {
# -------------------------------------------------------------------
# Adds a warning message to delete Users and Categories.
#
my $self = shift;
my $name = shift;
my $prefix = GT::SQL->prefix();
my $msg = '';
if (($self->{table}->name eq $prefix . "Users") and ($name eq 'Delete')) {
$msg = qq~<p><font face="Tahoma,Arial,Helvetica" size="2"><font color="red"><b>Warning:</b></font> deleting a user will also delete all links associated with that user!</font></p>~;
}
if (($self->{table}->name eq $prefix . "Category") and ($name eq 'Delete')) {
$msg = qq~<p><font face="Tahoma,Arial,Helvetica" size="2"><font color="red"><b>Warning:</b></font> deleting a category will also delete all sub categories and links in those categories!</font></p>~;
}
return qq~
<table border=1 cellpadding=0 cellspacing=0><tr><td align=center>
<table border=0 width=500><tr><td align=center>$msg<center><font face="Tahoma,Arial,Helvetica" size="2"><input type=submit value="$name $self->{record}"></font></center></td></tr></table>
</td></tr></table>
~;
}
1;

View File

@ -0,0 +1,244 @@
# ==================================================================
# 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: Authenticate.pm,v 1.34 2008/10/06 17:41:18 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::Authenticate;
# ==================================================================
use strict;
use Links qw/:objects/;
use GT::Session::SQL;
# This package lets you integrate Gossamer Links into another authentication
# system. You can do this by replacing the functions with your own
# code. Note: to return error results, simply set error => message in
# the passed in hash.
sub auth {
# -----------------------------------------------------------------------------
# Runs the request auth function through the plugin system.
#
shift if UNIVERSAL::isa($_[0], 'Links::Authenticate');
my ($auth, $args) = @_;
my $code = exists $Links::Authenticate::{"auth_$auth"}
? $Links::Authenticate::{"auth_$auth"}
: die "Invalid Authenticate method 'auth_$auth' called";
$PLG->dispatch("auth_$auth", $code, $args);
}
sub auth_init {
# -----------------------------------------------------------------------------
# This function is guaranteed to be called before any other authentication
# function, but may be called multiple times during one request.
#
return 1;
}
sub auth_add_user {
# -----------------------------------------------------------------------------
# This function is called whenever a user is added to the database. It takes a
# hash reference with Username and Password as input. If there is an error, set
# $args->{error} to the message.
#
my $args = shift;
return { Username => $args->{Username}, Password => $args->{Password} };
}
sub auth_del_user {
# -----------------------------------------------------------------------------
# This function is called whenever a user is trying to be deleted. It returns
# the username on success, or undef on failure.
#
my $args = shift;
return $args->{Username};
}
sub auth_valid_user {
# -----------------------------------------------------------------------------
# This function returns true if the user/pass combo is valid, 0/undef
# otherwise.
#
my $args = shift;
return int $DB->table('Users')->count({ Username => $args->{Username}, Password => $args->{Password} });
}
sub auth_valid_format {
# -----------------------------------------------------------------------------
# This function returns 1 if the user format is valid, undef otherwise.
#
my $args = shift;
my $user = $args->{Username};
return if length $user > 50 or $user !~ /^[\w\s\-\@\.]+$/;
return 1;
}
sub auth_change_pass {
# -----------------------------------------------------------------------------
# This function takes the username, old pass and new pass and returns 1 if
# successful, false otherwise.
#
my $args = shift;
return 1;
}
sub auth_get_pass {
# -----------------------------------------------------------------------------
# This function returns the password (if available) of a given user, undef
# otherwise.
#
my $args = shift;
my $user = $args->{Username};
my $pass = $DB->table('Users')->select(Password => { Username => $user })->fetchrow;
return $pass;
}
sub auth_get_user {
# -----------------------------------------------------------------------------
# This function returns user information for a given user, auto creating if it
# doesn't exist.
#
my $args = shift;
my $user = $args->{Username};
my $pass = $args->{Password};
my $db = $DB->table('Users');
my $user_r = $db->get($user);
if (!$user_r and $args->{auto_create}) {
$user_r->{Username} = $user;
$user_r->{Password} = defined $pass ? $pass : Links::Authenticate::auth('get_pass', { Username => $user });
$user_r->{Email} = $user . '@noemail.nodomain';
$user_r->{ReceiveMail} = 'No';
$user_r->{Password} = '' unless defined $user_r->{Password};
my $defaults = $db->default();
for (keys %$defaults) {
$user_r->{$_} = $defaults->{$_} unless exists $user_r->{$_};
}
$db->insert($user_r) or die "Unable to auto-create user: $user. Reason: $GT::SQL::error";
}
return $user_r;
}
sub auth_valid_session {
# -----------------------------------------------------------------------------
# This functions checks to see if the session is valid, and returns the
# username.
#
my $args = shift;
my $session_id = $IN->param('s') || $IN->cookie($CFG->{user_cookie_prefix} . 's') || return;
my $session;
unless ($session = GT::Session::SQL->new({
_debug => $CFG->{debug_level},
tb => $DB->table('Sessions'),
session_id => $session_id,
expires => $CFG->{user_session_length},
session_data => { sessions => $CFG->{user_sessions}, d => scalar($IN->param('d')) },
})) { # Possibly an expired session
GT::Session::SQL->new({
tb => $DB->table('Sessions'),
expires => $CFG->{user_session_length}
})->cleanup; # Clear out old sessions
return;
}
return $session->{info}->{session_user_id};
}
sub auth_create_session {
# -----------------------------------------------------------------------------
# This function creates a session, and prints the header and returns a hash
# reference with session => $id, and redirect => 0/1.
#
my $args = shift;
my $user = $args->{Username};
my $remember = ($CFG->{user_sessions} eq 'Cookies' and ($args->{Remember} or $IN->param('Remember')));
# Create a new session.
my $session = GT::Session::SQL->new({
_debug => $CFG->{debug_level},
tb => $DB->table('Sessions'),
session_user_id => $user,
session_data => { sessions => $CFG->{user_sessions}, d => scalar($IN->param('d')) },
expires => ($remember ? 0 : $CFG->{user_session_length}),
});
# Clear out old sessions.
$session->cleanup;
# Get session id
my $session_id = $session->{info}->{session_id};
# Now redirect to another URL and set cookies, or set URL string.
my $url = $IN->param('url');
my $redirect = 0;
if ($CFG->{user_sessions} eq 'Cookies') {
my $session_cookie = $IN->cookie(
-name => $CFG->{user_cookie_prefix} . 's',
-value => $session_id,
-path => '/',
-domain => $CFG->{user_cookie_domain},
-expires => ($remember ? '+10y' : '')
);
if ($url) {
print $IN->redirect(-force => 1, -cookie => [$session_cookie], -url => $url);
$redirect = 1;
}
else {
print $IN->header(-force => 1, -cookie => [$session_cookie]);
}
}
else {
# If URL sessions are used, then the user will be forced into dynamic mode
# since there's no way to pass around the session id with the static URLs.
if ($url) {
unless ($url =~ s/([;&\?]s=)([^&;]+)/$1$session_id/) {
$url .= ($url =~ /\?/ ? ';' : '?') . "s=$session_id";
}
unless ($url =~ /([;&\?]d=)([^&;]+)/) {
$url .= ($url =~ /\?/ ? ';' : '?') . "d=1";
}
print $IN->redirect($url);
$redirect = 1;
}
else {
$IN->param(s => $session_id);
$IN->param(d => 1);
print $IN->header();
}
}
return { session => $session_id, redirect => $redirect };
}
sub auth_delete_session {
# -----------------------------------------------------------------------------
# This function removes a session, returns 1 on success, undef on failure.
#
print $IN->header(
-cookie => $IN->cookie(
-name => $CFG->{user_cookie_prefix} . 's',
-value => '',
-path => '/',
-domain => $CFG->{user_cookie_domain},
-expires => '-1y'
)
);
my $session_id = $IN->cookie($CFG->{user_cookie_prefix} . 's') || $IN->param('s') || return;
my $session = GT::Session::SQL->new({
_debug => $CFG->{debug_level},
tb => $DB->table('Sessions'),
session_id => $session_id
}) or return;
# Delete the cookie
$session->delete or return;
1;
}
1;

View File

@ -0,0 +1,753 @@
# ==================================================================
# 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: Bookmark.pm,v 1.35 2007/08/28 22:57:14 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::Bookmark;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# -------------------------------------------------------------------
#
my $action = $IN->param('action');
my %valid = (
map { $_ => 1 } qw(
show_folders
show_links
folder_add
folder_edit
folder_remove
folder_view
edit_preferences
link_add
edit_bookmark
links_manage
users_list
users_folder
users_links
)
);
if ($action !~ /^users_/ and !$USER) {
print $IN->redirect(Links::redirect_login_url('bookmark'));
return;
}
no strict 'refs';
if ($action eq 'folder_manage') {
return $PLG->dispatch("bookmark_$action", \&show_links);
}
if ($action eq 'users_links') {
return $PLG->dispatch("bookmark_$action", \&folder_view);
}
if (defined &$action and $valid{$action}) {
return $PLG->dispatch("bookmark_$action", \&$action);
}
# Otherwise display the modify form.
$PLG->dispatch("bookmark_show_folders", \&show_folders);
}
sub show_folders {
# --------------------------------------------------------
# Show Folders
#
my $username = $USER->{Username};
my $folders = _folder_list($username);
if (exists $folders->{paging} and exists $folders->{paging}->{url}) {
$folders->{paging}->{url} .= (index($folders->{paging}->{url}, '?') != -1 ? ';' : '?') . "action=show_folders";
}
$folders->{link_count} ||= $DB->table('Bookmark_Links', 'Links')->count({ my_user_username_fk => $username }, VIEWABLE);
$folders->{message} = shift;
$folders->{error} = shift;
$folders->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
print $IN->header();
print Links::SiteHTML::display('bookmark_list', $folders);
}
sub users_list {
# --------------------------------------------------------
# Display the users list with public folders
#
my $error = shift || "";
my $db = $DB->table('Users', 'Bookmark_Folders');
# Get our options.
my ($limit, $offset, $nh) = Links::limit_offset($CFG->{bookmark_users_per_page});
$db->select_options("GROUP BY my_folder_user_username_fk");
$db->select_options("ORDER BY my_folder_user_username_fk ASC");
$db->select_options("LIMIT $limit OFFSET $offset");
my @users;
my $sth = $db->select('my_folder_user_username_fk', { my_folder_public => 1 });
my $total = $db->select('COUNT(DISTINCT(my_folder_user_username_fk))', { my_folder_public => 1 })->fetchrow;
while (my $row = $sth->fetchrow_hashref()) {
$row->{public_folders} = $db->count({ my_folder_user_username_fk => $row->{my_folder_user_username_fk}, my_folder_public => 1 });
$row->{public_links} = _total_pub_links($row->{my_folder_user_username_fk});
push @users, $row;
}
my ($toolbar, %paging);
if ($total > $limit) {
my $url = _bookmark_url();
$url .= (index($url, '?') != -1 ? ';' : '?') . 'action=users_list';
$toolbar = $DB->html($db, $IN)->toolbar($nh, $limit, $total, $url);
%paging = (
url => $url,
num_hits => $total,
max_hits => $limit,
current_page => $nh
);
}
print $IN->header();
print Links::SiteHTML::display('bookmark_users', {
users => \@users,
total_users => $total,
error => $error,
toolbar => $toolbar,
paging => \%paging,
main_title_loop => Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi")
});
}
sub users_folder {
# --------------------------------------------------------
# Display user's public folders
#
my $username = $IN->param('my_folder_username');
unless ($DB->table('Users')->get($username)) {
return users_list(Links::language('BOOKMARK_USER_NOTEXISTS', $username));
}
my $folders = _folder_list($username);
if (exists $folders->{paging} and exists $folders->{paging}->{url}) {
$folders->{paging}->{url} .= (index($folders->{paging}->{url}, '?') != -1 ? ';' : '?') . "action=users_folder;my_folder_username=" . $IN->escape($username);
}
$folders->{link_count} ||= $DB->table('Bookmark_Links', 'Bookmark_Folders', 'Links')->count({ my_user_username_fk => $username, my_folder_public => 1 }, VIEWABLE);
$folders->{message} = shift;
$folders->{error} = shift;
$folders->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
if ($folders->{link_count} == 0 and $folders->{folder_count} == 0) {
my $error = Links::language('BOOKMARK_PUBLIC_USER', $username);
return users_list($error);
}
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_view', $folders);
}
sub folder_view {
# --------------------------------------------------------
# View the links in the folder
#
my $message = shift;
my $error = shift;
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
my $folderid = $IN->param('my_folder_id') || $IN->param('my_folder_id_fk');
my $folder = $DB->table('Bookmark_Folders')->get($folderid);
unless ($folder) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('BOOKMARK_FOLDER_NOTEXISTS'), main_title_loop => $mtl });
return;
}
if ($USER->{Username} ne $folder->{my_folder_user_username_fk} and not $folder->{my_folder_public}) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('BOOKMARK_FOLDER_NOTPUBLIC'), main_title_loop => $mtl });
return;
}
my $username = $folder->{my_folder_user_username_fk};
my $links = _links_list($username, $folderid);
if (exists $links->{paging} and exists $links->{paging}->{url}) {
my $action = $IN->param('action') eq 'users_links' ? 'users_links' : 'folder_view';
$links->{paging}->{url} .= (index($links->{paging}->{url}, '?') != -1 ? ';' : '?') . "action=$action;my_folder_id=$folderid";
}
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_view', {
%$links,
my_folder_username => $username,
message => $message,
error => $error,
main_title_loop => $mtl
});
}
sub show_links {
# --------------------------------------------------------
# Show user's link for the user to manage
#
my $message = shift;
my $error = shift;
my $username = shift || $USER->{Username};
my $folderid = $IN->param('my_folder_id') || $IN->param('my_folder_id_fk');
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
my $folder = $DB->table('Bookmark_Folders')->get($folderid);
if (not $error and $username ne $folder->{my_folder_user_username_fk} and not $folder->{my_folder_public}) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('BOOKMARK_FOLDER_NOTPUBLIC'), main_title_loop => $mtl });
return;
}
my $links = _links_list($username, $folderid);
if (exists $links->{paging} and exists $links->{paging}->{url}) {
$links->{paging}->{url} .= (index($links->{paging}->{url}, '?') != -1 ? ';' : '?') . "action=folder_manage;my_folder_id=$folderid";
}
my $folders = _folder_list($username, $folderid, 1);
print $IN->header();
print Links::SiteHTML::display('bookmark_list', {
%$links,
folder_select => $folders->{Folders},
message => $message,
error => $error,
main_title_loop => $mtl
});
}
sub _folder_list {
# --------------------------------------------------------
# Generate folder lists
#
my $username = shift;
my $exclude = shift;
my $dropdown = shift;
my $bf = $DB->table('Bookmark_Folders');
return unless $username;
# Get our options.
my ($limit, $offset, $nh) = Links::limit_offset($CFG->{bookmark_folders_per_page});
$bf->select_options("ORDER BY my_folder_name ASC");
unless ($dropdown) {
$bf->select_options("LIMIT $limit OFFSET $offset");
}
my $cond = GT::SQL::Condition->new(my_folder_user_username_fk => '=' => $username);
if ($USER->{Username} ne $username) {
$cond->add(my_folder_public => '=' => 1);
}
if ($exclude > 0) {
$cond->add(my_folder_id => '!=' => $exclude);
}
my $sth = $bf->select($cond);
my $total = $bf->hits;
my $folder = [];
if ($sth->rows) {
my $i = 0;
while (my $row = $sth->fetchrow_hashref) {
$row->{num_links} = _count_links($username, $row->{my_folder_id});
$row->{my_folder_name} = $IN->html_escape($row->{my_folder_name});
$row->{my_folder_description} = $IN->html_escape($row->{my_folder_description});
push @$folder, $row;
$i++;
}
my ($toolbar, %paging);
if (!$dropdown and $total > $limit) {
my $url = _bookmark_url();
$toolbar = $DB->html($bf, $IN)->toolbar($nh, $limit, $total, $url);
%paging = (
url => $url,
num_hits => $total,
max_hits => $limit,
current_page => $nh
);
}
return { Folders => $folder, folder_count => $total, toolbar => $toolbar, paging => \%paging };
}
return { Folders => '', folder_count => 0 };
}
sub _count_links {
# --------------------------------------------------------
# Return the number of links in folder
#
my ($username, $folderid) = @_;
return $DB->table('Bookmark_Links', 'Links')->count({ my_folder_id_fk => $folderid }, VIEWABLE);
}
sub _total_pub_links {
# --------------------------------------------------------
# Return the number of public links for a user
#
my $username = shift;
my $links_db = $DB->table('Bookmark_Folders', 'Bookmark_Links', 'Links');
return $links_db->count({ my_folder_public => 1, my_user_username_fk => $username }, VIEWABLE);
}
sub _links_list {
# --------------------------------------------------------
# Generate links list
#
my ($username, $folderid) = @_;
my $db = $DB->table('Bookmark_Links', 'Links');
my $html = $DB->html($db, $IN);
return unless $username;
if ($username eq $USER->{Username}) {
$db->select_options("ORDER BY $USER->{SortField} $USER->{SortOrd}");
}
else {
$db->select_options("ORDER BY $CFG->{bookmark_links_sort} $CFG->{bookmark_links_sort_order}");
}
my ($limit, $offset, $nh) = Links::limit_offset(
($USER->{Username} eq $username and $USER->{PerPage})
? $USER->{PerPage}
: $CFG->{bookmark_links_per_page}
);
$db->select_options("LIMIT $limit OFFSET $offset");
my $sth = $db->select({ my_user_username_fk => $username, my_folder_id_fk => $folderid }, VIEWABLE);
my $link_count = $db->hits;
my $links = [];
my $folder = $DB->table('Bookmark_Folders')->get($folderid);
# Generate a toolbar if requested.
my ($toolbar, %paging);
if ($link_count > $limit) {
my $url = _bookmark_url();
$toolbar = $html->toolbar($nh, $limit, $link_count, $url);
%paging = (
url => $url,
num_hits => $link_count,
max_hits => $limit,
current_page => $nh
);
}
my @link_results_loop;
my ($link_results, %link_output);
if ($link_count) {
my $results = $sth->fetchall_hashref;
my $links_tb = $DB->table('Links');
for (@$results) {
$_->{my_comment} = $IN->html_escape($_->{my_comment});
$links_tb->add_reviews($_);
}
if ($USER->{Grouping}) {
my @ids = map { $_->{ID} } @$results;
my $catlink = $DB->table('CatLinks', 'Category');
my %names = $catlink->select('LinkID', 'Full_Name', { LinkID => \@ids })->fetchall_list;
foreach my $link (@$results) {
push @{$link_output{$names{$link->{ID}}}}, $link;
}
}
else {
push @{$link_output{none}}, @$results;
}
}
$folder ||= {};
$folder->{my_folder_name} = $IN->html_escape($folder->{my_folder_name});
$folder->{my_folder_description} = $IN->html_escape($folder->{my_folder_description});
if ($link_count) {
my $i = 0;
if ($USER->{Grouping}) {
foreach my $cat (sort keys %link_output) {
$link_output{$cat}->[0]->{title_linked} = sub { Links::Build::build('title_linked', { name => $cat, complete => 1, home => 0 }) };
$link_output{$cat}->[0]->{title_loop} = Links::Build::build('title', $cat);
push @link_results_loop, @{$link_output{$cat}};
}
}
else {
push @link_results_loop, @{$link_output{none}};
}
return { Bookmarks => \@link_results_loop, link_count => $link_count, %$folder, toolbar => $toolbar, paging => \%paging };
}
return { Bookmarks => "", link_count => 0, %$folder, toolbar => $toolbar };
}
sub folder_add {
# --------------------------------------------------------
# Add Folder
#
my $args = $IN->get_hash();
my $bf = $DB->table('Bookmark_Folders');
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
if ($IN->param('add')) {
unless ($args->{my_folder_name}) {
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_add', { error => Links::language('BOOKMARK_BAD_FOLDER'), main_title_loop => $mtl });
return;
}
if ($bf->count({ my_folder_user_username_fk => $USER->{Username} }) >= $CFG->{bookmark_folder_limit}) {
return show_folders('', Links::language('BOOKMARK_FOLDER_LIMIT'));
}
if ($bf->count({ my_folder_name => $args->{my_folder_name}, my_folder_user_username_fk => $USER->{Username} })) {
$args->{error} = Links::language('BOOKMARK_FOLDER_DUPLICATE', $args->{my_folder_name});
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_add', { %$args, main_title_loop => $mtl });
return;
}
$args->{my_folder_user_username_fk} = $USER->{Username};
$args->{my_folder_public} = $args->{my_folder_public} ? 1 : 0;
$args->{my_folder_default} = $args->{my_folder_default} ? 1 : 0;
if ($args->{my_folder_default}) {
$bf->update({ my_folder_default => 0 }, { my_folder_user_username_fk => $USER->{Username}, my_folder_default => 1 });
}
my $fid = $bf->add($args);
if ($fid) {
return show_folders();
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_add', { error => Links::language('BOOKMARK_BAD_FOLDER', $GT::SQL::error), main_title_loop => $mtl });
}
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_add', { main_title_loop => $mtl });
}
}
sub folder_edit {
# --------------------------------------------------------
# Edit Folder
#
my $args = $IN->get_hash();
my $bf = $DB->table('Bookmark_Folders');
my $folderid = $args->{my_folder_id};
my $folder = $bf->get($folderid);
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
if (!$folder or $folder->{my_folder_user_username_fk} ne $USER->{Username}) {
return show_folders('', Links::language('BOOKMARK_BAD_FOLDER_ID', $folderid));
}
if ($IN->param('modify')) {
unless ($args->{my_folder_name}) {
$args->{error} = Links::language('BOOKMARK_BAD_FOLDER');
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_edit', { %$args, main_title_loop => $mtl });
return;
}
if ($args->{my_folder_name} ne $folder->{my_folder_name} and $bf->count({ my_folder_name => $args->{my_folder_name}, my_folder_user_username_fk => $USER->{Username} })) {
$args->{error} = Links::language('BOOKMARK_FOLDER_DUPLICATE', $args->{my_folder_name});
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_edit', { %$args, main_title_loop => $mtl });
return;
}
$args->{my_folder_user_username_fk} = $USER->{Username};
$args->{my_folder_public} = $args->{my_folder_public} ? 1 : 0;
$args->{my_folder_default} = $args->{my_folder_default} ? 1 : 0;
if ($args->{my_folder_default}) {
$bf->update({ my_folder_default => 0 }, { my_folder_user_username_fk => $USER->{Username}, my_folder_default => 1 });
}
my $rec = $bf->modify($args);
if ($rec) {
$IN->param('my_folder_name', '');
show_folders(Links::language('BOOKMARK_FOLDER_MODIFIED', $folderid));
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_edit', { error => Links::language('BOOKMARK_BAD_FOLDER', $GT::SQL::error), main_title_loop => $mtl });
}
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_folder_edit', { %$folder, main_title_loop => $mtl });
}
}
sub folder_remove {
# -------------------------------------------------------------------
# Remove folder and the links in it
#
my $folderid = $IN->param('my_folder_id');
my $bf = $DB->table('Bookmark_Folders');
my $folder = $bf->get($folderid);
my $error;
if ($folder->{my_folder_default}) {
$error = Links::language('BOOKMARK_FOLDER_DEFAULT');
}
elsif ($bf->count({ my_folder_id => $folderid, my_folder_user_username_fk => $USER->{Username} })) {
my $rc = $bf->delete({ my_folder_id => $folderid, my_folder_user_username_fk => $USER->{Username} });
unless ($rc) {
$error = $GT::SQL::error;
}
}
else {
$error = Links::language('BOOKMARK_FOLDER_NOTEXISTS', $folderid);
}
if ($error) {
return show_folders('', $error);
}
else {
return show_folders(Links::language('BOOKMARK_FOLDER_REMOVED', $folder->{my_folder_name}));
}
}
sub edit_bookmark {
# -------------------------------------------------------------------
# edit Bookmark Comments
#
my $args = $IN->get_hash();
my $id = $args->{my_id} || shift;
my $bl = $DB->table('Bookmark_Links');
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
# <=3.2 template backwards compatibility
# Previously the link id was passed to edit, but the ability to bookmark a link
# multiple times was added in 3.2.
my $lid = $args->{id} || shift;
if (!$id and $lid) {
$bl->select_options('ORDER BY my_id');
$id = $bl->select('my_id', { my_link_id_fk => $lid, my_user_username_fk => $USER->{Username} })->fetchrow;
}
if (not $bl->count({ my_id => $id, my_user_username_fk => $USER->{Username} })) {
print $IN->header();
print Links::SiteHTML::display('bookmark_link_edit', { error => Links::language('BOOKMARK_LINK_NOTEXISTS', $id), main_title_loop => $mtl });
return;
}
my $link = $DB->table('Bookmark_Links', 'Links')->select({ my_id => $id, my_user_username_fk => $USER->{Username} })->fetchrow_hashref;
my $folders = _folder_list($USER->{Username}, undef, 1);
if ($args->{edit}) {
my %set = (my_comment => $args->{my_comment});
if ($link->{my_folder_id_fk} != $args->{my_folder_id_fk} and $bl->count({ my_link_id_fk => $link->{my_link_id_fk}, my_folder_id_fk => $args->{my_folder_id_fk}, my_user_username_fk => $USER->{Username} })) {
print $IN->header();
print Links::SiteHTML::display('bookmark_link_edit', { error => Links::language('BOOKMARK_LINK_EXISTS', $id), %$folders, %$link, main_title_loop => $mtl });
return;
}
if ($DB->table('Bookmark_Folders')->count({ my_folder_id => $args->{my_folder_id_fk}, my_folder_user_username_fk => $USER->{Username} })) {
$set{my_folder_id_fk} = $args->{my_folder_id_fk};
}
my $rec = $bl->update(\%set, { my_id => $id, my_user_username_fk => $USER->{Username} });
if ($rec) {
$IN->param(my_folder_id_fk => $args->{my_folder_id_fk});
$IN->param(id => '');
$IN->param(my_comment => '');
show_links(Links::language('BOOKMARK_COMMENTS_EDITED'));
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_link_edit', { error => $GT::SQL::error, main_title_loop => $mtl });
}
}
else {
print $IN->header();
$link->{my_comment} = $IN->html_escape($link->{my_comment});
print Links::SiteHTML::display('bookmark_link_edit', { %$folders, %$link, main_title_loop => $mtl });
}
}
sub edit_preferences {
# -------------------------------------------------------------------
# edit Bookmark Preferences
#
my $args = $IN->get_hash();
my $username = $USER->{Username} || shift;
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
if ($args->{modify}) {
if ($args->{PerPage} <= 0) {
print $IN->header();
print Links::SiteHTML::display('bookmark_preferences', { error => Links::language('BOOKMARK_PREF_INVALIDPERPAGE'), main_title_loop => $mtl });
return;
}
my $rec = $DB->table('Users')->update({
SortField => $args->{SortField},
SortOrd => $args->{SortOrd},
PerPage => $args->{PerPage},
Grouping => $args->{Grouping}
}, { Username => $username });
if ($rec) {
print $IN->header();
print Links::SiteHTML::display('bookmark_preferences', { %$args, message => Links::language('BOOKMARK_PREFERENCES'), main_title_loop => $mtl });
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_preferences', { error => $GT::SQL::error, main_title_loop => $mtl });
}
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_preferences', { main_title_loop => $mtl });
}
}
sub link_add {
# -------------------------------------------------------------------
# add a link to a folder
#
my $args = $IN->get_hash();
my $linkid = $args->{ID} || $args->{my_link_id_fk};
my $username = $USER->{Username} || shift;
my $bl = $DB->table('Bookmark_Links');
my $bf = $DB->table('Bookmark_Folders');
my $mtl = Links::Build::build('title', Links::language('LINKS_BOOKMARK'), "$CFG->{db_cgi_url}/bookmark.cgi");
# check if they've reached the limit of number of bookmarks
if ($bl->count({ my_user_username_fk => $username }) >= $CFG->{bookmark_links_limit}) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('BOOKMARK_LINK_LIMIT'), main_title_loop => $mtl });
return;
}
# check to make sure the link exists
my $link = $DB->table('Links')->get($linkid);
unless ($link) {
print $IN->header();
print Links::SiteHTML::display('bookmark_link_add', { error => Links::language('RATE_INVALIDID', $linkid), main_title_loop => $mtl });
return;
}
# check if the user has any folder, if not, create one default for them.
unless ($DB->table('Bookmark_Folders')->count({ my_folder_user_username_fk => $username })) {
my $rc = $bf->insert({
my_folder_user_username_fk => $username,
my_folder_name => $CFG->{bookmark_folder_default_name},
my_folder_default => 1,
my_folder_public => 0,
});
unless ($rc) {
print $IN->header();
print Links::SiteHTML::display('error', { error => $GT::SQL::error, main_title_loop => $mtl });
return;
}
else {
$args->{my_folder_id_fk} = $rc->insert_id;
}
}
my $folders = _folder_list($username, undef, 1);
if ($args->{add}) {
my $error;
if (!$bf->count({ my_folder_id => $args->{my_folder_id_fk}, my_folder_user_username_fk => $username })) {
$error = Links::language('BOOKMARK_FOLDER_INVALID');
}
# Don't allow duplicate links in a folder
elsif ($bl->count({ my_link_id_fk => $linkid, my_folder_id_fk => $args->{my_folder_id_fk}, my_user_username_fk => $username })) {
$error = Links::language('BOOKMARK_LINK_EXISTS', $linkid);
}
if ($error) {
print $IN->header();
print Links::SiteHTML::display('bookmark_link_add', { error => $error, %$folders, %$link, main_title_loop => $mtl });
}
else {
$args->{my_user_username_fk} = $username;
my $rec = $bl->add($args);
if ($rec) {
folder_view(Links::language('BOOKMARK_LINK_ADDED'));
}
else {
print $IN->header();
print Links::SiteHTML::display('error', { error => $GT::SQL::error, main_title_loop => $mtl });
}
}
}
else {
print $IN->header();
print Links::SiteHTML::display('bookmark_link_add', { %$folders, %$link, main_title_loop => $mtl });
}
}
sub links_manage {
# -------------------------------------------------------------------
# manage links, such as move or delete
#
my $folderid = $IN->param('move_folderid');
my $old_folderid = $IN->param('my_folder_id');
my $username = $USER->{Username};
my $db = $DB->table('Bookmark_Links');
my ($i, $error, $message) = 0;
my @ids = $IN->param('my_id');
my @lids = $IN->param('m-id');
# <=3.2 template backwards compatibility
if (!@ids and @lids) {
$db->select_options('GROUP BY my_link_id_fk');
@ids = $db->select('my_id', { my_link_id_fk => \@lids, my_folder_id_fk => $old_folderid, my_user_username_fk => $username })->fetchall_list;
}
if ($IN->param('move')) {
if (not $DB->table('Bookmark_Folders')->count({ my_folder_id => $folderid, my_folder_user_username_fk => $username })) {
$error .= Links::language('BOOKMARK_FOLDER_NO_MOVE', $folderid);
}
else {
for my $id (@ids) {
my $link = $db->select({ my_id => $id, my_user_username_fk => $username })->fetchrow_hashref;
if (!$link or $db->count({ my_link_id_fk => $link->{my_link_id_fk}, my_folder_id_fk => $folderid, my_user_username_fk => $username })) {
$error .= Links::language('BOOKMARK_LINK_EXISTS', $id);
next;
}
my $rc = $db->update({ my_folder_id_fk => $folderid }, { my_id => $id, my_user_username_fk => $username });
unless ($rc) {
$error .= $GT::SQL::error;
}
else {
$i++;
}
}
my $folder = $DB->table('Bookmark_Folders')->get($folderid);
$message = Links::language('BOOKMARK_LINK_MOVED', $i, $folder->{my_folder_name});
}
}
elsif ($IN->param('remove')) {
foreach my $id (@ids) {
if ($db->count({ my_id => $id, my_folder_id_fk => $old_folderid, my_user_username_fk => $username })) {
my $rc = $db->delete({ my_id => $id, my_folder_id_fk => $old_folderid, my_user_username_fk => $username });
unless ($rc) {
$error = $GT::SQL::error;
}
else {
$i++;
}
}
else {
$error .= Links::language('BOOKMARK_LINK_NOTEXISTS', $id);
}
}
$message = Links::language('BOOKMARK_LINK_REMOVED', $i);
}
else {
$error .= Links::language('BOOKMARK_NO_ACTION');
}
if ($error) {
return show_links("", $error);
}
else {
return show_links($message);
}
}
sub _bookmark_url {
# -------------------------------------------------------------------
# Generate a bookmark.cgi url (mainly used by paging).
#
my $url = $CFG->{db_cgi_url} . "/bookmark.cgi";
my $in_hash = $IN->get_hash(0);
my @url_hidden;
foreach (@{$CFG->{dynamic_preserve}}) {
next unless defined $in_hash->{$_} and $in_hash->{$_} =~ /\S/;
push @url_hidden, $IN->escape($_) . "=" . $IN->escape($in_hash->{$_});
}
if (@url_hidden) {
$url .= "?" . join ';', @url_hidden;
}
return $url;
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,499 @@
# ==================================================================
# 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: Controller.pm,v 1.9 2009/07/09 23:13:41 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::Browser::Controller;
# ==================================================================
use strict;
use vars qw/@ISA $AUTOLOAD $ATTRIBS/;
use GT::Base;
use Links qw/$CFG $IN $DB/;
use Links::Browser;
@ISA = qw/GT::Base/;
$ATTRIBS = {
user_base_node => [],
load_tree => 0,
perms => {},
admin => 0,
user => {},
admin_templates => 0
};
sub can_run {
# -------------------------------------------------------------------
# Determines whether or not the user can run the requested function.
#
my $self = shift;
my $action = $IN->param ("action") || return "main_panel_init";
if (exists $Links::Browser::COMPILE{$action}) {
if ($self->{admin}) {
return $action;
}
if ($self->$action()) { return $action }
else { return }
}
else { return }
return $action;
}
# Everyone can load the browser.
sub main_panel_init { return 1 }
sub tree_panel_init { return 1 }
sub info_panel_init { return 1 }
sub code_panel_init { return 1 }
sub code_panel_reload_empty { return 1 }
sub code_panel_reload_full { return 1 }
sub category_click {
# -------------------------------------------------------------------
# Determine whether the user can view a category.
#
my $self = shift;
return $self->is_in_subtree ($IN->param ('category_id'));
}
sub code_panel_category_expand {
# -------------------------------------------------------------------
# Expand a section of the tree.
#
my $self = shift;
return $self->is_in_subtree ($IN->param ('category_id'));
}
sub category_add_form {
# -------------------------------------------------------------------
# Display add form.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanAddCat} eq 'Yes') ? 1 : 0;
}
sub category_add {
# -------------------------------------------------------------------
# Determines whether you can actually add a category.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('FatherID')) or return;
return ($self->{perms}->{$base}->{CanAddCat} eq 'Yes') ? 1 : 0;
}
sub category_del_form {
# -------------------------------------------------------------------
# Display category delete form.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanDelCat} eq 'Yes') ? 1 : 0;
}
sub category_del { return shift->category_del_form (@_); }
sub category_modify_form {
# -------------------------------------------------------------------
# Display category modify form.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanModCat} eq 'Yes') ? 1 : 0;
}
sub category_modify {
# -------------------------------------------------------------------
# Determines whether you can actually modify the given category.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('ID')) or return;
return ($self->{perms}->{$base}->{CanModCat} eq 'Yes') ? 1 : 0;
}
sub category_move_form {
# -------------------------------------------------------------------
# Display category move form.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanMoveCat} eq 'Yes') ? 1 : 0;
}
sub category_move {
# -------------------------------------------------------------------
# Display category move form.
#
my $self = shift;
my $base1 = $self->is_in_subtree ($IN->param ('category_from')) or return;
my $base2 = $self->is_in_subtree ($IN->param ('category_to')) or return;
$self->{perms}->{$base1}->{CanMoveCat} eq 'Yes' or return;
$self->{perms}->{$base2}->{CanMoveCat} eq 'Yes' or return;
return 1;
}
sub category_editors_form {
# -------------------------------------------------------------------
# Display category editors form and process edits.
#
my $self = shift;
return if (defined $self->{perms}->{CanAddEdit} and $self->{perms}->{CanAddEdit} eq 'No');
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
$self->{perms}->{$base}->{CanAddEdit} eq 'Yes' or return;
foreach my $key ($IN->param('to_delete')) {
my ($name, $id) = split /\|/, $key;
$base = $self->is_in_subtree ($id) or return;
$self->{perms}->{$base}->{CanAddEdit} eq 'Yes' or return;
}
return 1;
}
sub category_related_form {
# -------------------------------------------------------------------
# Display related categories form and process relations.
#
my $self = shift;
return if (defined $self->{perms}->{CanAddRel} and $self->{perms}->{CanAddRel} eq 'No');
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
$self->{perms}->{$base}->{CanAddRel} eq 'Yes' or return;
foreach my $id ($IN->param('to_delete')) {
$base = $self->is_in_subtree ($id) or return;
$self->{perms}->{$base}->{CanAddRel} eq 'Yes' or return;
}
return 1;
}
sub link_user_list {
# -------------------------------------------------------------------
# Display list of links this user owns.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param('category_id')) or return;
return 1;
}
sub link_add_form {
# -------------------------------------------------------------------
# Display add link form.
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanAddLink} eq 'Yes') ? 1 : 0;
}
sub link_add {
# -------------------------------------------------------------------
# Display add link form.
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('CatLinks.CategoryID')) or return;
return ($self->{perms}->{$base}->{CanAddLink} eq 'Yes') ? 1 : 0;
}
sub link_modify_form {
# -------------------------------------------------------------------
# Display modify link form.
#
my $self = shift;
my $catlinks = $DB->table('CatLinks');
my $q = $catlinks->select({ LinkID => $IN->param("link_id") || $IN->param('ID') });
my $allowed = 0;
while (my $h = $q->fetchrow_hashref) {
my $base = $self->is_in_subtree ($h->{CategoryID}) or next;
if ($self->{perms}->{$base}->{CanModLink} eq 'Yes') {
$allowed = 1;
last;
}
}
return $allowed;
}
sub link_modify {
# -------------------------------------------------------------------
# Display modify link form.
#
my $self = shift;
$self->link_modify_form(@_);
}
sub link_del_form {
# -------------------------------------------------------------------
# Display delete link form.
#
my $self = shift;
return if (defined $self->{perms}->{CanDelLink} and $self->{perms}->{CanDelLink} eq 'No');
my $catlinks = $DB->table (qw /CatLinks/);
my $q = $catlinks->select ( { LinkID => scalar $IN->param ("link_id") } );
my $allowed = 0;
while (my $h = $q->fetchrow_hashref) {
my $base = $self->is_in_subtree ($h->{CategoryID}) or next;
if ($self->{perms}->{$base}->{CanDelLink} eq 'Yes') {
$allowed = 1;
last;
}
}
return $allowed;
}
sub link_del { shift->link_del_form (@_); }
sub link_move_form {
# -------------------------------------------------------------------
# Display form to move link.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanMoveLink} eq 'Yes') ? 1 : 0;
}
sub link_move {
# -------------------------------------------------------------------
# Checks whether the link can be moved into the requested category.
#
my $self = shift;
my $old_category_id = $IN->param ("old_category_id");
my $new_category_id = $IN->param ("new_category_id");
my $base1 = $self->is_in_subtree ($old_category_id) or return;
my $base2 = $self->is_in_subtree ($new_category_id) or return;
$self->{perms}->{$base1}->{CanMoveLink} eq 'Yes' or return;
$self->{perms}->{$base2}->{CanMoveLink} eq 'Yes' or return;
return 1;
}
sub link_copy_form {
# -------------------------------------------------------------------
# Display form to copy a link.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanCopyLink} eq 'Yes') ? 1 : 0;
}
sub link_copy {
# -------------------------------------------------------------------
# Checks whether a link can be moved into requested category.
#
my $self = shift;
my $old_category_id = $IN->param ("old_category_id");
my $new_category_id = $IN->param ("new_category_id");
my $base1 = $self->is_in_subtree ($old_category_id) or return;
my $base2 = $self->is_in_subtree ($new_category_id) or return;
$self->{perms}->{$base1}->{CanCopyLink} eq 'Yes' or return;
$self->{perms}->{$base2}->{CanCopyLink} eq 'Yes' or return;
return 1;
}
sub link_validate_list {
# -------------------------------------------------------------------
# Checks whether a user can display links awaiting validation.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanValLink} eq 'Yes') ? 1 : 0;
}
sub link_validate_detailed {
# -------------------------------------------------------------------
# Checks whether a user can display links awaiting validation.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
# Let's parse out the form, and group our links together.
my $args = $IN->get_hash();
my $catlinks_db = $DB->table( 'CatLinks' );
my ( @denied, @allowed );
while (my ($key, $param) = each %$args) {
if ($key =~ /^validate-(\d+)/) {
my $id = $1;
my $q = $catlinks_db->select ( { LinkID => $id } );
my $base;
while (my $h = $q->fetchrow_hashref ) {
if ( $base = $self->is_in_subtree ($h->{CategoryID})
and $self->{perms}->{$base}->{CanValLink} eq 'Yes' ) {
push @allowed, $id;
next;
}
push @denied, $id;
}
}
}
# Remove action verbs for any listings the user is not allowed to validate
for my $id ( @denied ) {
$IN->param( "validate-$id", undef );
}
return ($self->{perms}->{$base}->{CanValLink} eq 'Yes') ? 1 : 0;
}
sub link_validate_changes_list {
# -------------------------------------------------------------------
# Checks whether a user can display links awaiting validation.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanValLink} eq 'Yes') ? 1 : 0;
}
sub link_validate_form {
# -------------------------------------------------------------------
# Checks whether a user can display links awaiting validation.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanValLink} eq 'Yes') ? 1 : 0;
}
sub link_validate {
# -------------------------------------------------------------------
# Checks whether user can actually validate link.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanValLink} eq 'Yes') ? 1 : 0;
}
sub review_list {
# -------------------------------------------------------------------
# Checks whether a user can display reviews awaiting validation.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanModReview} eq 'Yes') ? 1 : 0;
}
sub review_del_form {
# -------------------------------------------------------------------
# Checks whether a user can delete reviews.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanModReview} eq 'Yes') ? 1 : 0;
}
sub review_del {
# -------------------------------------------------------------------
# Checks whether a user can delete reviews.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanModReview} eq 'Yes') ? 1 : 0;
}
sub review_modify_form {
# -------------------------------------------------------------------
# Checks whether a user can display the review modify form.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanModReview} eq 'Yes') ? 1 : 0;
}
sub review_modify {
# -------------------------------------------------------------------
# Checks whether user can actually validate reviews.
#
my $self = shift;
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return ($self->{perms}->{$base}->{CanModReview} eq 'Yes') ? 1 : 0;
}
sub link_search_form {
# -------------------------------------------------------------------
# Display search link form.
my $self = shift;
if (!$IN->param('category_id')) {
my @check_ids;
if (ref $self->{ctrl}->user_base_node) {
@check_ids = @{$self->{ctrl}->user_base_node};
}
else {
$check_ids[0] = $self->{ctrl}->user_base_node;
}
$IN->param('category_id',$check_ids[0]);
}
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return 1;
}
sub link_search_results {
# -------------------------------------------------------------------
# Display search link form.
my $self = shift;
if ($IN->param('in_category')) {
return if (!$self->is_in_subtree ($IN->param ('in_category')));
}
if (!$IN->param('category_id')) {
my @check_ids;
if (ref $self->user_base_node) {
@check_ids = @{$self->user_base_node};
}
else {
$check_ids[0] = $self->user_base_node;
}
$IN->param('category_id',$check_ids[0]);
}
my $base = $self->is_in_subtree ($IN->param ('category_id')) or return;
return 1;
}
sub is_in_subtree {
# -------------------------------------------------------------------
# Returns the category ID of the base node this user is in.
#
my $self = shift;
my $base_r = $self->user_base_node();
@$base_r || return 1; # Root can do anything, no base specified.
my $node = shift or return; # No node specified!
my $category = $DB->table (qw /Category/);
my $info_node = $category->get ( { ID => $node }, 'HASH', ['ID','Full_Name']);
defined $info_node or return; # Invalid node requested.
# Get closest permissions first.
$category->select_options ("ORDER BY Full_Name DESC");
my $sth = $category->select ( ['ID', 'Full_Name'], { ID => $base_r });
while (my ($id, $name) = $sth->fetchrow_array) {
($info_node->{Full_Name} =~ m,^\Q$name\E(?:/|$),) and return $id;
}
return;
}
sub perms {
# -------------------------------------------------------------------
# Returns a list of permissions the user has for a requested category.
#
my ($self, $category_id) = @_;
if ($self->{admin}) {
return { CanAddCat => 'Yes', CanDelCat => 'Yes', CanModCat => 'Yes', CanMoveCat => 'Yes',
CanAddLink => 'Yes', CanDelLink => 'Yes', CanModLink => 'Yes', CanMoveLink => 'Yes', CanCopyLink => 'Yes',
CanValLink => 'Yes', CanModReview => 'Yes',
CanAddRel => 'Yes', CanAddEdit => 'Yes' };
}
my $base = $self->is_in_subtree($category_id) or return {};
if (exists $self->{perms}->{$base}) {
return $self->{perms}->{$base};
}
return {};
}
##
# $obj->user_base_node;
# ---------------------
# Returns an array ref of categories the user can edit.
##
sub user_base_node { return shift->{user_base_node} || [] }
1;

View File

@ -0,0 +1,212 @@
# ==================================================================
# 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: JFunction.pm,v 1.16 2005/03/22 01:42:22 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::Browser::JFunction;
# ==================================================================
use strict;
use Links qw/$CFG $IN $DB/;
##
# Path to image urls.
##
sub node_expand_button_fake { return $CFG->{build_static_url} . "/browser/expandfake.gif"; }
sub node_expand_button_plus { return $CFG->{build_static_url} . "/browser/expandplus.gif"; }
sub node_expand_button_less { return $CFG->{build_static_url} . "/browser/expandless.gif"; }
sub node_unselected_button { return $CFG->{build_static_url} . "/browser/unselected.gif"; }
##
# $obj->tree_loadnode;
# --------------------
# This function must return the URL that the Javascript
# must point the user to when an unloaded node has been expanded.
##
sub tree_loadnode
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "code_panel_category_expand");
return $cgix->url;
}
##
# $obj->tree_selectnode;
# ----------------------
# This function must return the URL that the Javascript
# must point the user to when a node is selected, i.e
# showing a certain category.
##
sub tree_selectnode
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "category_click");
return $cgix->url;
}
##
# $obj->tree_panel_url;
# ---------------------
# This function returns the URL that should be used
# in order to display ...
##
sub tree_panel_url
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "tree_panel_init");
return $cgix->url;
}
##
# $obj->info_panel_url;
# ---------------------
# This function returns the URL that should be used
# in order to display ...
##
sub info_panel_url
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "info_panel_init");
return $cgix->url;
}
##
# $obj->code_panel_url;
# ---------------------
# This function returns the URL that should be used
# in order to display ...
##
sub code_panel_url
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "code_panel_init");
return $cgix->url;
}
##
# $obj->tree_reload_empty;
# ------------------------
# This method returns the URL that when called in the code
# panel empties the Javascript Tree.
##
sub tree_reload_empty
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "code_panel_reload_empty");
return $cgix->url;
}
##
# $obj->tree_reload_full;
# -----------------------
# This method returns the URL that when called in the code
# panel replaces the Javascript tree with a fully loaded
# tree (no need to database for requests).
##
sub tree_reload_full
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "code_panel_reload_full");
return $cgix->url;
}
##
# $obj->tree_refreshnode_url;
# ---------------------------
# This method should return the URL that has to be invoked
# when an user has updated a node and wants to refresh the
# node's contents.
##
sub tree_refreshnode_url
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "code_panel_init");
return $cgix->url;
}
##
# $obj->user_add_node;
# --------------------
# This method should return the URL that must be invoked
# by the code frame when an user has added a subcategory
# and that the javascript tree needs being refreshed.
##
sub user_add_node
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "code_panel_category_add");
return $cgix->url;
}
##
# $obj->tree_movenode;
# ---------------------
# This method should return the URL that must be invoked
# by the code frame when an user has added a subcategory
# and that the javascript tree needs being refreshed.
##
sub tree_movenode
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "category_move");
return $cgix->url;
}
##
# $obj->tree_movelink;
# --------------------
##
sub movelink
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "link_move");
return $cgix->url;
}
##
# $obj->tree_copylink;
# --------------------
##
sub copylink
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "link_copy");
return $cgix->url;
}
##
# $obj->category_related_url;
# ---------------------
# This function returns the URL that should be used
# in order to relate categories ...
##
sub category_related_url
{
my $cgix = new GT::CGI ($IN);
$cgix->param ("action", "category_related_form");
return $cgix->url;
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,365 @@
# ==================================================================
# 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: Config.pm,v 1.117 2009/05/12 02:24:18 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::Config;
# ======================================================================
# Sets up our config variables -- the data itself is stored in
# Links/Config/Data.pm, but you shouldn't need to edit it directly!
#
use GT::Config;
use strict;
use vars qw/@ISA $BIN %FILE_CACHE/;
@ISA = 'GT::Config';
sub new {
# --------------------------------------------------------------------
# Takes one optional argument, the path to the admin directory.
#
my $class = ref $_[0] ? ref shift : shift;
my $path = shift || '.';
my $file = $path . '/Links/Config/Data.pm';
my $header = <<END_OF_CONFIG;
# ==================================================================
# Gossamer Links - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/support/
# Updated : [localtime]
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
END_OF_CONFIG
my $self = $class->load($file, { inheritance => 0, cache => 1, header => $header });
exists $self->{admin_root_path} or ($self->{admin_root_path} = $path || '.');
exists $self->{version} or ($self->{version} = $Links::VERSION);
exists $self->{setup} or ($self->{setup} = 0);
return $self;
}
sub textarea {
# ------------------------------------------------------------------
# Taken from gforum. This takes a string of a config hash key whose value is an array reference.
# Returns the array elements separated by \n's
#
my $want = shift;
if (ref $Links::CFG->{$want} eq 'ARRAY') {
return join "\n", @{$Links::CFG->{$want}};
}
elsif (ref $Links::CFG->{$want} eq 'HASH') {
return join "\n", map "$_ => $Links::CFG->{$want}->{$_}", sort { $a <=> $b } keys %{$Links::CFG->{$want}};
}
else {
return $Links::CFG->{$want};
}
}
sub load_vars {
# ------------------------------------------------------------------
# Returns a hash of config variables for use in templates.
#
my $t = {};
while (my ($key, $val) = each %{$Links::CFG}) {
if (ref $val eq 'ARRAY') { $val = join ", ", @$val }
elsif (ref $val eq 'HASH') { $val = join ", ", map "$_ = $val->{$_}", keys %$val }
$t->{"cfg_$key"} = $Links::IN->html_escape($val);
}
return $t;
}
sub set_defaults {
# ------------------------------------------------------------------
# Set sensible defaults for the config values, overwriting old values.
#
my ($self, $val) = @_;
$self->{setup} = 1;
$self->default_path($val);
$self->default_build($val);
$self->default_search($val);
$self->default_review($val);
$self->default_user($val);
$self->default_email($val);
$self->default_misc($val);
$self->default_date($val);
$self->default_other($val);
}
sub default { shift->set_defaults(1); } # Overwrite
sub create_defaults { shift->set_defaults(0); } # Don't Overwrite
sub set {
# ------------------------------------------------------------------
# Sets a value.
#
my ($self, $key, $val, $overwrite) = @_;
if ($overwrite or ! exists $self->{$key}) { $self->{$key} = $val; }
}
sub default_path {
# ------------------------------------------------------------------
# Set the path settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('admin_root_url', _find_admin_url(), $overwrite);
$self->set('db_cgi_url', _find_cgi_url(), $overwrite);
$self->set('db_cgi_url_https', '', $overwrite);
$self->set('build_root_url', _find_pages_url(), $overwrite);
$self->set('path_to_perl', _find_perl(), $overwrite);
$self->set('build_static_path', "$self->{build_root_path}/static", $overwrite);
$self->set('build_static_url', "$self->{build_root_url}/static", $overwrite);
$self->set('fileman_root_dir', $self->{admin_root_path}, $overwrite);
$self->set('build_images_url', "$self->{build_root_url}/images", $overwrite);
$self->set('build_css_url', "$self->{build_root_url}/links.css", $overwrite);
$self->set('build_new_path', "$self->{build_root_path}/New", $overwrite);
$self->set('build_new_url', "$self->{build_root_url}/New", $overwrite);
$self->set('build_cool_path', "$self->{build_root_path}/Cool", $overwrite);
$self->set('build_cool_url', "$self->{build_root_url}/Cool", $overwrite);
$self->set('build_ratings_path', "$self->{build_root_path}/Ratings", $overwrite);
$self->set('build_ratings_url', "$self->{build_root_url}/Ratings", $overwrite);
$self->set('build_detail_path', "$self->{build_root_path}/Detailed", $overwrite);
$self->set('build_detail_url', "$self->{build_root_url}/Detailed", $overwrite);
}
sub default_build {
# ------------------------------------------------------------------
# Set the build settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('build_default_tpl', 'luna', $overwrite);
$self->set('build_new_cutoff', 7, $overwrite);
$self->set('build_pop_cutoff', 0.01, $overwrite);
$self->set('build_use_backup', 1, $overwrite);
$self->set('db_gen_category_list', 2, $overwrite);
$self->set('add_system_fields', { Hits => 0, isNew => 'No', isPopular => 'No', isChanged => 'No', Status => 0, Rating => 0, Votes => 0 }, $overwrite);
$self->set('build_auto_validate', 0, $overwrite);
$self->set('db_referers', [], $overwrite);
$self->set('links_cols_update_category', 'isPopular, Rating, Votes', $overwrite);
$self->set('protected_vars', [qw/error message secondarynav Meta_Description Meta_Keywords/], $overwrite);
$self->set('build_sort_order_category', "isNew DESC,isPopular DESC,Title", $overwrite);
$self->set('build_sort_paid_first', 1, $overwrite);
$self->set('build_sort_order_new', "Add_Date DESC,Title", $overwrite);
$self->set('build_sort_order_cool', "Title", $overwrite);
$self->set('build_sort_order_editor', "isValidated DESC, Title ASC", $overwrite);
$self->set('build_span_pages', 1, $overwrite);
$self->set('build_links_per_page', 25, $overwrite);
$self->set('build_new_date_span_pages', 1, $overwrite);
$self->set('build_new_gb', 1, $overwrite);
$self->set('build_cool_gb', 1, $overwrite);
$self->set('build_category_sort', 'Name', $overwrite);
$self->set('build_category_yahoo', 1, $overwrite);
$self->set('build_category_columns', 2, $overwrite);
$self->set('build_category_table', 'border=0 width="100%"', $overwrite);
$self->set('dynamic_no_url_transform', ['<%build_static_url%>'], $overwrite);
$self->set('dynamic_pages', 1, $overwrite);
$self->set('dynamic_preserve', ['t', 'd', 's'], $overwrite);
$self->set('dynamic_preserve_sort_pages', ['category', 'detailed'], $overwrite);
$self->set('compress', 0, $overwrite);
$self->set('build_detailed', 0, $overwrite);
$self->set('build_home', '', $overwrite);
$self->set('build_index', 'index.html', $overwrite);
$self->set('build_index_include', 1, $overwrite);
$self->set('build_more', 'more', $overwrite);
$self->set('build_extension', '.html', $overwrite);
$self->set('build_detail_format', '%ID%', $overwrite);
$self->set('build_category_format', '%Full_Name%', $overwrite);
$self->set('build_format_compat', 0, $overwrite);
$self->set('build_category_dynamic', 'Full_Name', $overwrite);
$self->set('build_dir_per', '0777', $overwrite);
$self->set('build_file_per', '0666', $overwrite);
}
sub default_user {
# ------------------------------------------------------------------
# Set the user settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('user_validation', 1, $overwrite);
$self->set('user_required', 1, $overwrite);
$self->set('user_rate_required', 1, $overwrite);
$self->set('user_direct_mod', 0, $overwrite);
$self->set('user_allow_pass', 1, $overwrite);
$self->set('user_sessions', 'Cookies', $overwrite);
$self->set('user_session_length', 3, $overwrite);
$self->set('framed_jump', 0, $overwrite);
$self->set('bookmark_enabled', 1, $overwrite);
$self->set('bookmark_folder_default_name', 'Default', $overwrite);
$self->set('bookmark_folder_limit', 5, $overwrite);
$self->set('bookmark_folders_per_page', 10, $overwrite);
$self->set('bookmark_links_limit', 25, $overwrite);
$self->set('bookmark_links_per_page', 25, $overwrite);
$self->set('bookmark_links_sort', 'Title', $overwrite);
$self->set('bookmark_links_sort_order', 'ASC', $overwrite);
$self->set('bookmark_users_per_page', 25, $overwrite);
$self->set('newsletter_enabled', 0, $overwrite);
$self->set('newsletter_global_subscribe', 0, $overwrite);
$self->set('newsletter_max_depth', 2, $overwrite);
}
sub default_email {
# ------------------------------------------------------------------
# Set the email settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('db_admin_email', '', $overwrite);
$self->set('db_smtp_server', '', $overwrite);
$self->set('db_mail_path', _find_sendmail(), $overwrite);
$self->set('email_add', 1, $overwrite);
$self->set('email_mod', 1, $overwrite);
$self->set('email_review_add', 1, $overwrite);
$self->set('email_payment', 1, $overwrite);
$self->set('admin_email_add', 1, $overwrite);
$self->set('admin_email_mod', 1, $overwrite);
$self->set('admin_email_review_add', 1, $overwrite);
$self->set('admin_email_review_mod', 1, $overwrite);
}
sub default_date {
# ------------------------------------------------------------------
# Set the default settings for any date routines.
#
my ($self, $overwrite) = @_;
$self->set('date_db_format', '%yyyy%-%mm%-%dd%', $overwrite);
$self->set('date_review_format', '%mmm% %d% %yyyy% %h%:%MM%%tt%', $overwrite);
$self->set('date_user_format', '%ddd% %mmm% %dd% %yyyy%', $overwrite);
$self->set('date_long_format', '%dddd%, %mmmm% %dd% %yyyy%', $overwrite);
$self->set('date_expiry_format', '%dddd% %mmm% %d% %yyyy% %h%:%MM% %tt%', $overwrite);
$self->set('date_offset', 0, $overwrite);
$self->set('date_days_short', [qw/Sun Mon Tue Wed Thu Fri Sat/], $overwrite);
$self->set('date_days_long', [qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/], $overwrite);
$self->set('date_month_short', [qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/], $overwrite);
$self->set('date_month_long', [qw/January February March April May June July August September October November December/], $overwrite);
}
sub default_search {
# ------------------------------------------------------------------
# Update the search settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('search_maxhits', 25, $overwrite);
$self->set('search_bool', 'AND', $overwrite);
$self->set('search_substring', 0, $overwrite);
$self->set('build_sort_order_search', "score", $overwrite);
$self->set('build_sort_order_search_cat', "score", $overwrite);
$self->set('build_search_gb', 1, $overwrite);
$self->set('search_blocked', [], $overwrite);
$self->set('search_highlighting', 1, $overwrite);
$self->set('search_highlight_colors', 5, $overwrite);
}
sub default_review {
# ------------------------------------------------------------------
# Update the review settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('user_review_required', 1, $overwrite);
$self->set('review_auto_validate', 0, $overwrite);
$self->set('review_allow_modify', 1, $overwrite);
$self->set('review_modify_timeout', 0, $overwrite);
$self->set('review_max_reviews', 1, $overwrite);
$self->set('reviews_per_page', 5, $overwrite);
$self->set('review_sort_by', 'Review_Date', $overwrite);
$self->set('review_sort_order', 'desc', $overwrite);
$self->set('review_convert_br_tags', 1, $overwrite);
$self->set('review_days_old', 7, $overwrite);
}
sub default_misc {
# ------------------------------------------------------------------
# Set the misc settings to default values.
#
my ($self, $overwrite) = @_;
$self->set('reg_number', '', $overwrite);
$self->set('nph_headers', 1, $overwrite);
$self->set('header_charset', 'ISO-8859-1', $overwrite);
$self->set('debug_level', 0, $overwrite);
$self->set('error_message', '', $overwrite);
$self->set('disabled', 0, $overwrite);
$self->set('bans', [], $overwrite);
$self->set('link_validate_date', 1, $overwrite);
}
sub default_other {
# ------------------------------------------------------------------
# Update settings not available throught the web.
#
my ($self, $overwrite) = @_;
$self->set('private_sessions', 1, $overwrite);
$self->set('db_hit_expire', 2, $overwrite);
$self->set('db_rate_expire', 2, $overwrite);
$self->set('quick_links', {
'admin.cgi?do=page&page=tools_validate.html' => 'Validate Links',
'admin.cgi?do=page&page=tools_validate_changes.html' => 'Validate Changes',
'admin.cgi?do=page&page=tools_validate_reviews.html' => 'Validate Reviews'
}, $overwrite);
}
sub _find_admin_url {
# ------------------------------------------------------------------
# Return base url of current script.
#
my $url = GT::CGI->url({ absolute => 1, query_string => 0 });
$url =~ s,/[^/]*$,,;
return $url;
}
sub _find_cgi_url {
# ------------------------------------------------------------------
# Returns base url of one level back.
#
my $url = _find_admin_url();
$url =~ s,/admin$,,;
return $url;
}
sub _find_pages_url {
# ------------------------------------------------------------------
# Returns pages url.
#
return _find_cgi_url();
}
sub _find_perl {
# ------------------------------------------------------------------
# Returns path to perl.
#
my @poss_perls = qw!
/usr/local/bin/perl /usr/bin/perl /bin/perl
/usr/local/bin/perl5 /usr/bin/perl5 /bin/perl
/perl/bin/perl.exe c:/perl/bin/perl.exe d:/perl/bin/perl.exe
!;
foreach my $perl_path (@poss_perls) {
return $perl_path if -f $perl_path and -x _;
}
return '';
}
sub _find_sendmail {
# ------------------------------------------------------------------
# Looks for sendmail.
#
for my $sendmail (qw|/usr/sbin/sendmail /usr/lib/sendmail /usr/bin/sendmail /bin/sendmail|) {
return $sendmail if -f $sendmail and -x _;
}
return '';
}
1;

View File

@ -0,0 +1,356 @@
# ==================================================================
# Gossamer Links - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/support/
# Updated : Sun May 19 21:05:55 2024
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
{
'add_system_fields' => {
'Hits' => '0',
'Rating' => '0',
'SlideShowCache' => '{}',
'Status' => '0',
'Votes' => '0',
'isChanged' => 'No',
'isNew' => 'No',
'isPopular' => 'No'
},
'admin_email_add' => '1',
'admin_email_mod' => '1',
'admin_email_review_add' => '1',
'admin_email_review_mod' => '1',
'admin_root_path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'admin_root_url' => '/cgi-bin/articles/admin',
'bans' => [],
'bookmark_enabled' => '0',
'bookmark_folder_default_name' => 'Default',
'bookmark_folder_limit' => '5',
'bookmark_folders_per_page' => '25',
'bookmark_links_limit' => '100',
'bookmark_links_per_page' => '25',
'bookmark_links_sort' => 'Title',
'bookmark_links_sort_order' => 'ASC',
'bookmark_users_per_page' => '25',
'build_auto_validate' => '1',
'build_category_columns' => '2',
'build_category_dynamic' => 'Full_Name',
'build_category_format' => '%Full_Name%',
'build_category_sort' => 'Name',
'build_category_table' => 'border=0 width="100%"',
'build_category_yahoo' => '1',
'build_cool_gb' => '1',
'build_cool_path' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'build_cool_url' => '/Cool',
'build_css_url' => '/articles/static/css/links.css',
'build_default_tpl' => 'twitch',
'build_detail_format' => '%Full_Name%/%Title%_%ID%',
'build_detail_path' => '/var/home/slowtwitch/slowtwitch.com/www',
'build_detail_url' => 'https://www.slowtwitch.com',
'build_detailed' => '1',
'build_dir_per' => '0777',
'build_extension' => '.html',
'build_file_per' => '0666',
'build_format_compat' => '2',
'build_home' => '',
'build_images_url' => '/images',
'build_index' => 'index.html',
'build_index_include' => '1',
'build_links_per_page' => '25',
'build_more' => 'more',
'build_new_cutoff' => '7',
'build_new_date_span_pages' => '1',
'build_new_gb' => '1',
'build_new_path' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'build_new_url' => '/New',
'build_pop_cutoff' => '0.01',
'build_ratings_path' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'build_ratings_url' => '/Ratings',
'build_root_path' => '/var/home/slowtwitch/slowtwitch.com/www',
'build_root_url' => 'https://www.slowtwitch.com',
'build_search_gb' => '0',
'build_sort_order_category' => 'Mod_Date DESC, Add_Date DESC,Title ASC',
'build_sort_order_cool' => 'Title',
'build_sort_order_editor' => 'isValidated DESC, Title ASC',
'build_sort_order_new' => 'Add_Date DESC,Title',
'build_sort_order_search' => 'Mod_Date DESC',
'build_sort_order_search_cat' => 'score',
'build_sort_paid_first' => '1',
'build_span_pages' => '1',
'build_static_path' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static',
'build_static_url' => '/articles/static',
'build_use_backup' => '0',
'compress' => '1',
'date_days_long' => [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
],
'date_days_short' => [
'Sun',
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat'
],
'date_db_format' => '%yyyy%-%mm%-%dd%',
'date_expiry_format' => '%dddd% %mmm% %d% %yyyy% %h%:%MM% %tt%',
'date_long_format' => '%dddd%, %mmmm% %dd% %yyyy%',
'date_month_long' => [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
],
'date_month_short' => [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
],
'date_offset' => '0',
'date_review_format' => '%mmm% %d% %yyyy% %h%:%MM%%tt%',
'date_user_format' => '%ddd% %mmm% %dd% %yyyy%',
'db_admin_email' => 'webmaster@slowtwitch.com',
'db_cgi_url' => '/cgi-bin/articles',
'db_cgi_url_https' => '/cgi-bin/articles',
'db_gen_category_list' => '2',
'db_hit_expire' => '2',
'db_mail_path' => '/usr/sbin/sendmail',
'db_rate_expire' => '2',
'db_referers' => [],
'db_smtp_server' => '',
'debug_level' => '0',
'disabled' => '0',
'dynamic_404_status' => '0',
'dynamic_no_url_transform' => [
'<%build_static_url%>'
],
'dynamic_pages' => '1',
'dynamic_preserve' => [
't',
'd',
's'
],
'dynamic_preserve_sort_pages' => [
'category',
'detailed'
],
'email_add' => '1',
'email_mod' => '1',
'email_payment' => '1',
'email_review_add' => '1',
'error_message' => '',
'featured_articles' => [
'8954',
'8953',
'8951',
'8950'
],
'featured_photos' => [
'8823',
'8822'
],
'fileman_root_dir' => '/var/home/slowtwitch/slowtwitch.com',
'framed_jump' => '0',
'header_charset' => 'utf-8',
'last_build' => '1598637754.56255',
'last_clicktrack_cleanup' => '2024-05-17',
'link_validate_date' => '1',
'links_cols_update_category' => 'isPopular, Rating, Votes',
'newsletter_enabled' => '0',
'newsletter_global_subscribe' => '0',
'newsletter_max_depth' => '2',
'nph_headers' => '1',
'path_to_perl' => '/usr/local/bin/perl',
'payment' => {
'auto_validate' => '1',
'description' => '',
'direct' => {
'methods' => {
'AuthorizeDotNet' => {
'module' => 'Links/Payment/Direct/AuthorizeDotNet.pm',
'package' => 'Links::Payment::Direct::AuthorizeDotNet',
'types' => [
'AMEX',
'DINERS',
'DISC',
'JCB',
'MC',
'VISA'
]
},
'Moneris' => {
'module' => 'Links/Payment/Direct/Moneris.pm',
'package' => 'Links::Payment::Direct::Moneris',
'types' => [
'AMEX',
'MC',
'VISA',
'DISC',
'NOVA',
'DINERS'
]
}
},
'used' => {}
},
'discounts' => {},
'enabled' => '0',
'expired_is_free' => '0',
'expiry_notify' => '7',
'mode' => '3',
'postback' => [
{
'method' => 'PayPal',
'type' => 'remote',
'var' => 'txn_type'
},
{
'method' => 'WorldPay',
'type' => 'remote',
'var' => 'transStatus'
},
{
'method' => '2CheckOut',
'type' => 'remote',
'var' => 'cart_order_id'
}
],
'remote' => {
'methods' => {
'2CheckOut' => {
'module' => 'Links/Payment/Remote/2CheckOut.pm',
'package' => 'Links::Payment::Remote::2CheckOut',
'recurring' => '0',
'types' => [
'VISA',
'MC',
'AMEX',
'DISC',
'DINERS',
'JCB'
]
},
'Manual' => {
'module' => 'Links/Payment/Remote/Manual.pm',
'package' => 'Links::Payment::Remote::Manual',
'recurring' => '0',
'types' => [
'MANUAL'
]
},
'PayPal' => {
'module' => 'Links/Payment/Remote/PayPal.pm',
'package' => 'Links::Payment::Remote::PayPal',
'recurring' => '1',
'types' => [
'PAYPAL'
]
},
'WorldPay' => {
'module' => 'Links/Payment/Remote/WorldPay.pm',
'package' => 'Links::Payment::Remote::WorldPay',
'recurring' => '1',
'types' => [
'VISA',
'MC',
'EURO',
'VISA_DEBIT',
'SWITCH',
'SOLO',
'DELTA',
'JCB',
'AMEX',
'DINERS'
]
}
},
'used' => {}
},
'term' => {}
},
'private_sessions' => '1',
'protected_vars' => [
'error',
'message',
'secondarynav',
'Meta_Description',
'Meta_Keywords'
],
'quick_links' => {
'admin.cgi?do=page&page=tools_validate.html' => 'Validate Links',
'admin.cgi?do=page&page=tools_validate_changes.html' => 'Validate Changes',
'admin.cgi?do=page&page=tools_validate_reviews.html' => 'Validate Reviews',
'https://forum.slowtwitch.com/admin/delete_spam_comments.php' => 'Delete Spam',
'https://www.slowtwitch.com/cgi-bin/articles/admin/cullGlist.php' => 'Cull gList',
'https://www.slowtwitch.com/cgi-bin/articles/admin/nph-build.cgi?do=all' => 'Build All',
'https://www.slowtwitch.com/cgi-bin/articles/admin/nph-build.cgi?do=changed' => 'Build Changed',
'https://www.slowtwitch.com/myadminphp/index.php?token=b521b01fa6ed8c9f890b24b148ac74cb' => 'PHPMyAdmin'
},
'reg_number' => 'GL0807-06828-37',
'review_allow_modify' => '1',
'review_auto_validate' => '0',
'review_convert_br_tags' => '1',
'review_days_old' => '7',
'review_max_reviews' => '1',
'review_modify_timeout' => '0',
'review_sort_by' => 'Review_Date',
'review_sort_order' => 'desc',
'reviews_per_page' => '25',
'search_blocked' => [],
'search_bool' => 'AND',
'search_highlight_colors' => '5',
'search_highlighting' => '1',
'search_logging' => '0',
'search_maxhits' => '25',
'search_substring' => '0',
'setup' => '1',
'show_upgrade_rerun' => '0',
'treecats_enabled' => '0',
'updates' => {},
'updates_check_on_home' => '1',
'user_allow_pass' => '1',
'user_cookie_domain' => '',
'user_cookie_prefix' => '',
'user_direct_mod' => '1',
'user_link_validation' => '0',
'user_rate_required' => '1',
'user_required' => '1',
'user_review_required' => '1',
'user_session_length' => '3',
'user_sessions' => 'Cookies',
'user_validation' => '1',
'verify_chunk' => '10',
'verify_max_children' => '3',
'version' => '3.3.0'
};
# vim:syn=perl:ts=4:noet

View File

@ -0,0 +1,701 @@
{
'cache' => {
'time' => '1672499734',
'updates' => [
bless(
{
'deps' => [],
'description' => 'This update fixes a bug in GT::WWW where it would cause link verify to fail with a -4 (Could not connect) error.',
'files' => [
[
'186',
'library',
'',
'0644',
'GT/WWW.pm'
]
],
'id' => '132',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '2',
'title' => 'GT::WWW unresolvable host error fix',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes an error where editors using browser.cgi get an error, "You are not authorized to perform this action," when attempting to modify a link that they should be able to modify.',
'files' => [
[
'187',
'library',
'',
'0644',
'Links/Browser/Controller.pm'
]
],
'id' => '133',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '1',
'title' => 'Editor browser error on link modify',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes "ColumnName cannot contain the value \'\'" error when attempting to modify a link with an INT column (null = yes, no default).',
'files' => [
[
'188',
'library',
'',
'0644',
'Links/User/Modify.pm'
]
],
'id' => '134',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '1',
'title' => 'INT validation error on modify',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes two bugs in the category browser: 1) Error deleting a link on the user side category browser (ie. not the admin one) from search results when the category depth is greater than 1. 2) When the link_validate_date option was turned on, Add_Date was getting set to the current date on modify.',
'files' => [
[
'189',
'library',
'',
'0644',
'Links/Browser.pm'
]
],
'id' => '135',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [
'140'
],
'reversible' => '1',
'severity' => '1',
'title' => 'Category Browser fixes',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes a bug in the payment code where the name and e-mail address of the user aren\'t correctly passed to the link_expired.eml and link_expiry_notify.eml e-mail templates.',
'files' => [
[
'190',
'library',
'',
'0644',
'Links/Payment.pm'
]
],
'id' => '136',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '1',
'title' => 'Broken payment link expiry e-mails',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes the "defined(%hash) is deprecated" warnings that occur when using Gossamer Links with Perl 5.12 and higher.',
'files' => [
[
'191',
'library',
'',
'0644',
'bases.pm'
],
[
'192',
'library',
'',
'0644',
'GT/MIMETypes.pm'
],
[
'194',
'library',
'',
'0644',
'GT/SQL/Base.pm'
],
[
'193',
'library',
'',
'0644',
'GT/Template.pm'
]
],
'id' => '137',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '1',
'title' => '"defined(%hash) is deprecated" warning',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes an issue with the template parser if there were extra spaces after the "and" in an "if" condition.',
'files' => [
[
'195',
'library',
'',
'0644',
'GT/Template/Parser.pm'
]
],
'id' => '138',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '1',
'title' => 'Template parser syntax error',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [],
'description' => 'This update fixes a bug where some files (eg. ones with parenthesis in them) uploaded to FILE columns would trigger an ILLEGALCHARS error.',
'files' => [
[
'196',
'library',
'',
'0644',
'GT/SQL/File.pm'
]
],
'id' => '139',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '1',
'title' => 'Filename errors with uploaded files',
'unique' => '0'
},
'GT::Update::Update'
),
bless(
{
'deps' => [
'135'
],
'description' => 'This update fixes a bug in the last category browser update where deleting a link that\'s in multiple categories deletes the link from all categories.',
'files' => [
[
'197',
'library',
'',
'0644',
'Links/Browser.pm'
]
],
'id' => '140',
'installed' => '1',
'paths' => {
'fixed' => {
'build' => '/var/home/slowtwitch/slowtwitch.com/www',
'cool' => '/var/home/slowtwitch/slowtwitch.com/www/Cool',
'detail' => '/var/home/slowtwitch/slowtwitch.com/www',
'new' => '/var/home/slowtwitch/slowtwitch.com/www/New',
'ratings' => '/var/home/slowtwitch/slowtwitch.com/www/Ratings',
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'library' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
},
'script' => {
'admin' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin',
'cgi' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles'
},
'static' => {
'static' => '/var/home/slowtwitch/slowtwitch.com/www/articles/static'
},
'template' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/templates'
},
'version' => {
'' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin'
}
},
'revdeps' => [],
'reversible' => '1',
'severity' => '2',
'title' => 'Category Browser fixes fix',
'unique' => '0'
},
'GT::Update::Update'
)
],
'version' => '1.1'
},
'installed' => {
'3.3.0' => {
'132' => {
'deps' => [],
'description' => 'This update fixes a bug in GT::WWW where it would cause link verify to fail with a -4 (Could not connect) error.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/132-1254088697-library--GT-WWW.pm.backup',
'dir' => '',
'file' => 'GT/WWW.pm',
'id' => '186',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/GT/WWW.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1254088697',
'reversible' => '1',
'severity' => '2',
'title' => 'GT::WWW unresolvable host error fix',
'unique' => '0'
},
'133' => {
'deps' => [],
'description' => 'This update fixes an error where editors using browser.cgi get an error, "You are not authorized to perform this action," when attempting to modify a link that they should be able to modify.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/133-1254088697-library--Links-Browser-Controller.pm.backup',
'dir' => '',
'file' => 'Links/Browser/Controller.pm',
'id' => '187',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/Links/Browser/Controller.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1254088697',
'reversible' => '1',
'severity' => '1',
'title' => 'Editor browser error on link modify',
'unique' => '0'
},
'134' => {
'deps' => [],
'description' => 'This update fixes "ColumnName cannot contain the value \'\'" error when attempting to modify a link with an INT column (null = yes, no default).',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/134-1362433281-library--Links-User-Modify.pm.backup',
'dir' => '',
'file' => 'Links/User/Modify.pm',
'id' => '188',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/Links/User/Modify.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '1',
'title' => 'INT validation error on modify',
'unique' => '0'
},
'135' => {
'deps' => [],
'description' => 'This update fixes two bugs in the category browser: 1) Error deleting a link on the user side category browser (ie. not the admin one) from search results when the category depth is greater than 1. 2) When the link_validate_date option was turned on, Add_Date was getting set to the current date on modify.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/135-1362433281-library--Links-Browser.pm.backup',
'dir' => '',
'file' => 'Links/Browser.pm',
'id' => '189',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/Links/Browser.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '1',
'title' => 'Category Browser fixes',
'unique' => '0'
},
'136' => {
'deps' => [],
'description' => 'This update fixes a bug in the payment code where the name and e-mail address of the user aren\'t correctly passed to the link_expired.eml and link_expiry_notify.eml e-mail templates.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/136-1362433281-library--Links-Payment.pm.backup',
'dir' => '',
'file' => 'Links/Payment.pm',
'id' => '190',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/Links/Payment.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '1',
'title' => 'Broken payment link expiry e-mails',
'unique' => '0'
},
'137' => {
'deps' => [],
'description' => 'This update fixes the "defined(%hash) is deprecated" warnings that occur when using Gossamer Links with Perl 5.12 and higher.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/137-1362433281-library--bases.pm.backup',
'dir' => '',
'file' => 'bases.pm',
'id' => '191',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/bases.pm',
'subtype' => '',
'type' => 'library'
},
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/137-1362433281-library--GT-MIMETypes.pm.backup',
'dir' => '',
'file' => 'GT/MIMETypes.pm',
'id' => '192',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/GT/MIMETypes.pm',
'subtype' => '',
'type' => 'library'
},
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/137-1362433281-library--GT-SQL-Base.pm.backup',
'dir' => '',
'file' => 'GT/SQL/Base.pm',
'id' => '194',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/GT/SQL/Base.pm',
'subtype' => '',
'type' => 'library'
},
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/137-1362433281-library--GT-Template.pm.backup',
'dir' => '',
'file' => 'GT/Template.pm',
'id' => '193',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/GT/Template.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '1',
'title' => '"defined(%hash) is deprecated" warning',
'unique' => '0'
},
'138' => {
'deps' => [],
'description' => 'This update fixes an issue with the template parser if there were extra spaces after the "and" in an "if" condition.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/138-1362433281-library--GT-Template-Parser.pm.backup',
'dir' => '',
'file' => 'GT/Template/Parser.pm',
'id' => '195',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/GT/Template/Parser.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '1',
'title' => 'Template parser syntax error',
'unique' => '0'
},
'139' => {
'deps' => [],
'description' => 'This update fixes a bug where some files (eg. ones with parenthesis in them) uploaded to FILE columns would trigger an ILLEGALCHARS error.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/139-1362433281-library--GT-SQL-File.pm.backup',
'dir' => '',
'file' => 'GT/SQL/File.pm',
'id' => '196',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/GT/SQL/File.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '1',
'title' => 'Filename errors with uploaded files',
'unique' => '0'
},
'140' => {
'deps' => [
'135'
],
'description' => 'This update fixes a bug in the last category browser update where deleting a link that\'s in multiple categories deletes the link from all categories.',
'files' => [
{
'backup' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/updates/140-1362433281-library--Links-Browser.pm.backup',
'dir' => '',
'file' => 'Links/Browser.pm',
'id' => '197',
'mode' => '0644',
'path' => '/var/home/slowtwitch/slowtwitch.com/cgi-bin/articles/admin/Links/Browser.pm',
'subtype' => '',
'type' => 'library'
}
],
'installed' => '1362433281',
'reversible' => '1',
'severity' => '2',
'title' => 'Category Browser fixes fix',
'unique' => '0'
}
}
}
};
# vim:syn=perl:ts=4:noet

View File

@ -0,0 +1,30 @@
# ==================================================================
# Gossamer Links - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# CVS Info : 087,068,085,094,083
# Revision : $Id: Custom.pm,v 1.3 2005/03/05 01:29:09 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.
# ==================================================================
#
# Description:
# By default, this file is empty, however it is here to allow installations
# to perform special operations required to make Gossamer Links load.
# For example, some installations might need a 'use lib' line to work
# properly.
#
# This file will NOT be overwritten when upgrading your installation, so you
# do not need to worry about additions made here being overwritten. This is
# generally loaded after Links.pm has started loading, but before any other
# modules are loaded.
#
package Links::Custom;
1; # This must remain at the bottom of the file.

View File

@ -0,0 +1,170 @@
# ==================================================================
# 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: Category.pm,v 1.14 2007/09/25 06:19:32 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::HTML::Category;
# ==================================================================
# Handles displaying of forms and HTML.
#
use strict;
use vars qw/@ISA/;
use Links qw/:payment :objects/;
use GT::SQL::Display::HTML::Table;
@ISA = qw/GT::SQL::Display::HTML::Table/;
my $FORM_HIDE = 'add_form|modify_form|modify_multi_search_results|modify_multi_result_changed|modify_multi_results_err';
my $FORM_HIDE_FIELDS = [qw/Full_Name Number_of_Links Direct_Links Has_New_Links Has_Changed_Links Newest_Link Timestmp/];
sub display {
# -------------------------------------------------------------------
# Displays a category, but passes through the plugin system.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('display_category', sub { return $self->_plg_display(@_); }, $p);
}
sub form {
# -------------------------------------------------------------------
# Displays a category form, but passes through the plugin system.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('form_category', sub { return $self->_plg_form(@_); }, $p);
}
sub _plg_display {
# -------------------------------------------------------------------
# Displays a record.
#
my ($self, $opts) = @_;
$opts->{hide} ||= [];
if (!exists $opts->{code}->{FatherID} and !grep { $_ eq 'FatherID' } @{$opts->{hide}}) {
$opts->{code}->{FatherID} = \&disp_fatherid_html
}
push @{$opts->{hide}}, qw/Full_Name/;
$CFG->{payment}->{enabled} or push @{$opts->{hide}}, 'Payment_Mode';
my $out = $self->SUPER::display($opts);
my $id = $opts->{values}->{ID};
if ($CFG->{payment}->{enabled} and $id and $opts->{values}->{Payment_Mode} >= OPTIONAL) {
my $font = $self->{font};
$out .= qq~
<p><table border=1 cellpadding=0 bgcolor="#FFFFFF" cellspacing=0 width="500"><tr><td>
<table border=0 bgcolor="#FFFFFF" width="500"><tr>
<td width="100%" valign="top" align="center"><font $font><a href="admin.cgi?do=page;page=payment_cat_price.html;ID=$id;not_global=1">Add/Update payment terms for this category</a></td>
</tr></table>
</td></tr></table>
~;
}
return $out;
}
sub _plg_form {
# -------------------------------------------------------------------
# Displays a form.
#
my ($self, $opts) = @_;
$opts->{hide} ||= [];
if ($opts->{mode} and $opts->{mode} =~ /$FORM_HIDE/o) {
push @{$opts->{hide}}, @{$FORM_HIDE_FIELDS};
}
if (!exists $opts->{code}->{FatherID} and !grep { $_ eq 'FatherID' } @{$opts->{hide}}) {
$opts->{code}->{FatherID} = \&disp_fatherid_form;
}
$CFG->{payment}->{enabled} or push @{$opts->{hide}}, 'Payment_Mode';
return $self->SUPER::form($opts);
}
sub select {
# -------------------------------------------------------------------
# Override Payment_Mode select field in add form.
#
my ($self, $opts) = @_;
$CFG->{payment}->{enabled} and $opts->{name} eq 'Payment_Mode'
and $self->{input}->{do} and ($self->{input}->{do} eq 'add_form' or $self->{input}->{do} eq 'modify_form')
and $opts->{blank} = 0;
return $self->SUPER::select($opts);
}
sub disp_fatherid_form {
# -------------------------------------------------------------------
# Display the list of subcategories as either a drop down list of a text box.
#
my ($self, $col, $rec) = @_;
my $font = $self->{font};
my $out;
my $form_name = $self->{multiple} ? "$self->{multiple}-FatherID" : 'FatherID';
if ($CFG->{db_gen_category_list} == 2) {
if ($rec->{FatherID}) {
$out .= qq|<input type="hidden" name="FatherID" value="$rec->{FatherID}" />|;
}
$out .= qq|<script type="text/javascript" src="$CFG->{build_static_url}/treecats.js"></script>
<link type="text/css" rel="stylesheet" href="$CFG->{build_static_url}/admin/treecats.css" />
<input type="hidden" name="FatherID-opt" value="=" />
<tr><td valign="top"><font $font>Subcategory of</font></td><td><font $font><div id="treecats"></div></font></td></tr>
<script type="text/javascript">var tc = new treecats({ selectionRequired : false, inputName : 'FatherID', cgiURL : '$CFG->{db_cgi_url}', imageURL : '$CFG->{build_static_url}/admin' }, { noSelection : 'Root', rootText : 'Root' }); tc.load();</script>\n|;
}
elsif ($CFG->{db_gen_category_list}) {
my $sth = $self->{db}->select(["DISTINCT Full_Name, ID"]);
my %names;
if ($sth) {
while (my ($name, $id) = $sth->fetchrow_array) {
$names{$id} = $name;
}
}
$names{0} = '--Root--';
my $select = $self->select({ name => $form_name, values => \%names, blank => 1, sort => sub { lc $_[0] cmp lc $_[1] }, value => defined $rec->{FatherID} ? $rec->{FatherID} : "" });
$out = qq~
<tr><td valign=top><font $font>Subcategory of</font></td><td><font $font>$select<input type=hidden name="FatherID-opt" value="="></td></tr>
~;
}
else {
my $value = $rec->{FatherID} || '';
if ($value =~ /^\d+$/) {
my $sth = $self->{db}->select('Full_Name', { ID => $value });
if ($sth) {
($value) = $sth->fetchrow_array;
}
}
$out = qq~
<tr><td><font $font>Full Sub Category<br><font size=1>Separated with /'s</font></font></td><td><input type=text size="40" name="$form_name" value="$value"></td></tr>
~;
}
return $out;
}
sub disp_fatherid_html {
# -------------------------------------------------------------------
# Display the father.
#
my ($self, $col, $rec) = @_;
my ($parent) = $rec->{Full_Name} =~ m,^(.*)/[^/]+$,;
my $font = $self->{font};
$parent ||= 'Root';
return qq~
<tr><td><font $font>Subcategory of</font></td><td><font $font>$parent</td></tr>
~;
}
1;

View File

@ -0,0 +1,409 @@
# ==================================================================
# 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: Links.pm,v 1.25 2007/11/14 02:40:26 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::HTML::Links;
# ==================================================================
# Handles displaying of forms and HTML.
#
use strict;
use vars qw/@ISA/;
use GT::SQL::Display::HTML::Table;
use Links qw/:payment :objects/;
@ISA = qw/GT::SQL::Display::HTML::Table/;
my $FORM_HIDE = '^(add_form|modify_form|modify_multi_search_results|modify_multi_result_changed|modify_multi_results_err|validate)$';
my $FORM_HIDE_FIELDS = [qw/isNew isChanged isPopular Status Date_Checked/];
my $SHOW_CAT_LIST = '^(search_results|add_success|delete_search_results|modify_search_results|modify_success|modify_multi_search_results|modify_multi_results_norec)$';
my $SHOW_CAT_FORM = '^(search_form|add_form|delete_search_form|modify_form|modify_search_form|modify_multi_search_results|modify_multi_result_changed|modify_multi_results_err|validate)$';
sub display {
# -------------------------------------------------------------------
# Displays a link, but passes through the plugin system.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('display_link', sub { return $self->_plg_display (@_); }, $p );
}
sub form {
# -------------------------------------------------------------------
# Displays a link form, but passes through the plugin system.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('form_link', sub { return $self->_plg_form (@_); }, $p );
}
sub _plg_display {
# -------------------------------------------------------------------
# Displays a record.
#
my ($self, $opts) = @_;
$opts->{code}->{LinkOwner} ||= \&disp_username;
$opts->{code}->{ExpiryDate} ||= \&disp_expiry;
my $hidden = sub { '' };
$opts->{code}->{ExpiryCounted} ||= $hidden;
$opts->{code}->{ExpiryNotify} ||= $hidden;
$opts->{code}->{LinkExpired} ||= $hidden;
my $out = $self->SUPER::display($opts);
if ($opts->{mode} =~ /$SHOW_CAT_LIST/o) {
my $id = $opts->{values}->{ID};
if ($id) {
my $font = $self->{font};
my $output = $self->disp_categories($id);
$out .= qq~
<p><table border=1 cellpadding=0 bgcolor="#FFFFFF" cellspacing=0 width="500"><tr><td>
<table border=0 bgcolor="#FFFFFF" width="500"><tr>
<td width="20%" valign="top"><font $font>Categories</td>
<td width="80%"><font $font>$output</td>
</tr></table>
</td></tr></table>
~;
}
}
return $out;
}
sub _plg_form {
# -------------------------------------------------------------------
# Displays a form.
#
my ($self, $opts) = @_;
my $link_id = $opts->{values}->{ID} || $self->{input}->{ID};
# Hide fields we don't want to show on add/modify forms.
if ($opts->{mode} and $opts->{mode} =~ /$FORM_HIDE/o) {
$opts->{hide} ||= [];
push @{$opts->{hide}}, @{$FORM_HIDE_FIELDS};
}
$opts->{code}->{ExpiryDate} ||= \&form_expiry;
# Add javascript to display the original values for text/textarea columns
if ($opts->{show_diff} and $link_id) {
my $current = $DB->table('Links')->select({ ID => $link_id })->fetchrow_hashref;
my $cols = $DB->table('Links')->cols;
my $textarea = sub {
my ($self, $opts, $values, $col) = @_;
my $field_name = $self->{multiple} ? "$self->{multiple}-$col" : $col;
my $display_name = (exists $self->{cols}->{$col}->{form_display} and length $self->{cols}->{$col}->{form_display})
? $self->{cols}->{$col}->{form_display} : $col;
my $value = $values->{$col};
my $disp = $opts->{form_type} eq 'TEXT' ? 'text' : 'textarea';
my $ret = qq|<tr $self->{tr}><td $self->{td} width='30%'><font $self->{col_font}><a href="javascript:toggleOriginal('$field_name-original')" title="Show/Hide original $display_name value">$display_name</a></font></td><td $self->{td} width='70%'><font $self->{val_font}>|;
$ret .= $self->$disp({ name => $field_name, def => $opts, value => (defined $value ? $value : '')});
$ret .= qq|</font></td></tr>\n<tr id="$field_name-original" style="display: none" $self->{tr}><td $self->{td} width="30%"><font $self->{col_font}>Original $display_name</font></td><td $self->{td} width="70%"><font $self->{val_font}>|;
if ($opts->{form_type} eq 'TEXT') {
$ret .= qq|<input type="text" value="$current->{$col}" size="| . ($opts->{form_size} || 20) . qq|" readonly="readonly" />|;
}
else {
my ($cols, $rows) = ref $opts->{form_size} ? (@{$opts->{form_size}}) : ($opts->{form_size} || 20, 4);
$ret .= qq|<textarea rows="$rows" cols="$cols" readonly="readonly">$current->{$col}</textarea>|
}
$ret .= "</font></td></tr>\n";
};
COL: for my $col (keys %$current) {
next if !$cols->{$col}->{form_type} or ($cols->{$col}->{form_type} ne 'TEXT' and $cols->{$col}->{form_type} ne 'TEXTAREA');
# Skip hidden fields
for (@{$opts->{hide}}) {
next COL if $_ eq $col;
}
if ((not defined $opts->{values}->{$col} or $current->{$col} ne $opts->{values}->{$col}) and not $opts->{code}->{$col}) {
$opts->{code}->{$col} = $textarea;
}
}
}
# Display the form.
my $out = $self->SUPER::form($opts);
# Display the category select box.
if ($opts->{mode} and $opts->{mode} =~ /$SHOW_CAT_FORM/o) {
my $name = $opts->{multiple} ? "$opts->{multiple}-CatLinks.CategoryID" : 'CatLinks.CategoryID';
my $id = $opts->{values}->{$name} || $self->{input}->{$name};
$id = (ref $id eq 'ARRAY') ? $id : $id ? [$id] : [];
my $font = $self->{font};
my ($output, $h);
# Add javascript to display the original categories
my $cats_modified;
if ($opts->{show_diff} and @$id and $link_id) {
my $ccl = $DB->table('Category', 'CatLinks');
$ccl->select_options("ORDER BY CategoryID");
my $sth = $ccl->select('CategoryID', 'Full_Name', { LinkID => $link_id });
my (@cid, @cats);
while (my $cat = $sth->fetchrow_hashref) {
push @cid, $cat->{CategoryID};
push @cats, $cat->{Full_Name};
}
if (@$id == @cid) {
my @sorted = sort { $a > $b } @$id;
for (my $i = 0; $i < @cid; $i++) {
if ($cid[$i] != $sorted[$i]) {
$cats_modified = join "\n", sort @cats;
last;
}
}
}
else {
$cats_modified = join "\n", sort @cats;
}
}
# Display the category using treecats
if ($CFG->{db_gen_category_list} == 2) {
my $name = $opts->{multiple} ? "$opts->{multiple}-CatLinks.CategoryID" : 'CatLinks.CategoryID';
my $jsname = $opts->{multiple} ? "tc$opts->{multiple}" : 'tc';
if (!@$id and $link_id) {
$h = $self->{db}->get_categories($link_id);
for (keys %$h) {
push @$id, $_;
}
}
$out .= qq~<p><table border=1 cellpadding=0 bgcolor="#FFFFFF" cellspacing=0><tr><td>~ unless exists $opts->{extra_table} and $opts->{extra_table} == 0;
$out .= qq~
<table border=0 bgcolor="#FFFFFF" width="500"><tr>
<td width="30%" valign="top"><font $font>~;
$out .= qq|<a href="javascript:toggleOriginal('$name-original')" title="Show/Hide original Category value">| if $cats_modified;
$out .= "Categories";
$out .= qq|</a>| if $cats_modified;
for (@$id) {
$out .= qq|<input type="hidden" name="$name" value="$_" />|;
}
$out .= qq~</td>
<td>
<script type="text/javascript" src="$CFG->{build_static_url}/treecats.js"></script>
<link type="text/css" rel="stylesheet" href="$CFG->{build_static_url}/admin/treecats.css" />
<font $font><div id="$jsname"></div></font>
<script type="text/javascript">var $jsname = new treecats({ workspace : '$jsname', objName : '$jsname', inputName : '$name', selectionMode : 'multiple', cgiURL : '$CFG->{db_cgi_url}', imageURL : '$CFG->{build_static_url}/admin' }); $jsname.load();</script>
</td>
</tr></table>
~;
$out .= qq~</p></td></tr></table>~ unless exists $opts->{extra_table} and $opts->{extra_table} == 0;
}
# Display category as a select box.
elsif ($CFG->{db_gen_category_list}) {
if (!@$id and $link_id) {
$h = $self->{db}->get_categories($link_id);
$output = $self->get_categories_with_html([keys %$h], $name);
}
else {
$output = $self->get_categories_with_html($id, $name);
}
$out .= "<p>";
$out .= qq~<table border=1 cellpadding=0 bgcolor="#FFFFFF" cellspacing=0><tr><td>~ unless exists $opts->{extra_table} and $opts->{extra_table} == 0;
$out .= qq~
<table border=0 bgcolor="#FFFFFF" width="500"><tr>
<td width="20%" valign="top"><font $font>~;
$out .= qq|<a href="javascript:toggleOriginal('$name-original')" title="Show/Hide original Category value">| if $cats_modified;
$out .= "Categories";
$out .= qq|</a>| if $cats_modified;
$out .= qq~</td>
<td width="80%"><font $font>$output</td>
</tr></table>
~;
$out .= qq~</td></tr></table>~ unless exists $opts->{extra_table} and $opts->{extra_table} == 0;
}
# Display category as a textarea box.
else {
my ($vals);
if (@$id) {
my $db = $DB->table('Category');
foreach (@$id) {
if (/^\d+$/) {
$vals .= $db->get_name_from_id($_) . "\n";
}
else {
$vals .= $_ . "\n";
}
}
}
elsif ($link_id) {
$h = $self->{db}->get_categories($link_id);
$vals = join("\n", sort values %$h);
}
else {
$vals = '';
}
$out .= "<p>";
$out .= qq~<table border=1 cellpadding=0 bgcolor="#FFFFFF" cellspacing=0><tr><td>~ unless exists $opts->{extra_table} and $opts->{extra_table} == 0;
$out .= qq~
<table border=0 bgcolor="#FFFFFF" width="500"><tr>
<td width="20%" valign="top"><font $font>~;
$out .= qq|<a href="javascript:toggleOriginal('$name-original')" title="Show/Hide original Category value">| if $cats_modified;
$out .= "Categories";
$out .= qq|</a>| if $cats_modified;
$out .= qq~<br><font size=1>One per line</font></td>
<td width="80%"><font $font><textarea rows="3" cols="50" name="$name">$vals</textarea></td>
</tr></table>
~;
$out .= qq~</td></tr></table>~ unless exists $opts->{extra_table} and $opts->{extra_table} == 0;
}
if ($cats_modified) {
$out .= qq~
<table border=0 bgcolor="#FFFFFF" width="500" id="$name-original" style="display: none"><tr>
<td width="20%" valign="top"><font $font>Original Categories</font></td>
<td width="80%"><font $font><textarea rows="3" cols="50" readonly="readonly">$cats_modified</textarea></td>
</tr></table>
~;
}
}
return $out;
}
sub disp_username {
# -------------------------------------------------------------------
# Display the username with links to edit.
#
my ($self, $col, $rec) = @_;
my $val = $rec->{LinkOwner};
my $val_e = GT::CGI->escape($val);
my $font = $self->{font};
return qq~
<tr><td><font $font>$col->{form_display}</font></td><td><font $font>$val <font size=1><a href="admin.cgi?db=Users&do=modify_form&modify=1&1-Username=$val_e&ww=1">edit</a></font></font></td></tr>
~;
}
sub disp_categories {
# -------------------------------------------------------------------
# Displays a list of categories for the form.
#
my $self = shift;
my $id = shift;
my $cat = $self->{db}->get_categories ($id);
my $out = '';
foreach my $id (sort { lc $cat->{$a} cmp lc $cat->{$b} } keys %$cat) {
$out .= "$id: $cat->{$id}<br>\n";
}
return $out;
}
sub disp_expiry {
# -------------------------------------------------------------------
#
my ($self, $col, $rec) = @_;
my $val = $rec->{ExpiryDate};
my $name = $col->{form_display};
my $font = $self->{font};
my $td = $self->{td};
my $out = qq|<tr><td $td><font $font>$name</font></td><td $td><font $font>|;
if ($val == UNLIMITED) {
$out .= "<i>Never</i>";
}
elsif ($val == UNPAID) {
$out .= "<i>Awaiting Payment</i>";
}
elsif ($val == FREE) {
$out .= "<i>No Payment Required (free)";
if ($rec->{LinkExpired}) {
require GT::Date;
$out .= " - Payment Expired " . GT::Date::date_get($rec->{LinkExpired}, '%yyyy%/%m%/%d% %HH%:%MM%:%ss%');
}
$out .= "</i>";
}
elsif ($val == 0) {
$out .= "<i>Invalid Date (0)!</i>";
}
else {
require GT::Date;
$out .= GT::Date::date_get($val, '%yyyy%/%m%/%d% %HH%:%MM%:%ss%');
}
$out .= qq|</font></td>|;
return $out;
}
sub form_expiry {
# -------------------------------------------------------------------
#
my ($self, $col, $rec) = @_;
require GT::Date;
my $val = $rec->{ExpiryDate};
my $name = $col->{form_display};
my $font = $self->{font};
my $td = $self->{td};
my $got_date = $val && $val < UNLIMITED && $val > 0;
($got_date and $val !~ m|^\d+$|) and $val = Links::date_to_time($val);
my $multiple = $self->{multiple} ? "$self->{multiple}-" : '';
$name .= '<br><i><font size=-2>Dates can be entered in the following formats: YYYY-MM-DD, YYYY/MM/DD, YYYY/MM/DD HH:MM:SS</font></i>';
my $out = qq|<tr><td $td><font $font>$name</font></td><td $td><font $font><input type="hidden" name="${multiple}ExpiryDate" value="$val" id="${multiple}ExpiryDate">|;
$out .= qq|<input type="radio" name="${multiple}pe_radio" onclick="document.getElementById('${multiple}ExpiryDate').value = document.getElementById('${multiple}pe_date').value"|;
$out .= qq| checked| if $got_date;
$out .= qq|><input type="text" name="${multiple}pe_date" id="${multiple}pe_date" onchange="document.getElementById('${multiple}ExpiryDate').value = document.getElementById('${multiple}pe_date').value"|;
$out .= qq| value="| . GT::Date::date_get($val, '%yyyy%/%m%/%d% %HH%:%MM%:%ss%') . qq|"| if $got_date;
$out .= qq|><br><input type="radio" name="${multiple}pe_radio" onclick="document.getElementById('${multiple}ExpiryDate').value = | . UNLIMITED . qq|"|;
$out .= qq| checked| if $val && $val == UNLIMITED;
$out .= qq|> Never<br><input type="radio" name="${multiple}pe_radio" onclick="document.getElementById('${multiple}ExpiryDate').value = | . UNPAID . qq|"|;
$out .= qq| checked| if $val && $val == UNPAID;
$out .= qq|> Awaiting Payment<br><input type="radio" name="${multiple}pe_radio" onclick="document.getElementById('${multiple}ExpiryDate').value = | . FREE . qq|"|;
$out .= qq| checked| if $val && $val == FREE || !$val;
$out .= qq|> No Payment Required (free)|;
$out .= qq| - Expired | . GT::Date::date_get($rec->{LinkExpired}, '%yyyy%/%m%/%d% %HH%:%MM%:%ss%') if $val and $val == FREE and $rec->{LinkExpired};
$out .= qq|</font></td>|;
if ($self->{mode} =~ /search/ or (exists $self->{input}->{action} and $self->{input}->{action} =~ /search/)) { # Hack to get this to show up on the Browser search
$out .= qq|<td $td><select name="${multiple}ExpiryDate-opt"><option value="=">Exact Match</option><option value="&gt;">Greater Than</option><option value="&lt;">Less Than</option><option value="&lt;&gt;">Not Equal</option></select></td>|;
}
return $out;
}
sub get_all_categories {
# -------------------------------------------------------------------
# Returns a select box of all categories.
#
my $self = shift;
my $id = shift;
my $name = shift || 'CatLinks.CategoryID';
my $mult = shift || 5;
my $db = $DB->table ('Category');
my $sth = $db->select ( ['ID', 'Full_Name'] );
my %res = ();
while (my ($id, $name) = $sth->fetchrow_array) {
$res{$id} = $name;
}
return $self->select ( { name => $name, values => \%res, value => $id, blank => 0, multiple => $mult, sort => sub { lc $_[0] cmp lc $_[1] } } );
}
sub get_categories_with_html {
# -------------------------------------------------------------------
# Returns select list, and adds which categories are selected as text.
#
my ($self, @param) = @_;
my $select = $self->get_all_categories(@param);
my $output = '';
my @vals = ref $param[0] ? @{$param[0]} : ($param[0]);
if (@vals) {
my $db = $DB->table ('Category');
foreach my $id (@vals) {
next unless ($id and $id =~ /^\d+$/);
my $name_r = $db->get ($id, 'ARRAY', ['Full_Name']);
$output .= $name_r->[0] . "<BR>";
}
}
$output .= $select;
return $output;
}
1;

View File

@ -0,0 +1,101 @@
# ==================================================================
# 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: Users.pm,v 1.4 2007/03/22 22:05:44 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::HTML::Users;
# ==================================================================
# Handles displaying of forms and HTML.
#
use strict;
use vars qw/@ISA/;
use Links qw/:objects/;
use GT::SQL::Display::HTML::Table;
@ISA = qw/GT::SQL::Display::HTML::Table/;
sub display {
# -------------------------------------------------------------------
# Displays a link, but passes through the plugin system.
#
my $self = shift;
my $p = ref $_[0] eq 'HASH' ? shift : {@_};
$PLG->dispatch('display_user', sub { $self->SUPER::display(@_) }, $p);
}
sub form {
# -------------------------------------------------------------------
# Displays a user form, but passes through the plugin system.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('form_user', sub { return $self->SUPER::form(@_) }, $p);
}
sub _display {
# -------------------------------------------------------------------
# Adds on a box with quick links to the users links.
#
my ($self, $opts) = @_;
my $user = $opts->{values}->{Username};
my $output = '';
# If we are modifying, then add a hidden field for the original record.
if ($opts->{mode} eq 'modify_form') {
$opts->{code}->{Username} ||= \&disp_username;
my $user_q = GT::CGI->html_escape($user);
$output .= qq~<input type="hidden" name="orig_username" value="$user_q">~;
}
else {
delete $self->{code}->{Username};
}
$output .= $self->SUPER::_display($opts);
if ($user) {
my $link_db = $DB->table('Links');
my $count = $link_db->count({ LinkOwner => $user });
my $url = GT::CGI->url({ query_string => 0 });
my $user_q = GT::CGI->escape($user);
$output .= <<HTML;
<p>
<table border=1 cellpadding=0 bgcolor="#FFFFFF" cellspacing=0 width="500"><tr><td>
<table border=0 bgcolor="#FFFFFF" width="500"><tr>
<td><font face="Tahoma,Arial,Helvetica" size="2">
Links ($count):
<a href="$url?db=Links&do=search_results&LinkOwner=$user_q&ww=1">View</a> |
<a href="$url?db=Links&do=modify_search_results&LinkOwner=$user_q&ww=1">Modify</a> |
<a href="$url?db=Links&do=delete_search_results&LinkOwner=$user_q&ww=1">Delete</a>
</font></td>
</tr></table>
</td></tr></table>
HTML
}
return $output;
}
sub disp_username {
# -------------------------------------------------------------------
# Display the username with links to edit.
#
my ($self, $col, $rec) = @_;
my $val = $rec->{Username};
my $val_e = GT::CGI->html_escape($val);
my $font = $self->{font};
return <<HTML;
<tr>
<td><font $font>Username</font></td>
<td><font $font><input type="text" name="Username" value="$val_e" size="20"></font></td>
</tr>
HTML
}
1;

View File

@ -0,0 +1,183 @@
# ==================================================================
# 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: BKS2.pm,v 1.14 2005/03/05 01:46:07 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::Import::BKS2;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use GT::SQL;
sub critical {
$Critical_Code->(@_) if ref $Critical_Code eq 'CODE';
}
sub warning {
$Warning_Code->(@_) if ref $Warning_Code eq 'CODE';
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
my $prefix = $DB->prefix || "";
my $odbc = 0;
my $e_dbh;
{
my $table = $DB->table("Links");
$table->connect();
$e_dbh = $table->{driver}->connect();
if ($table->{connect}->{driver} eq 'ODBC') {
$odbc = 1;
$e_dbh->{LongReadLen} = 1000000;
}
}
my $delimiter;
local (*IMPORT_FH);
local $/ = "\0"; # "Lines" are actually delimited by \0 (hex & ascii 0)
import_print "Verifying table headers ...\n";
my $all_problems = "";
open IMPORT_FH, "<$$opt{source}" or critical "Unable to open $$opt{source}: $!";
binmode IMPORT_FH; # Don't want to worry about windows line feeds!
while (<IMPORT_FH>) {
last if substr($_,0,2) eq '\\\\';
} # Eat up until a \\
while (<IMPORT_FH>) {
chomp;
my $table = $_;
import_print "\tChecking $table\n";
my $has_problems = 0;
TABLE: while (<IMPORT_FH>) {
chomp;
my $header = $_;
my $delimiter = substr($header,0,1);
substr($header,0,1) = '';
my @cols = map BK_unescape($_,$delimiter), split /\Q$delimiter/, $header, -1;
my %cols = $DB->table($table)->cols;
my $problem = "";
for (grep !$cols{$_}, @cols) {
$problem .= ($problem ? ", " : "") . $_;
}
if ($problem) {
my $plural = $problem =~ /, /;
$all_problems .= "\nThe following column".($plural?"s":"")." in the $table table ($$opt{source}) ".($plural?"are":"is")." NOT in the Gossamer Links database: $problem. ".($plural?"They":"It")." will have to be created prior to performing this import.";
$has_problems++;
}
while (<IMPORT_FH>) {
last TABLE if substr($_,0,2) eq '\\\\';
}
}
}
close IMPORT_FH;
critical $all_problems if $all_problems;
import_print "All tables verified successfully\n\n\n";
open IMPORT_FH, "<$$opt{source}" or critical "Unable to open $$opt{source}: $!";
binmode IMPORT_FH; # Don't want to worry about windows line feeds!
while (<IMPORT_FH>) {
last if substr($_,0,2) eq '\\\\';
} # Eat up until \\
while (<IMPORT_FH>) {
chomp;
my $table = $_;
$e_dbh->do("DELETE FROM $prefix$_");
import_print "Importing $prefix$table ... (starting at line ".($.+2)." of $$opt{source})\n";
my $imported = 0;
TABLE: while (<IMPORT_FH>) {
chomp;
my $header = $_;
my $delimiter = substr($header,0,1);
substr($header,0,1) = '';
my @cols = map BK_unescape($_,$delimiter), split /\Q$delimiter/, $header, -1;
# If this is an ODBC db, we need to turn identity insert on.
my $insert = "INSERT INTO $prefix$table (" . join(",", @cols) . ") VALUES (" . join(",",("?") x @cols) . ")";
if ($odbc) {
if ($DB->table($table)->ai) {
$insert = "SET IDENTITY_INSERT $prefix$table ON; $insert";
}
}
my $sth = $e_dbh->prepare($insert) or critical "Unable to prepare query `$insert': ".$e_dbh->errstr;
import_print "\tStarting import to table $prefix$table ...\n";
while (<IMPORT_FH>) {
last TABLE if substr($_,0,2) eq '\\\\';
chomp;
my @data = map BK_unescape($_,$delimiter), split /\Q$delimiter/, $_, -1;
$sth->execute(@data) or warning "\tUnable to import `$_' (line $. of $$opt{source}): ".$sth->errstr;
import_print "\t$imported imported ...\n" unless ++$imported % 500;
}
}
import_print "\t$imported records imported to $prefix$table.\n",
"All records have been imported to $prefix$table.\n\n";
}
import_print "All tables contained in $$opt{source} have been imported.\n\nNOTE: You must run Repair Tables and Rebuild Search after performing an import!\n";
}
# Takes two parameters: The field to escape, and the delimiter. It will return
# the field unescaped.
sub BK_unescape ($$) {
my $field = shift;
my $delimiter = shift;
$delimiter = "" unless defined $delimiter;
critical "Bad delimiter `$delimiter'" unless length $delimiter == 1 and $delimiter ne '\\';
critical "An escaped field cannot be undefined. You have data errors!" unless defined $field;
return undef if $field eq 'NULL';
my $escape_chr = '\\';
$field =~ s/\Q$escape_chr\E([0-9A-Fa-f]{2})/chr hex $1/ge;
$field;
}
2;

View File

@ -0,0 +1,189 @@
# ==================================================================
# 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: BKS2.pm,v 1.14 2005/03/05 01:46:07 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::Import::BKS2;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use GT::SQL;
sub critical {
$Critical_Code->(@_) if ref $Critical_Code eq 'CODE';
}
sub warning {
$Warning_Code->(@_) if ref $Warning_Code eq 'CODE';
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
my $prefix = $DB->prefix || "";
my $odbc = 0;
my $e_dbh;
{
my $table = $DB->table("Links");
$table->connect();
$e_dbh = $table->{driver}->connect();
if ($table->{connect}->{driver} eq 'ODBC') {
$odbc = 1;
$e_dbh->{LongReadLen} = 1000000;
}
}
my $delimiter;
local (*IMPORT_FH);
local $/ = "\0"; # "Lines" are actually delimited by \0 (hex & ascii 0)
import_print "Verifying table headers ...\n";
my $all_problems = "";
open IMPORT_FH, "<$$opt{source}" or critical "Unable to open $$opt{source}: $!";
binmode IMPORT_FH; # Don't want to worry about windows line feeds!
while (<IMPORT_FH>) {
last if substr($_,0,2) eq '\\\\';
} # Eat up until a \\
while (<IMPORT_FH>) {
chomp;
my $table = $_;
import_print "\tChecking $table\n";
my $has_problems = 0;
TABLE: while (<IMPORT_FH>) {
chomp;
my $header = $_;
my $delimiter = substr($header,0,1);
substr($header,0,1) = '';
my @cols = map BK_unescape($_,$delimiter), split /\Q$delimiter/, $header, -1;
my %cols = $DB->table($table)->cols;
my $problem = "";
for (grep !$cols{$_}, @cols) {
$problem .= ($problem ? ", " : "") . $_;
}
if ($problem) {
my $plural = $problem =~ /, /;
$all_problems .= "\nThe following column".($plural?"s":"")." in the $table table ($$opt{source}) ".($plural?"are":"is")." NOT in the Gossamer Links database: $problem. ".($plural?"They":"It")." will have to be created prior to performing this import.";
$has_problems++;
}
while (<IMPORT_FH>) {
last TABLE if substr($_,0,2) eq '\\\\';
}
}
}
close IMPORT_FH;
critical $all_problems if $all_problems;
import_print "All tables verified successfully\n\n\n";
open IMPORT_FH, "<$$opt{source}" or critical "Unable to open $$opt{source}: $!";
binmode IMPORT_FH; # Don't want to worry about windows line feeds!
while (<IMPORT_FH>) {
last if substr($_,0,2) eq '\\\\';
} # Eat up until \\
while (<IMPORT_FH>) {
chomp;
my $table = $_;
#$e_dbh->do("DELETE FROM $prefix$_");
import_print "Importing $prefix$table ... (starting at line ".($.+2)." of $$opt{source})\n";
my $imported = 0;
TABLE: while (<IMPORT_FH>) {
chomp;
my $header = $_;
my $delimiter = substr($header,0,1);
substr($header,0,1) = '';
my @cols = map BK_unescape($_,$delimiter), split /\Q$delimiter/, $header, -1;
if ($table =~ /Category$/) {
push @cols, 'tmp_col';
}
# If this is an ODBC db, we need to turn identity insert on.
my $insert = "INSERT INTO $prefix$table (" . join(",", @cols) . ") VALUES (" . join(",",("?") x @cols) . ")";
if ($odbc) {
if ($DB->table($table)->ai) {
$insert = "SET IDENTITY_INSERT $prefix$table ON; $insert";
}
}
my $sth = $e_dbh->prepare($insert) or critical "Unable to prepare query `$insert': ".$e_dbh->errstr;
import_print "\tStarting import to table $prefix$table ...\n";
while (<IMPORT_FH>) {
last TABLE if substr($_,0,2) eq '\\\\';
chomp;
if ($table =~ /Category$/) {
print $_ . "\n";
my @data = map BK_unescape($_,$delimiter), split /\Q$delimiter/, $_, -1;
$sth->execute(@data) or warning "\tUnable to import `$_' (line $. of $$opt{source}): ".$sth->errstr;
import_print "\t$imported imported ...\n" unless ++$imported % 500;
}
}
}
import_print "\t$imported records imported to $prefix$table.\n",
"All records have been imported to $prefix$table.\n\n";
}
import_print "All tables contained in $$opt{source} have been imported.\n\nNOTE: You must run Repair Tables and Rebuild Search after performing an import!\n";
}
# Takes two parameters: The field to escape, and the delimiter. It will return
# the field unescaped.
sub BK_unescape ($$) {
my $field = shift;
my $delimiter = shift;
$delimiter = "" unless defined $delimiter;
critical "Bad delimiter `$delimiter'" unless length $delimiter == 1 and $delimiter ne '\\';
critical "An escaped field cannot be undefined. You have data errors!" unless defined $field;
return undef if $field eq 'NULL';
my $escape_chr = '\\';
$field =~ s/\Q$escape_chr\E([0-9A-Fa-f]{2})/chr hex $1/ge;
$field;
}
2;

View File

@ -0,0 +1,581 @@
# ==================================================================
# Gossamer Links - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: CGI.pm,v 1.17 2005/04/05 08:44:30 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::Import::Interface::CGI;
# ==================================================================
use strict;
use Links qw/$IN $CFG/;
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {};
bless $self, $class;
return $self;
}
sub isin {
my $val = shift;
for (@_) {
return 1 if $val eq $_;
}
return undef;
}
sub html_escape {
shift if ref $_[0];
my $to_escape = shift;
$to_escape = "" unless defined $to_escape;
$to_escape =~ s/&/&amp;/g;
$to_escape =~ s/ /&nbsp;/g;
$to_escape =~ s/</&lt;/g;
$to_escape =~ s/>/&gt;/g;
$to_escape =~ s/"/&quot;/g;
$to_escape;
}
sub make_opts {
my $self = shift;
return if ref $self->{cgi} eq 'HASH';
$self->{cgi} = { };
$self->{cgi}{help} = 1, return if $IN->param("help");
return unless $IN->param("Interface_CGI");
$self->{cgi}{transfer} = isin($IN->param("transfer"),qw/L1S2 L2S2 S1S2 BKS2 S2BK/)
? $IN->param("transfer")
: "";
for ($IN->param) { $self->{cgi}{$_} = $IN->param($_); }
}
sub get_options {
my $self = shift;
$self->make_opts;
return wantarray ? (help => 1) : { help => 1 } if $self->{cgi}{'help'};
$self->start_page(),exit unless $IN->param("Interface_CGI");
if ($self->{cgi}{'errors_to_browser'}) {
if ($self->{cgi}{error_file}) {
my $fh = \do { local *FH; *FH };
unless (open $fh, "> $self->{cgi}{error_file}") {
_print_headers();
print "<pre>Unable to open error file @{[html_pre_format(qq($self->{cgi}{error_file}: $!))]}</pre>";
exit;
}
$self->{cgi}{error_file} = sub {
for (@_) {
print html_pre_format("Import error: $_\n");
print $fh "Import error: $_\n";
}
}
}
else {
$self->{cgi}{error_file} = sub {
for (@_) {
print html_pre_format("Import error: $_\n");
}
}
}
}
else {
$self->{cgi}{error_file} = 'STDOUT';
}
return wantarray ? %{$self->{cgi}} : $self->{cgi};
}
sub usage ($$$) {
my $self = shift;
push @{$self->{usage_list}}, shift if @_;
# Don't care about the third argument; it is exclusively for Interface::Text
}
sub has_usage {
my $self = shift;
return ref $self->{usage_list} ? scalar @{$self->{usage_list}} : undef;
}
sub show_usage {
my $self = shift;
$self->start_page(1);
}
sub pre_import {
require Links;
_print_headers();
print "<html>\n<head>\n<title>Import Results</title>\n</head>\n<body bgcolor=#FFFFFF>\n";
print Links::header("Import/Export", "Please be patient, this can take a while...");
print "<blockquote><pre>";
}
sub finished {
print "</pre></blockquote>\n<b><font face='Tahoma,Arial,Helvetica' size=2>Data has been successfully import/exported!</font></b>\n</body>\n</html>";
exit;
}
# Takes one optional argument which, if true, will make it print usage messages
sub start_page {
my $self = shift;
$self->make_opts unless ref $self->{cgi} eq 'HASH';
_print_headers();
$self->_start_page_top;
if (shift and ref $self->{usage_list} and @{$self->{usage_list}}) {
print "\n\n<ul>\n";
for (@{$self->{usage_list}}) {
print " <li><font color=red><b>$_&nbsp;</b></font></li>\n";
}
print "</ul>\n\n";
}
$self->_start_page_bottom;
exit;
}
sub _start_page_top {
print <<'HTML';
<html>
<head>
<title>Gossamer Links Import</title>
</head>
<body bgcolor="#FFFFFF">
<table border="1" cellpadding="0" cellspacing="0"><tr><td>
<table bgColor="#ffffff" border="0" cellPadding="3" cellSpacing="3" width="500" valign="top">
<tr>
<td align="left" bgColor="navy"><b><font color="#ffffff" size="2" face="Tahoma,Arial,Helvetica">Gossamer Links Import/Backup/Restore</font></b></td>
</tr>
<tr>
<td>
<p align="center"><b><font color="#000000" size="2" face="Tahoma,Arial,Helvetica">Gossamer Links Import/Backup/Restore</font></b></p>
<p><font size="2" face="Tahoma,Arial,Helvetica">This tool will allow you to easily migrate from a previous
version of Links, or backup and restore your data. For more information on the specific options, please
consult the <b><a href="nph-import.cgi?help=1&Interface_CGI=1">Help</a></b></font></td>
</tr>
</table>
</td></tr>
</table>
HTML
}
sub _start_page_bottom {
my $self = shift;
print qq[
<form action="nph-import.cgi" method="POST">
<input type=hidden name="Interface_CGI" value=1>
<input type="hidden" name="destination" value="$CFG->{admin_root_path}/defs">
<table border="1" cellspacing="0" cellpadding="0"><tr><td>
<table border="0" cellspacing="0" cellpadding="3" width=500>
<tr>
<td colspan="2" bgcolor="#DDDDDD"><b><font face="Tahoma,Arial,Helvetica" size="2">Import Data from previous versions of Links</font></b></td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
Import From:&nbsp;&nbsp;
</font>
</td>
<td valign="top" align="left"><font face="Tahoma,Arial,Helvetica" size="2">
<select size="1" name="transfer" style="font-family: Tahoma, Arial, Helvetica; font-size: 10pt; color: #000000">
<option ];
print "selected " if $self->{cgi}{transfer} and $self->{cgi}{transfer} eq "S1S2";
print qq[value="S1S2">Links SQL 1.x</option>
<option ];
print "selected " if $self->{cgi}{transfer} and $self->{cgi}{transfer} eq "L1S2";
print qq[value="L1S2">Links 1.x</option>
<option ];
print "selected " if $self->{cgi}{transfer} and $self->{cgi}{transfer} eq "L2S2";
print qq[value="L2S2">Links 2.x</option>
</select></font>
</td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
Location of def files:
</font>
</td>
<td valign="top" align="left">
<input type="text" name="source" size="40" ];
print qq[value="].html_escape($self->{cgi}{source}).qq[" ] if $self->{cgi}{source} && ($self->{cgi}{transfer} =~ /^(?:L[12]|S1)S2$/);
print qq[style="font-family: Tahoma, Arial, Helvetica; font-size: 10pt">
</td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
Error File (optional):
</font>
</td>
<td valign="top" align="left">
<input type="text" name="error_file" size="40" ];
print qq[value="].html_escape($self->{cgi}{error_file}).qq[" ] if $self->{cgi}{error_file} && ($self->{cgi}{transfer} =~ /^(?:L[12]|S1)S2$/);
print qq[style="font-family: Tahoma, Arial, Helvetica; font-size: 10pt">
</td>
</tr>
</table>
<table border="0" cellspacing="0" width="100%">
<tr>
<td valign="top" align="left" colspan="6">
<font face="Tahoma,Arial,Helvetica" size="2">
<br><b>Options:</b>
</font>
</td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
<input type="checkbox" name="show_mild_warnings" value=1];
print " checked" if $self->{cgi}{show_mild_warnings};
print qq[>
Show Mild Warnings
</font>
</td>
<td valign="top" align="left" colspan=2>
<font face="Tahoma,Arial,Helvetica" size="2">
<input type="checkbox" name="critical_warnings" value=1];
print " checked" if $self->{cgi}{critical_warnings};
print qq[>
Critical Warnings
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
<input type="checkbox" name="data_integrity" value=1];
print " checked" if $self->{cgi}{data_integrity};
print qq[>
Extra Data Integrity
</font>
</td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
<input type="checkbox" name="clear_tables" value=1];
print " checked" if not keys %{$self->{cgi}} or $self->{cgi}{clear_tables} and ($self->{cgi}{transfer} =~ /^(?:L[12]|S1)S2$/);
print qq[>
Clear Tables
</font>
</td>
<td valign="top" align="left" colspan=2>
<font face="Tahoma,Arial,Helvetica" size="2">
<input type="checkbox" name="errors_to_browser" value=1];
print " checked" if ($self->{cgi}{errors_to_browser} or not keys %{$self->{cgi}});
print qq[>
Show Errors
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
<input type="checkbox" name="straight_import" value=1];
print " checked" if $self->{cgi}{straight_import};
print qq[>
Straight Import
</font>
</td>
</tr>
<tr>
<td valign=top align=left colspan=2>
<font face="Tahoma,Arial,Helvetica" size=2>
<input type="checkbox" name="create_columns" value=1];
print " checked" if $self->{cgi}{create_columns} or not keys %{$self->{cgi}};
print qq[>
Recreate Non-standard Columns
</font>
</td>
<td valign=top align=left colspan=2>
<font face="Tahoma,Arial,Helvetica" size=2>
<input type=checkbox name=create_missing_categories value=1];
print " checked" if $self->{cgi}{create_missing_categories} or not keys %{$self->{cgi}};
print qq[>
Create Missing Categories
</font>
</td>
</tr>
<tr>
<td colspan="8"><br><center><input type="submit" value="Import Data"></center><br></td>
</tr>
</table>
</td></tr></table>
</form>
<br>
<form action="nph-import.cgi" method="POST">
<input type=hidden name="Interface_CGI" value=1>
<input type="hidden" name="source" value="$CFG->{admin_root_path}/defs">
<input type="hidden" name="transfer" value="S2BK">
<input type="hidden" name="delimiter" value="|">
<table border="1" cellspacing="0" cellpadding="0"><tr><td>
<table border="0" cellspacing="0" cellpadding="3" width=500>
<tr>
<td colspan="2" bgcolor="#DDDDDD"><b><font face="Tahoma,Arial,Helvetica" size="2">Create backup file of all Gossamer Links data</font></b></td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
Location of Backup File:
</font>
</td>
<td valign="top" align="left">
<input type="text" name="destination" size="40" ];
print qq[value="].html_escape($self->{cgi}{destination}).qq[" ] if $self->{cgi}{destination} && ($self->{cgi}{transfer} eq 'S2BK');
print qq[style="font-family: Tahoma, Arial, Helvetica; font-size: 10pt">
</td>
</tr>
<tr>
<td colspan="2"><br><center><input type="submit" value="Backup Data"></center><br></td>
</tr>
</table>
</td></tr></table>
</form>
<br>
<form action="nph-import.cgi" method="POST">
<input type=hidden name="Interface_CGI" value=1>
<input type="hidden" name="destination" value="$CFG->{admin_root_path}/defs">
<input type="hidden" name="transfer" value="BKS2">
<input type="hidden" name="delimiter" value="|">
<input type="hidden" name="clear_tables" value="1">
<table border="1" cellspacing="0" cellpadding="0"><tr><td>
<table border="0" cellspacing="0" cellpadding="3" width=500>
<tr>
<td colspan="2" bgcolor="#DDDDDD"><b><font face="Tahoma,Arial,Helvetica" size="2">Restore Gossamer Links from backup file</font></b></td>
</tr>
<tr>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
Location of Backup File:
</font>
</td>
<td valign="top" align="left">
<input type="text" name="source" size="40" ];
print qq[value="].html_escape($self->{cgi}{source}).qq[" ] if $self->{cgi}{source} && ($self->{cgi}{transfer} eq 'BKS2');
print qq[style="font-family: Tahoma, Arial, Helvetica; font-size: 10pt">
</td>
</tr>
<tr>
<td colspan="2"><br><center><input type="submit" value="Restore Data"></center><br></td>
</tr>
</table>
</td></tr></table>
</form>
<br><br>
</form>
</body>
</html>
];
}
sub show_help {
my $self = shift;
_print_headers();
print <<'HTML';
<html>
<head>
<title>Gossamer Links Import Help</title>
</head>
<body bgcolor="#FFFFFF">
<table border="1" cellpadding="0" cellspacing="0"><tr><td>
<table bgColor="#ffffff" border="0" cellPadding="3" cellSpacing="3" width="500" valign="top">
<tr>
<td align="left" bgColor="navy"><b><font color="#ffffff" size="2" face="Tahoma,Arial,Helvetica">Links
SQL Import Help</font></b></td>
</tr>
<tr>
<td>
<p align="center"><b><font color="#000000" size="2" face="Tahoma,Arial,Helvetica">Links SQL Import Help</font></b></p>
<p><font size="2" face="Tahoma,Arial,Helvetica">Below is a list of all the options available to you when importing
data into Gossamer Links.</font></td>
</tr>
</table>
</td></tr>
</table>
<br><br>
<table cellpadding="3" cellspacing="0" border="1" width="500">
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
<b><u>Column</u></b>
</font>
</td>
<td valign="top" align="center">
<font face="Tahoma,Arial,Helvetica" size="2">
<b><u>Description</u></b>
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Error File:
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
If present, all errors will be written to the filename provided. The
errors will be appended to the end, with a header including the date
written before any errors.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Show Mild Warnings
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
If this option is selected, 'mild' warnings (indicating minor errors
such as setting the username associated with a link to 'admin' because
of insufficient information to create a user) will be displayed in the
error file. If unchecked, such errors are never displayed.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Critical Warnings
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
If this option is enabled, all warnings (such as not being able to
import a Category or Link for whatever reason) will be promoted to
critical errors, stopping the import. This field has NO effect on mild
warnings - this is, mild warnings will NOT cause the script to abort.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Clear Tables
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
With this option enabled, all tables will be cleared before importing.
This has no effect when exporting to a delimited text file.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Straight Import
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
With this option enabled, Link IDs will not be changed for the new
database. That is, a Link with ID 12 in the old database will still be
12 in the new Gossamer Links database. This option is not recommended unless
you are linking directly to a link using its ID and must preserve the
existing link numbering. This option <b>requires</b> that the <i>Clear
Tables</i> option be enabled.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Show Warnings
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
With this option enabled, all warnings will be displayed to the
browser (as well as the log if a log is specified). This option is
automatically enabled if no log file is specified.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Recreate Non-standard Columns
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
If this option is enabled, when the import finds extra (custom) columns
in the source database that do not have an equivelant extra column in
the destination table, they will be created in the destination table so
that all data will be imported.
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Create Missing Categories
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
This option, if enabled, causes the import to create any categories
that are "missing". A category can be missing when a category such as
"A/B/C" exists, but the category "A/B" does not. This option will make
the import create the "A/B" category, as well as the "A" category (if
necessary (i.e. it doesn't exist)).<br>
A category is also considered "missing" if a link refers to a category
that does not exist (Links 1.x and 2.x only).
</font>
</td>
</tr>
<tr>
<td valign="top" align="left" width="25%">
<font face="Tahoma,Arial,Helvetica" size="2">
Extra Data Integrity
</font>
</td>
<td valign="top" align="left">
<font face="Tahoma,Arial,Helvetica" size="2">
This option makes the import check each time a category is imported to
ensure that no duplicate categories will be created by the import. If a
duplicate is identified, the duplicated category will only be inserted
once. Note that this option will most likely make the script take
several times longer to import data, and should only be used if you
suspect that there may be duplicate categories.
</font>
</td>
</tr>
</table>
</body>
</html>
HTML
}
sub html_pre_format {
local $_ = shift;
s/&/&amp;/g;
s/</&lt;/g;
s/>/&gt;/g;
$_;
}
sub _print_headers {
# ------------------------------------------------------------------
# Prints the HTTP headers. Loads Links config file to see if we
# should use nph headers or not.
#
print $IN->header ( -nph => $CFG->{nph_headers} );
}
"Do I *look* like a false value?"

View File

@ -0,0 +1,294 @@
# ==================================================================
# Links SQL - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: Text.pm,v 1.14 2004/05/04 00:50:09 jagerman 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::Import::Interface::Text;
# ==================================================================
use vars qw/%IMPORT_OPT_MAP/;
use strict;
use Getopt::Long;
%IMPORT_OPT_MAP = (
LINKSSQL1 => 'S1S2',
LINKSQL1 => 'S1S2',
LINKS1 => 'L1S2',
LINKS2 => 'L2S2',
S1 => 'S1S2',
L2 => 'L2S2',
L1 => 'L1S2',
LINKS => 'L2S2',
RDF => 'RDFS2',
DMOZ => 'RDFS2'
);
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = { };
bless $self, $class;
return $self;
$self->_init();
}
sub get_options {
my $self = shift;
my %option = ();
my ($backup,$restore,$import);
GetOptions(
"backup" => \$backup,
"restore" => \$restore,
"import=s" => \$import,
"source=s" => \$option{source},
"destination=s" => \$option{destination},
"help" => \$option{help},
"error-file=s" => \$option{error_file},
"critical-warnings" => \$option{critical_warnings},
"mild-warnings" => \$option{show_mild_warnings},
"data-integrity" => \$option{data_integrity},
"create-columns" => \$option{create_columns},
"create-missing-categories" => \$option{create_missing_categories},
"clear-tables" => \$option{clear_tables},
"straight-import" => \$option{straight_import},
"rdf-category=s" => \$option{rdf_category},
"rdf-destination=s" => \$option{rdf_destination},
"rdf-add-date=s" => \$option{rdf_add_date},
"with-gzip=s" => \$option{with_gzip},
"rdf-update" => \$option{rdf_update},
"rdf-user=s" => \$option{rdf_user},
"xml-parser!" => \$option{xml_parser}
);
$option{transfer} = $IMPORT_OPT_MAP{uc $import} || "";
unless ($option{from} or $option{to} or $option{source} or $option{destination}) {
return wantarray ? () : {};
}
if (($backup and $restore) or ($backup and $option{transfer}) or ($restore and $option{transfer})) {
delete $option{transfer}; # Two options provided!
}
elsif ($backup) {
$option{transfer} = "S2BK";
}
elsif ($restore) {
$option{transfer} = "BKS2";
}
return wantarray ? %option : \%option;
}
sub start_page {
show_help(@_);
}
sub show_help {
my $self = shift;
print <<HELP;
Links SQL 2 Importer/Exporter
Usage:
perl $0 {--backup|--restore|--import type} --source=<source>
--destination=<destination> [any others of the following options]
Options are (options may be simplified to uniqueness):
(One of the following three is required)
--import Links1|Links2|LinksSQL1|RDF
Will do an import from the chosen source.
--backup
This option will perform a Links SQL 2 backup.
--restore
This option will return from a Links SQL 2 backup file created
with --backup.
--source=<input_source> (required)
Sets according to the following:
--import Links1|Links2
the path of the def and db files
--import LinksSQL1
the path of the def files
--import RDF
the path and filename of the RDF file to import from.
Note that if the file ends in .gz, the import will attempt to run
it through gzip decompression trying several standard locations for
gzip. You may specify a location for gzip using the --with-gzip
option.
--restore
the path and filename of the backup file created with --backup
--backup
the path of the Links SQL 2 def files
--destination=<output_dest> (required)
Sets according to the following:
--import Links1|Links2|LinksSQL1|RDF
--restore
the path of the Links SQL 2 def files
--backup
the path and filename of a file to use for the Links SQL 2 backup.
--error-file="./error/errors.txt" (not required)
Sets a file to which all import errors will be written. If you set it
to STDOUT, or if it is not set, it will write all errors to standard
output (STDOUT) prepended with "IMPORT ERROR: ".
--critical-warnings
Makes import warnings become fatal errors. Note that relatively minor
warnings such as not having enough information to create a new user for
a link (therefore setting the link to be owned by admin) are not
promoted to fatal errors.
--mild-warnings
Displays mild import warnings. Mild warnings are those that affect a
relatively minor portion of the script. Note that mild warnings will
NOT cause the script to abort, even if the --critical-warnings option
has been enabled.
--data-integrity
Makes the import check every category before inserting it to insure
that there are no duplicates. Note that this option will make the
import take much longer to complete as each and every category will
have to be checked to see if it exists. This option is only recommended
if you suspect that your data might contain duplicate categories. It
only works when importing data to a Links SQL 2 database from Links
1.x, Links 2.x, or Links SQL 1.x (NOT when backing up, restoring, or
importing from an RDF).
--create-columns
Makes the import attempt to create any columns which are in the source
tables, but NOT in the destination tables. That is, custom tables will
be imported into the new Links database. Without this option, existing
tables that do not exist in the destination format will cause a
warning. If this feature is enabled, a mild warning will occur whenever
a table does not exist and is being created.
--clear-tables
(This option is required to use --restore, but optional for --backup
and all imports)
Makes the current Links SQL tables be cleared (except for the admin
user in the Users table) before doing the import. Only takes effect
when importing to Links SQL 2. This option allows you to use the
--straight-import option below.
--straight-import
(This option can only be used with --clear-tables).
Makes the import not recalculate Category/Link ID numbers. That is, a
link with ID number 12 in the source will be inserted into the Links
SQL 2 database with an ID number of 12. Note that this can leave a
fairly large gap in the Links ID fields depending on the usage of the
source import. This option does nothing with --backup and --restore
--create-missing-categories
Used with an import. Categories are "missing" when they are required
for the database to be complete but do not exist. For example, if
category A/B/C existed in the database but A and A/B did not, then both
A and A/B would be considered "missing" and would be automatically
created if this option is enabled. For Links 1.x and 2.x imports, this
will also make the import attempt to create categories that are
required for links. For example, if a link exists and thinks it is in
category A/B/C but A/B does no exist, A/B and A/B/C will be created to
allow the link to be imported.
--rdf-category="Top/Category/Name"
(This option can only be and must be used with `--import RDF')
Specifies the RDF category to import such as "Top/Business".
--rdf-destination="Links SQL2/Category/Name"
(This option can only be used with `--import RDF')
Specifies a Links SQL 2 category to import the data to. For example,
"My Business Links" would import the RDF category specified with
--rdf-category into the "My Business Links" category. If this is not
specified (or specified as "/" or "") the import will be done into the
Links SQL category root.
--with-gzip="/path/to/gzip"
(This option can only be used with `--import RDF')
Specifies the location of gzip. This option is only needed if the RDF
file has been compressed with gzip (the file will end with ".gz") and
the import is unable to locate gzip on its own.
--rdf-update
(This option can only be used with `--import RDF')
Specifies that the import should check to see that categories and links
do not already exist. For an initial RDF import, this option is not
needed, however to update a previous RDF import you MUST use this
option; failing to do so would result in duplicate categories and links
appearing. It is not recommended that you use this option when
performing an initial import from an RDF as it will increase the import
time considerably.
--rdf-user="Username"
(This option can only be used with `--import RDF')
Specifies a user who all new links should belong to. The user MUST
already exist in the Links SQL Users table. If not specified, all links
will have `admin' as the LinkOwner. Note that if the --clear-tables
option is specified, this user will also be preserved when all tables
are wiped.
--rdf-add-date="2001-01-05"
(This option can only be used with `--import RDF' and is required)
This sets the date that new link links should have their `Add_Date' and
`Mod_Date' fields set to. This should be in the format `YYYY-MM-DD'.
NOTE: You should NOT set this to a very recent date as all links would
then show up as "New" links.
--help
Displays this screen
HELP
# Understood, but often fails as RDF files are commonly malformed:
#
# --xml-parser
# (This option can only be used with `--import RDF')
# Attempts to use the new XML::Parser-based code for importing the RDF
# file. Although much faster, it requires that the XML::Parser module be
# installed, and should be considered an experimental feature.
}
sub pre_import () { }
sub usage ($$;$) {
my $self = shift;
$self->{usage_list} = [ ] unless exists $self->{usage_list};
my $message = "";
if (@_) {
$message = shift() . ".";
$message .= " See " . shift() . " and --help" if @_;
}
push @{$self->{usage_list}}, $message if $message;
}
sub has_usage {
my $self = shift;
return ref($self->{usage_list}) ? scalar @{$self->{usage_list}} : undef;
}
sub show_usage {
my $self = shift;
for (@{$self->{usage_list}}) { print <<USAGE }
Incorrect usage.
$_
USAGE
}
sub finished () {
my $self = shift;
print "\n\nImport completed successfully\n";
exit;
}
"Apparently, I'm true";

View File

@ -0,0 +1,689 @@
# ==================================================================
# Gossamer Links - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: L1S2.pm,v 1.25 2005/04/16 02:11:50 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::Import::L1S2;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use GT::SQL;
sub critical {
$Critical_Code->(@_);
}
sub warning {
$Warning_Code->(@_);
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
my ($have_email_db,$have_validate_db);
my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
my $e_prefix = $DB->prefix;
my $e_dbh;
{
my $table = $DB->table("Links");
$table->connect();
$e_dbh = $table->{driver}->connect();
}
local (*LINKS,*CATS,*EMAIL,$@,$!,*VALIDATE);
# Check to see if this should be a Links SQL 1.x import instead of Links 1.x.
my $error_msg = "";
-e "$$opt{source}/links.cfg" or $error_msg .= "$$opt{source}/links.cfg does not exist.";
-e "$$opt{source}/Links.def" and $error_msg .= " $$opt{source}/Links.def DOES exist. Perhaps you meant to import Links SQL 1.x instead of Links 1.x?";
critical $error_msg if $error_msg;
my $did = do {
package Links1::Def::Links; # Avoid namespace pollution
do "$$opt{source}/links.cfg";
};
!$did and $! and critical "Cannot open $$opt{source}/links.cfg (This error may result from links.def): $!".($@ ? ", ".substr($@,0,length($@)-1) : "");
!$did and $@ and critical "Cannot parse $$opt{source}/links.cfg (This error may result from links.def): $@";
$Links1::Def::Links::db_file_name or critical "links.cfg did not load correctly. Import aborted.";
$did = do {
package Links1::Def::Category;
local $ENV{PATH_INFO} = "/category";
do "$$opt{source}/links.cfg";
};
!$did and $! and critical "Cannot open $$opt{source}/links.cfg (This error may result from category.def): $!".($@ ? ", ".substr($@,0,length($@)-1) : "");
!$did and $@ and critical "Cannot parse $$opt{source}/links.cfg (This error may result from category.def): $@";
$Links1::Def::Category::db_file_name or critical "links.cfg did not load correctly. Import aborted.";
open CATS, "<$Links1::Def::Category::db_file_name" or critical "Unable to open $Links1::Def::Links::db_file_name: $!";
open LINKS, "<$Links1::Def::Links::db_file_name" or critical "Unable to open $Links1::Def::Links::db_file_name: $!";
if (open VALIDATE, "<$Links1::Def::Links::db_valid_name") {
$have_validate_db = 1;
}
else {
warning "Could not open $Links1::Def::Links::db_valid_name: $!. Non-validated links will not be imported.";
}
my %e_standard_cols = (
Category => { map { ($_ => 1) } qw/ID Name FatherID Full_Name Description Meta_Description Meta_Keywords Header Footer Category_Template Number_of_Links Has_New_Links Has_Changed_Links Newest_Link Timestmp Payment_Mode/},
Links => { map { ($_ => 1) } qw/ID Title URL LinkOwner Add_Date Mod_Date Description Contact_Name Contact_Email Hits isNew isChanged isPopular isValidated Rating Votes Status Date_Checked Timestmp ExpiryDate ExpiryCounted ExpiryNotify/},
);
my %e_non_standard_cols;
for my $table (keys %e_standard_cols) {
my %cols = $DB->table($table)->cols;
for (grep !$e_standard_cols{$table}{$_}, keys %cols) {
$e_non_standard_cols{$table}{$_} = 1;
}
}
my %i_standard_cols = (
Category => { map { ($_ => 1) } qw/ID Name Description Related Header Footer/,'Meta Description','Meta Keywords' },
Links => { map { ($_ => 1) } qw/ID Title URL Date Category Description Hits isNew isPopular/,'Contact Name','Contact Email'}
);
my %i_non_standard_cols;
$i_non_standard_cols{Links} = { map { !$i_standard_cols{Links}{$_} ? ($_ => 1) : () } @Links1::Def::Links::db_cols };
$i_non_standard_cols{Category} = { map { !$i_standard_cols{Category}{$_} ? ($_ => 1) : () } @Links1::Def::Category::db_cols };
my $Links_counter;
my $Category_counter;
if (($DB->table('Links')->{connect}->{driver} || "") eq "ODBC") {
$e_dbh->do("SET IDENTITY_INSERT Links ON");
$e_dbh->do("SET IDENTITY_INSERT Category ON");
}
if ($$opt{clear_tables}) {
# Delete everything from all tables, EXCEPT for the `admin' user from the Users table
$e_dbh->do("DELETE FROM ${e_prefix}Users WHERE Username <> 'admin'") or critical "Unable to delete all existing users: ".$e_dbh->errstr;
for (qw/Links Category CatLinks CatRelations Category_Score_List
Category_Word_List ClickTrack Editors EmailMailings EmailTemplates
Links_Score_List Links_Word_List MailingIndex MailingList
MailingListIndex Sessions Verify/) {
$e_dbh->do("DELETE FROM $e_prefix$_");
}
unless ($$opt{straight_import}) {
$Links_counter = $Category_counter = 0;
}
}
else {
my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
$Links_counter = $sth->fetchrow_array;
$sth->finish();
$sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$sth->errstr;
$Category_counter = $sth->fetchrow_array;
$sth->finish();
}
# Categories
my %cat_map; # $cat_map{name} = new_id
my @num_of_links; # $num_of_links[category_id] = (the number of links in that category)
{
my @cat_get_cols = ('ID','Name','Related', 'Description','Meta Description','Meta Keywords','Header','Footer');
my $cat_ins_cols = "(ID, Name, FatherID, Full_Name, Description, Meta_Description, Meta_Keywords, Header, Footer";
my $cat_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?";
# Build up extra fields that exist in both old and new Category tables
for (keys %{$e_non_standard_cols{"${e_prefix}Category"}}) {
if ($i_non_standard_cols{Category}{$_}) {
$cat_ins_cols .= ", $_";
$cat_ins_vals .= ", ?";
push @cat_get_cols, $_;
}
else {
mild_warning("Custom destination column `${e_prefix}Category.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep !$e_non_standard_cols{"${e_prefix}Category"}{$_}, keys %{$i_non_standard_cols{Category}}) {
if ($opt->{create_columns}) {
if (/\W/) {
critical "Custom import column `Category.$_' cannot be imported because is is not a valid column name. You will need to rename the column name in the def file and in any relevant templates to a new name consisting only of letters, numbers, and the _ character.";
next;
}
mild_warning("Custom import column `Category.$_' had no destination equivelant. A destination column will be created");
my $editor = $DB->editor("Category");
$editor->add_col(
$_,
{
type => 'TEXT',
size => $Links1::Def::Category::db_max_field_length,
($Links1::Def::Category::db_not_null{$_} ? (not_null => 1) : ()),
($Links1::Def::Category::db_defaults{$_} ? (default => $Links1::Def::Category::db_defaults{$_}) : ()),
($Links1::Def::Category::db_valid_types{$_} ? (regex => $Links1::Def::Category::db_valid_types{$_}) : ()),
}
);
$cat_ins_cols .= ", $_";
$cat_ins_vals .= ", ?";
push @cat_get_cols, $_;
$e_non_standard_cols{"${e_prefix}Category"}{$_} = 1;
}
else {
warning("Custom import column `Category.$_' has no destination equivelant. It will be ignored");
}
}
$cat_ins_cols .= ")";
$cat_ins_vals .= ")";
my $add_cat_relation = $e_dbh->prepare("INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)': ".$e_dbh->errstr;
my $count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);
my $cat_ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr);
my $cat_ins_simple_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr;
my $get_id_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);
my @cat_relations;
my $cat_imported = 0;
import_print "\nImporting Categories ...\n";
my @cat_data;
while (my $row = get_rec(\*CATS,'Category',\@Links1::Def::Category::db_cols,"|",\@cat_get_cols)) {
push @cat_data, $row if ref $row eq 'ARRAY';
}
@cat_data = sort { $a->[1] cmp $b->[1] } @cat_data;
my @missing_cats;
my %missing_cats;
for my $row (@cat_data) {
$row = [@$row];
my $old_id = shift @$row;
my $new_id = $$opt{straight_import} ? $old_id : ++$Category_counter;
my ($name) = (my $full_name = shift @$row) =~ m[([^/]*)\Z];
unless (defined $name and length $name) {
$Category_counter-- unless $$opt{straight_import};
warning "Cannot insert Category $full_name because it is an invalid name";
next;
}
my ($father_full_name) = $full_name =~ m[\A(.*)/];
my $father_id;
if (not defined $father_full_name) {
$father_id = 0;
}
else {
$get_id_sth->execute($father_full_name) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_id_sth->errstr;
if (my $ar = $get_id_sth->fetchrow_arrayref()) {
$father_id = $ar->[0] || 0;
}
else {
if ($$opt{create_missing_categories}) {
unless ($missing_cats{$father_full_name}++) {
unshift @missing_cats, $father_full_name;
mild_warning "$father_full_name is needed for category $full_name and does not exist. It will be created";
my $fn = $father_full_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array or $missing_cats{$fn}++) { # It exists
last;
}
else {
unshift @missing_cats, $fn;
mild_warning "$fn is needed for category $full_name and does not exist. It will be created";
}
}
}
else {
mild_warning "$father_full_name is also needed for category $full_name and is already in the queue to be created.";
}
}
else {
warning "No father row found for $full_name! This may be a serious error as $full_name should probably have a father category";
}
$father_id = 0;
}
}
$cat_relations[$new_id] = shift @$row; # This has to be dealt with later.
if ($$opt{data_integrity}) {
$count_cats_sth->execute($full_name) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
unless ($count_cats_sth->fetchrow_array) {
unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
$Category_counter-- unless $$opt{straight_import};
warning "Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr;
next;
}
import_print "$cat_imported\n" unless ++$cat_imported % 500;
$cat_map{$full_name} = $new_id;
$num_of_links[$new_id] = 0;
}
else {
--$Category_counter unless $$opt{straight_import};
mild_warning("Duplicate category found ($full_name) and skipped");
next;
}
}
else {
unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
--$Category_counter unless $$opt{straight_import};
warning("Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr);
next;
}
import_print "$cat_imported\n" unless ++$cat_imported % 500;
$cat_map{$full_name} = $new_id;
$num_of_links[$new_id] = 0;
}
}
my $missing_cats;
if ($$opt{create_missing_categories} and @missing_cats) {
my $counter = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category");
$counter->execute();
my $ins_id = $counter->fetchrow_array();
my $update_sub_cats = $e_dbh->prepare("UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?': ".$e_dbh->errstr;
for (@missing_cats) {
my ($name) = m[([^/]*)\Z];
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$get_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_id_sth->errstr;
$father_id = $get_id_sth->fetchrow_array;
}
else { # Must be a category of root
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$ins_id,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$cat_map{$_} = $ins_id;
$update_sub_cats->execute($ins_id,"$_/%","$_/%/%") or critical "Unable to execute query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? and Full_Name NOT LIKE ?': ".$update_sub_cats->errstr;
$missing_cats++;
}
}
import_print "$cat_imported Categories imported";
import_print ", $missing_cats missing categories created" if $missing_cats;
import_print ".\n";
# Category Relations
import_print "\nImporting Category Relations ...\n";
my $cat_rel_imported = 0;
for my $cat_id (0..$#cat_relations) {
next unless defined $cat_relations[$cat_id];
my @cats = split /\|/, $cat_relations[$cat_id];
for (@cats) {
$get_id_sth->execute($_) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_id_sth->errstr;
my $rel_id = $get_id_sth->fetchrow_array;
if (defined $rel_id) {
unless ($add_cat_relation->execute($cat_id,$rel_id)) {
warning "Unable to execute query `INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)': ".$add_cat_relation->errstr;
}
else {
import_print "$cat_rel_imported\n" unless ++$cat_rel_imported % 500;
}
}
else {
warning "Unable to add category relation for category with ID $cat_id and `$_'. Reason: Category `$_' not found in database.";
}
}
}
import_print "$cat_rel_imported Category Relations imported.\n";
}
# Links
{
my @links_get_cols = ('ID','Category','Date','Contact Name','Contact Email', qw/Title URL Description Hits isNew isPopular/);
my $links_ins_cols = "(ID, LinkOwner, isValidated, Add_Date, Mod_Date, Contact_Name, Contact_Email, Title, URL, Description, Hits, isNew, isPopular";
my $links_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";
for (keys %{$e_non_standard_cols{"${e_prefix}Links"}}) {
if ($i_non_standard_cols{Links}{$_}) {
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
push @links_get_cols, $_;
}
else {
mild_warning("Custom destination column `${e_prefix}Links.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep $e_standard_cols{Links}{$_}, keys %{$i_non_standard_cols{Links}}) {
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
push @links_get_cols, $_;
}
for (grep +(!$e_standard_cols{Links} and !$e_non_standard_cols{"${e_prefix}Links"}{$_}), keys %{$i_non_standard_cols{Links}}) {
if ($opt->{create_columns}) {
mild_warning("Custom import column `Links.$_' had no destination equivelant. A destination column will be created");
my $editor = $DB->editor("Links");
$editor->add_col(
$_,
{
type => 'TEXT',
size => $Links1::Def::Links::db_max_field_length,
($Links1::Def::Links::db_not_null{$_} ? (not_null => 1) : ()),
($Links1::Def::Links::db_defaults{$_} ? (default => $Links1::Def::Links::db_defaults{$_}) : ()),
($Links1::Def::Links::db_valid_types{$_} ? (regex => $Links1::Def::Links::db_valid_types{$_}) : ()),
}
) or critical("Unable to add column $_: $GT::SQL::error");
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
push @links_get_cols, $_;
$e_non_standard_cols{"${e_prefix}Links"}{$_} = 1;
}
else {
warning("Custom import column `Links.$_' has no destination equivelant. It will be ignored");
}
}
$links_ins_cols .= ")";
$links_ins_vals .= ")";
my $user_ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Registered')") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Registered')': ".$e_dbh->errstr);
my $cat_links_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$e_dbh->errstr);
my $insert_link_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr);
my $user_count_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
my $username_sth = $e_dbh->prepare("SELECT Username FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT Username FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
# What other than the Name and ReceiveMail can be updated here?
my $user_mod_sth = $e_dbh->prepare("UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$e_dbh->errstr;
my $num_links_sth = $e_dbh->prepare("UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$e_dbh->errstr;
my ($count_cats_sth,$get_cat_id_sth,$cat_ins_simple_sth);
my $ins_id;
if ($$opt{create_missing_categories}) {
$count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical "Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr;
$get_cat_id_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical "Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr;
$cat_ins_simple_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr;
my $counter = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category");
$counter->execute();
$ins_id = $counter->fetchrow_array();
}
import_print "\nImporting Links ...\n";
my $links_imported = 0;
my $missing_cats = 0;
my @more_needed; # This will hold any missing categories (such as A/B in A/B/C)
LINK: while (my $row = get_rec(\*LINKS,'Links',\@Links1::Def::Links::db_cols,"|",\@links_get_cols)) {
$row = [@$row]; # Remove aliasing
my ($id, $cat_name, $date, $contact_name, $contact_email) = splice @$row,0,5;
unshift @$row, $contact_name, $contact_email;
$date = convert_date($date) or warning("Invalid date `$date' for link with ID $id. Link skipped."),next;
$id = ++$Links_counter unless $$opt{straight_import};
my $cat_id = $cat_map{$cat_name};
unless (defined $cat_id) {
if ($$opt{create_missing_categories} and $cat_name) {
my @needed = my $fn = $cat_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) { # It exists
last;
}
else {
unshift @needed, $fn;
}
}
for (@needed) {
my ($name) = m[([^/]+)\Z];
unless ($name) {
warning "Unable to create category $_ because it is an invalid name. Link ID $id will be skipped as a result.";
last;
}
mild_warning("Creating category $_ as it is needed by link ID $id");
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$get_cat_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_cat_id_sth->errstr;
$father_id = $get_cat_id_sth->fetchrow_array;
}
else { # Must be a root category
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$ins_id,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$cat_map{$_} = $ins_id;
$cat_id = $ins_id;
$missing_cats++;
}
}
else {
warning("Invalid category `$cat_name' for link $$row[0] (ID: $id, line $.). Link skipped"),next unless defined $cat_id;
}
}
next LINK unless defined $cat_id;
my $username;
$user_count_sth->execute($contact_email) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_arrayref()->[0]) { # This e-mail address already exists
$user_mod_sth->execute($contact_name, $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
$username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
$username = $username_sth->fetchrow_arrayref()->[0];
}
elsif ($contact_email) {
$user_ins_sth->execute(($contact_email) x 2, (defined $contact_name ? $contact_name : ""), 'Yes') or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Registered')': ".$user_ins_sth->errstr);
$username = $contact_email;
}
else {
mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
$username = 'admin';
}
if ($insert_link_sth->execute($id,$username,'Yes',$date,$date,@$row)) {
$cat_links_sth->execute($id,$cat_id) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
$num_of_links[$cat_id]++;
import_print "$links_imported\n" unless ++$links_imported % 500;
}
else {
$Links_counter-- unless $$opt{straight_import};
warning("Unable to insert link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals'): ".$insert_link_sth->errstr);
}
}
import_print "$links_imported records from 'Links' imported.\n";
if ($have_validate_db) {
$links_imported = 0;
import_print "Importing records from 'Validate'.\n";
LINK: while(my $row = get_rec(\*VALIDATE,'Links',\@Links1::Def::Links::db_cols,"|",\@links_get_cols)) {
$row = [@$row]; # Remove aliasing
my ($id, $cat_name, $date, $contact_name, $contact_email) = splice @$row,0,5;
unshift @$row, $contact_name, $contact_email;
$date = convert_date($date) or warning("Invalid date `$date' for link with ID $id. Link skipped."),next;
$id = ++$Links_counter unless $$opt{straight_import};
my $cat_id = $cat_map{$cat_name};
unless (defined $cat_id) {
if ($$opt{create_missing_categories} and $cat_name) {
my @needed = my $fn = $cat_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) { # It exists
last;
}
else {
unshift @needed, $fn;
}
}
for (@needed) {
my ($name) = m[([^/]+)\Z];
unless ($name) {
warning "Unable to create category $_ because it is an invalid name. Link ID $id will be skipped as a result.";
last;
}
mild_warning("Creating category $_ as it is needed by link ID $id");
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$get_cat_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_cat_id_sth->errstr;
$father_id = $get_cat_id_sth->fetchrow_array;
}
else { # Must be a root category
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$ins_id,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$cat_map{$_} = $ins_id;
$cat_id = $ins_id;
$missing_cats++;
}
}
else {
warning("Invalid category `$cat_name' for link $$row[0] (ID: $id, line $.). Link skipped"),next unless defined $cat_id;
}
}
next LINK unless defined $cat_id;
my $username;
$user_count_sth->execute($contact_email) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_arrayref()->[0]) { # This e-mail address already exists
$user_mod_sth->execute($contact_name, 'Yes', $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
$username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
$username = $username_sth->fetchrow_arrayref()->[0];
}
elsif ($contact_email) {
$user_ins_sth->execute(($contact_email) x 2, (defined $contact_name ? $contact_name : ""), 'Yes') or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Registered')': ".$user_ins_sth->errstr);
$username = $contact_email;
}
else {
mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
$username = 'admin';
}
if ($insert_link_sth->execute($id,$username,'No',$date,$date,@$row)) {
$cat_links_sth->execute($id,$cat_id) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
$num_of_links[$cat_id]++;
import_print "$links_imported\n" unless ++$links_imported % 500;
}
else {
$Links_counter-- unless $$opt{straight_import};
warning("Unable to insert link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals'): ".$insert_link_sth->errstr);
}
}
import_print "$links_imported records from 'Validate' imported.\n";
}
import_print "$missing_cats categories have been created due to missing categories for links\n" if $missing_cats;
for (grep $num_of_links[$_], 0..$#num_of_links) {
$num_links_sth->execute($num_of_links[$_],$_) or warning "Unable to execute query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$num_links_sth->errstr;
}
}
$e_dbh->disconnect;
import_print "\nNOTE: You must run Rebuild Cat. tree, Repair Tables, and Rebuild Search after performing an import!\n";
}
# Takes 4 options: a glob ref containing an opened filehandle, a table name, a
# hash ref, a scalar delimiter, and (optionally) an array of fields to return.
# The table name should be 'Links', 'CatRelations', 'Category', or 'Sessions'.
# If you give it the fields, it will come back with an array (or array ref) of
# the values for those fields in the order specified.
# Otherwise, it will return a hash ref (or hash in list context) of the fields
# in column => value format.
#
# Call it as %rec = get_rec(\*FH, $table_name, \@db_cols, $delimiter, \@fields);
# You can, if you prefer, also make the delimiter a scalar reference.
# @db_cols should be the @db_cols from Links 1.x.
sub get_rec {
defined wantarray or return; # Don't bother doing anything in void context
my $fh = shift;
my $table_name = shift;
my $db_cols = shift;
my $delimiter = ref $_[0] eq 'SCALAR' ? ${shift()} : shift;
my ($fields,@fields,%fields);
if (@_) {
$fields = 1;
@fields = ref $_[0] eq 'ARRAY' ? @{shift()} : @_;
%fields = map { ($_ => 1) } @fields;
}
defined fileno($fh) or critical "Interal error: File handle passed to get_rec() is not an opened file";
local $/ = "\n";
my $line;
until (defined $line) {
$line = <$fh>;
return unless defined $line; # Catch the end of the file.
chomp $line;
$line ||= undef; # skip blanks
}
my $i = 0;
my @rec = split /\Q$delimiter/, $line, -1;
my %rec;
for (@rec) {
s/``/\n/g;
s/~~/|/g;
$_ = undef if $_ eq 'NULL';
}
for (0..$#rec) {
if (defined $db_cols->[$_] and (!$fields or $fields{$db_cols->[$_]})) { # Skip "extra" and unwanted records
$rec{$db_cols->[$_]} = $rec[$_];
}
}
if ($table_name eq 'Links') {
$rec{Category} =~ y/_/ / if $rec{Category};
}
elsif ($table_name eq 'Category') {
$rec{Name} =~ y/_/ / if $rec{Name};
$rec{Related} =~ y/_/ / if $rec{Related};
}
$fields or return wantarray ? %rec : \%rec;
my @ret = map $rec{$_}, @fields;
return wantarray ? @ret : \@ret;
}
# Converts a date. Returns false if the date is invalid.
sub convert_date ($) {
my $in = shift;
my ($day, $mon, $year) = split /-/, $in, 3;
my %months = qw(Jan 01 Feb 02 Mar 03 Apr 04 May 05 Jun 06 Jul 07 Aug 08 Sep 09 Oct 10 Nov 11 Dec 12);
# Any extra fields needed should be set like this:
# $months{Okt} = "10";
# $months{Mai} = "05";
# $months{Dez} = "12";
#
$day = sprintf "%02d", $day;
$year = sprintf "%04d", $year;
if ($year and $months{$mon} and $day) {
return sprintf("%04d-$months{$mon}-%02d", $year, $day);
} else {
return;
}
}
# Returns a random password of random length (20-25 characters).
sub random_pass () {
my @chars = ('a'..'z','A'..'Z',0..9,qw a _ [ ] { } ` ' " ! @ ^ * ( ) - _ = + : ; . / \ a,'#',',');
my $pass = join '', map { $chars[rand @chars] } 0..(20+rand(5));
}
"True or not true? That is the question."

View File

@ -0,0 +1,814 @@
# ==================================================================
# Gossamer Links - enhanced directory management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: L2S2.pm,v 1.39 2005/04/16 02:11:50 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::Import::L2S2;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use GT::SQL;
sub critical {
$Critical_Code->(@_);
}
sub warning {
$Warning_Code->(@_);
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
my ($have_email_db,$have_validate_db);
my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
my $e_prefix = $DB->prefix;
my $e_dbh;
{
my $table = $DB->table("Links");
$table->connect();
$e_dbh = $table->{driver}->connect();
}
local (*LINKS,*CATS,*EMAIL,$@,$!,*VALIDATE);
my $did = do {
package Links2::Def::Category; # Avoid namespace pollution
do "$$opt{source}/category.def";
};
!$did and $@ and critical "Cannot parse $$opt{source}/category.def: $@";
!$did and $! and critical "Cannot open $$opt{source}/category.def: $!";
open CATS, "<$$opt{source}/data/categories.db" or critical "Unable to open $$opt{source}/data/categories.db: $!";
$did = do {
package Links2::Def::Links;
do "$$opt{source}/links.def";
};
!$did and $@ and critical "Cannot parse $$opt{source}/links.def: $@";
!$did and $! and critical "Cannot open $$opt{source}/links.def: $!";
open LINKS, "<$$opt{source}/data/links.db" or critical "Unable to open $$opt{source}/data/links.db: $!";
if (open VALIDATE, "<$$opt{source}/data/validate.db") {
$have_validate_db = 1;
}
else {
warning "Could not open $$opt{source}/data/validate.db: $!. Non-validated links will not be imported.";
}
if (open EMAIL, "$$opt{source}/data/email.db") {
$have_email_db = 1;
}
else {
warning "Could not open $$opt{source}/data/email.db: $!. No newsletter users will be imported.";
}
my %e_standard_cols = (
Category => { map { ($_ => 1) } qw/ID Name FatherID Full_Name Description Meta_Description Meta_Keywords Header Footer Category_Template Number_of_Links Has_New_Links Has_Changed_Links Newest_Link Timestmp Payment_Mode/},
Links => { map { ($_ => 1) } qw/ID Title URL LinkOwner Add_Date Mod_Date Description Contact_Name Contact_Email Hits isNew isChanged isPopular isValidated Rating Votes Status Date_Checked Timestmp ExpiryDate ExpiryCounted ExpiryNotify/},
);
my %e_non_standard_cols;
for my $table (keys %e_standard_cols) {
my %cols = $DB->table($table)->cols;
for (grep !$e_standard_cols{$table}{$_}, keys %cols) {
$e_non_standard_cols{$table}{$_} = 1;
}
}
my %i_standard_cols = (
Category => { map { ($_ => 1) } qw/ID Name Description Related Header Footer/,'Meta Description','Meta Keywords' },
Links => { map { ($_ => 1) } qw/ID Title URL Date Category Description Hits isNew isPopular Rating Votes ReceiveMail/,'Contact Name','Contact Email'}
);
my %i_non_standard_cols;
$i_non_standard_cols{Links} = { map { !$i_standard_cols{Links}{$_} ? ($_ => 1) : () } keys %Links2::Def::Links::db_def };
$i_non_standard_cols{Category} = { map { !$i_standard_cols{Category}{$_} ? ($_ => 1) : () } keys %Links2::Def::Category::db_def };
my $alt_categories = delete $i_non_standard_cols{Links}{AltCategories};
my $Links_counter;
my $Category_counter;
my $odbc = 0;
if ($DB->table('Links')->{connect}->{driver} eq 'ODBC') {
$odbc = 1;
}
if ($$opt{clear_tables}) {
# Delete everything from all tables, EXCEPT for the `admin' user from the Users table
$e_dbh->do("DELETE FROM ${e_prefix}Users WHERE Username <> 'admin'") or critical "Unable to delete all existing users: ".$e_dbh->errstr;
for (qw/Links Category CatLinks CatRelations Category_Score_List
Category_Word_List ClickTrack Editors EmailMailings EmailTemplates
Links_Score_List Links_Word_List MailingIndex MailingList
MailingListIndex Sessions Verify/) {
$e_dbh->do("DELETE FROM $e_prefix$_");
}
unless ($$opt{straight_import}) {
$Links_counter = $Category_counter = 0;
}
}
else {
my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
$Links_counter = $sth->fetchrow_array;
$sth->finish();
$sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$sth->errstr;
$Category_counter = $sth->fetchrow_array;
$sth->finish();
}
# Subscribe users - these users receive the newsletter.
if ($have_email_db) {
my $count_users = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?");
my $add_user = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Name, Username, Password, Email, ReceiveMail) VALUES (?, ?, ?, ?, 'Yes')");
my $give_newsletter = $e_dbh->prepare("UPDATE ${e_prefix}Users SET ReceiveMail = 'Yes' WHERE Email = ?");
my $sub_imported = 0;
import_print "\nImporting Subscribe users (newsletter receivers) ...\n";
while (<EMAIL>) {
chomp;
my ($email,$name) = split /\|/;
$name ||= "";
$count_users->execute($email) or warning("Unable to count users with email $email: ".$count_users->errstr), next;
if ($count_users->fetchrow_array) {
$give_newsletter->execute($email) or warning("Unable to set ReceiveMail = 'Yes' for user with e-mail $email: ".$give_newsletter->errstr),--$sub_imported;
}
else { # User doesn't already exist
$add_user->execute($name, $email, random_pass(), $email) or warning("Unable to insert user $email: ".$add_user->errstr),--$sub_imported;
}
import_print "$sub_imported\n" unless ++$sub_imported % 500;
}
import_print "$sub_imported Subscribed users imported.\n";
}
# Categories
my %cat_map; # $cat_map{name} = new_id
my @num_of_links; # $num_of_links[category_id] = (the number of links in that category)
{
my @cat_get_cols = ('ID','Name','Related', 'Description','Meta Description','Meta Keywords','Header','Footer');
my $cat_ins_cols = "(ID, Name, FatherID, Full_Name, Description, Meta_Description, Meta_Keywords, Header, Footer";
my $cat_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?";
# Build up extra fields that exist in both old and new Category tables
for (keys %{$e_non_standard_cols{"${e_prefix}Category"}}) {
if ($i_non_standard_cols{Category}{$_}) {
$cat_ins_cols .= ", $_";
$cat_ins_vals .= ", ?";
push @cat_get_cols, $_;
}
else {
mild_warning("Custom destination column `${e_prefix}Category.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep !$e_non_standard_cols{"${e_prefix}Category"}{$_}, keys %{$i_non_standard_cols{Category}}) {
if ($opt->{create_columns}) {
if (/\W/) {
critical "Custom import column `Category.$_' cannot be imported because is is not a valid column name. You will need to rename the column name in the def file and in any relevant templates to a new name consisting only of letters, numbers, and the _ character.";
next;
}
mild_warning("Custom import column `Category.$_' had no destination equivelant. A destination column will be created");
my $editor = $DB->editor("Category");
my @def = @{$Links2::Def::Category::db_def{$_}};
$editor->add_col(
$_,
{
type => ((uc $def[1] eq 'ALPHA' and $def[3] > 255) ? 'TEXT' : 'CHAR'),
($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
size => $def[3],
($def[4] ? (not_null => 1) : ()),
($def[5] ? (default => $def[5]) : ()),
($def[6] ? (regex => $def[6]) : ()),
}
);
$cat_ins_cols .= ", $_";
$cat_ins_vals .= ", ?";
push @cat_get_cols, $_;
$e_non_standard_cols{"${e_prefix}Category"}{$_} = 1;
}
else {
warning("Custom import column `Category.$_' has no destination equivelant. It will be ignored");
}
}
$cat_ins_cols .= ")";
$cat_ins_vals .= ")";
my $add_cat_relation = $e_dbh->prepare("INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)': ".$e_dbh->errstr;
my $count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);
my $cat_ins_sth = $odbc
? ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr))
: ($e_dbh->prepare("INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr));
my $cat_ins_simple_sth = $odbc
? ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr))
: ($e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr));
my $get_id_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);
{
my ($no_warning) = (
$Links2::Def::Category::db_delim,
$Links2::Def::Links::db_delim
)
}
my @cat_relations;
my $cat_imported = 0;
import_print "\nImporting Categories ...\n";
my @cat_data;
while (my $row = get_rec(\*CATS,'Category',\%Links2::Def::Category::db_def,\$Links2::Def::Category::db_delim,\@cat_get_cols)) {
push @cat_data, $row if ref $row eq 'ARRAY';
}
@cat_data = sort { $a->[0] cmp $b->[0] } @cat_data;
my @missing_cats;
my %missing_cats;
for my $row (@cat_data) {
$row = [@$row];
my $old_id = shift @$row;
my $new_id = $$opt{straight_import} ? $old_id : ++$Category_counter;
my ($name) = (my $full_name = shift @$row) =~ m[([^/]*)\Z];
unless (defined $name and length $name) {
$Category_counter-- unless $$opt{straight_import};
warning "Cannot insert Category $full_name because it is an invalid name";
next;
}
my ($father_full_name) = $full_name =~ m[\A(.*)/];
my $father_id;
if (not defined $father_full_name) {
$father_id = 0;
}
else {
$get_id_sth->execute($father_full_name) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_id_sth->errstr;
if (my $ar = $get_id_sth->fetchrow_arrayref()) {
$father_id = $ar->[0] || 0;
}
else {
my $ins_pos = @missing_cats;
if ($$opt{create_missing_categories}) {
unless ($missing_cats{$father_full_name}++) {
splice @missing_cats, $ins_pos, 0, $father_full_name;
mild_warning "$father_full_name is needed for category $full_name and does not exist. It will be created";
my $fn = $father_full_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array or $missing_cats{$fn}++) { # It exists
$count_cats_sth->finish;
last;
}
else {
splice @missing_cats, $ins_pos, 0, $fn;
mild_warning "$fn is needed for category $full_name and does not exist. It will be created";
$count_cats_sth->finish;
}
}
}
else {
mild_warning "$father_full_name is also needed for category $full_name and is already in the queue to be created.";
}
}
else {
warning "No father row found for $full_name! This may be a serious error as $full_name should probably have a father category";
}
$father_id = 0;
}
$get_id_sth->finish;
}
$cat_relations[$new_id] = shift @$row; # This has to be dealt with later.
if ($$opt{data_integrity}) {
$count_cats_sth->execute($full_name) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
unless ($count_cats_sth->fetchrow_array) {
unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
$Category_counter-- unless $$opt{straight_import};
warning "Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr;
$count_cats_sth->finish;
next;
}
import_print "$cat_imported\n" unless ++$cat_imported % 500;
$cat_map{$full_name} = $new_id;
$num_of_links[$new_id] = 0;
$count_cats_sth->finish;
}
else {
--$Category_counter unless $$opt{straight_import};
mild_warning("Duplicate category found ($full_name) and skipped");
$count_cats_sth->finish;
next;
}
}
elsif (!$cat_map{$full_name}) {
unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
--$Category_counter unless $$opt{straight_import};
warning("Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr);
next;
}
import_print "$cat_imported\n" unless ++$cat_imported % 500;
$cat_map{$full_name} = $new_id;
$num_of_links[$new_id] = 0;
}
else {
--$Category_counter unless $$opt{straight_import};
mild_warning("Duplicate category found ($full_name) and skipped");
next;
}
}
my $missing_cats;
if ($$opt{create_missing_categories} and @missing_cats) {
my $counter = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category");
$counter->execute();
my $ins_id = $counter->fetchrow_array();
my $update_sub_cats = $e_dbh->prepare("UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?': ".$e_dbh->errstr;
for (@missing_cats) {
if ($cat_map{$_}) { # Already exists
$update_sub_cats->execute($cat_map{$_},"$_/%","$_/%/%") or critical "Unable to execute query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? and Full_Name NOT LIKE ?': ".$update_sub_cats->errstr;
next;
}
my ($name) = m[([^/]*)\Z];
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full and exists $cat_map{$father_full}) {
$father_id = $cat_map{$father_full};
}
elsif ($father_full) {
$get_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_id_sth->errstr;
$father_id = $get_id_sth->fetchrow_array;
}
else { # Must be a category of root
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$ins_id,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$cat_map{$_} = $ins_id;
$update_sub_cats->execute($ins_id,"$_/%","$_/%/%") or critical "Unable to execute query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? and Full_Name NOT LIKE ?': ".$update_sub_cats->errstr;
$missing_cats++;
}
}
import_print "$cat_imported Categories imported";
import_print ", $missing_cats missing categories created" if $missing_cats;
import_print ".\n";
# Category Relations
import_print "\nImporting Category Relations ...\n";
my $cat_rel_imported = 0;
for my $cat_id (0..$#cat_relations) {
next unless defined $cat_relations[$cat_id];
my @cats = split /\Q$Links2::Def::Category::db_delim/, $cat_relations[$cat_id];
for (@cats) {
$get_id_sth->execute($_) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ? <- $_': ".$get_id_sth->errstr;
my $rel_id = $get_id_sth->fetchrow_array;
if (defined $rel_id) {
unless ($add_cat_relation->execute($cat_id,$rel_id)) {
warning "Unable to execute query `INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)': ".$add_cat_relation->errstr;
}
else {
import_print "$cat_rel_imported\n" unless ++$cat_rel_imported % 500;
}
}
else {
warning "Unable to add category relation for category with ID $cat_id and `$_'. Reason: Category `$_' not found in database.";
}
$get_id_sth->finish;
}
}
import_print "$cat_rel_imported Category Relations imported.\n";
}
# Links
{
my @links_get_cols = ('ID','Category','Date','Contact Name','Contact Email','ReceiveMail', qw/Title URL Description Hits isNew isPopular Rating Votes/);
my $links_ins_cols = "(ID, LinkOwner, isValidated, Add_Date, Mod_Date, Contact_Name, Contact_Email, Title, URL, Description, Hits, isNew, isPopular, Rating, Votes";
my $links_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";
for (keys %{$e_non_standard_cols{"${e_prefix}Links"}}) {
if ($i_non_standard_cols{Links}{$_}) {
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
push @links_get_cols, $_;
}
else {
mild_warning("Custom destination column `${e_prefix}Links.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep !$e_non_standard_cols{"${e_prefix}Links"}{$_}, keys %{$i_non_standard_cols{Links}}) {
if ($opt->{create_columns}) {
mild_warning("Custom import column `Links.$_' had no destination equivelant. A destination column will be created");
my $editor = $DB->editor("Links");
my @def = @{$Links2::Def::Links::db_def{$_}};
$editor->add_col(
$_,
{
type => ((uc $def[1] eq 'ALPHA' and $def[3] > 255) ? 'TEXT' : 'CHAR'),
($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
size => $def[3],
($def[4] ? (not_null => 1) : ()),
($def[5] ? (default => $def[5]) : ()),
($def[6] ? (regex => $def[6]) : ())
}
);
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
push @links_get_cols, $_;
$e_non_standard_cols{"${e_prefix}Links"}{$_} = 1;
}
else {
warning("Custom import column `Links.$_' has no destination equivelant. It will be ignored");
}
}
$links_ins_cols .= ")";
$links_ins_vals .= ")";
unshift @links_get_cols, "AltCategories" if $alt_categories;
my $user_ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Password, Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')': ".$e_dbh->errstr);
my $cat_links_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$e_dbh->errstr);
my $insert_link_sth = $odbc
? ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Links ON; INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr))
: ($e_dbh->prepare("INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr));
my $user_count_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
my $username_sth = $e_dbh->prepare("SELECT Username FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT Username FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
# What other than the Name and ReceiveMail can be updated here?
my $user_mod_sth = $e_dbh->prepare("UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$e_dbh->errstr;
my $num_links_sth = $e_dbh->prepare("UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$e_dbh->errstr;
my ($count_cats_sth,$get_cat_id_sth,$cat_ins_simple_sth);
my $ins_id;
if ($$opt{create_missing_categories}) {
$count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical "Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr;
$get_cat_id_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical "Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr;
$cat_ins_simple_sth = $odbc
? ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr)
: ($e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr);
my $counter = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category");
$counter->execute();
$ins_id = $counter->fetchrow_array();
}
import_print "\nImporting Links ...\n";
my $links_imported = 0;
my $missing_cats = 0;
my @more_needed; # This will hold any missing categories (such as A/B in A/B/C)
LINK: while (my $row = get_rec(\*LINKS,'Links',\%Links2::Def::Links::db_def,\$Links2::Def::Links::db_delim,\@links_get_cols)) {
$row = [@$row]; # Remove aliasing
my $alt_cats;
$alt_cats = shift @$row if $alt_categories;
my ($id, $cat_name, $date, $contact_name, $contact_email, $receive_mail) = splice @$row,0,6;
unshift @$row, $contact_name, $contact_email;
$date = convert_date($date) or warning("Invalid date for link with ID $id. Link skipped."),next;
$id = ++$Links_counter unless $$opt{straight_import};
my @category_alternates;
if ($alt_categories) {
@category_alternates = split /\Q$Links2::Def::Links::db_delim/, $alt_cats;
for (@category_alternates) { y/_/ / }
my %dups;
# Get rid of duplicates
@category_alternates = grep !$dups{$_}++, @category_alternates;
}
my @cats = ($cat_name,@category_alternates);
my @cat_ids = @cat_map{@cats};
my $bad_cats = 0;
for my $j (0..$#cats) {
my $cat_id = $cat_ids[$j];
my $cat_name = $cats[$j];
unless (defined $cat_id) {
if ($$opt{create_missing_categories} and $cat_name) {
my @needed = my $fn = $cat_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) { # It exists
$count_cats_sth->finish;
last;
}
else {
$count_cats_sth->finish;
unshift @needed, $fn;
}
}
for (@needed) {
my ($name) = m[([^/]+)\Z];
unless ($name) {
warning "Unable to create category $_ because it is an invalid name.";
$bad_cats++;
last;
}
mild_warning("Creating category $_ as it is needed by link ID $id");
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$get_cat_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_cat_id_sth->errstr;
$father_id = $get_cat_id_sth->fetchrow_array;
$get_cat_id_sth->finish;
}
else { # Must be a root category
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$ins_id,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$cat_map{$_} = $ins_id;
$cat_ids[$j] = $ins_id;
$missing_cats++;
}
}
else {
$bad_cats++;
}
}
}
if ($bad_cats == @cat_ids) { # Between the category and the alternate categories, there has to be at least ONE good one.
if (@cat_ids == 1) {
warning "Invalid category `$cat_ids[0]' for link $$row[0] (ID: $id, line $.). Link skipped";
next LINK;
}
else {
warning "No valid categories ($cat_name @category_alternates) for link $$row[0] (ID: $id, line $.). Link skipped";
next LINK;
}
}
my $username;
$user_count_sth->execute($contact_email) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_arrayref()->[0]) { # This e-mail address already exists
$user_mod_sth->execute($contact_name, ($receive_mail eq 'Yes' ? 'Yes' : 'No'), $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
$username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
$username = $username_sth->fetchrow_arrayref()->[0];
$username_sth->finish;
}
elsif ($contact_email) {
$user_ins_sth->execute('', ($contact_email) x 2, (defined $contact_name ? $contact_name : ""), ($receive_mail eq 'Yes' ? 'Yes' : 'No')) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')': ".$user_ins_sth->errstr);
$username = $contact_email;
}
else {
mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
$username = 'admin';
}
$user_count_sth->finish;
if ($insert_link_sth->execute($id,$username,'Yes',$date,$date,@$row)) {
for my $cat_id (@cat_ids) {
$cat_links_sth->execute($id,$cat_id) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
$num_of_links[$cat_id]++;
}
import_print "$links_imported\n" unless ++$links_imported % 500;
}
else {
$Links_counter-- unless $$opt{straight_import};
warning("Unable to insert link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals'): ".$insert_link_sth->errstr);
}
}
import_print "$links_imported records from 'Links' imported.\n";
if ($have_validate_db) {
$links_imported = 0;
import_print "Importing records from 'Validate'.\n";
LINK: while(my $row = get_rec(\*VALIDATE,'Links',\%Links2::Def::Links::db_def,\$Links2::Def::Links::db_delim,\@links_get_cols)) {
$row = [@$row]; # Remove aliasing
my $alt_cats;
$alt_cats = shift @$row if $alt_categories;
my ($id, $cat_name, $date, $contact_name, $contact_email, $receive_mail) = splice @$row,0,6;
unshift @$row, $contact_name, $contact_email;
$date = convert_date($date) or warning("Invalid date `$date' for link with ID $id. Link skipped."),next;
$id = ++$Links_counter unless $$opt{straight_import};
my @category_alternates;
if ($alt_categories) {
@category_alternates = split /\Q$Links2::Def::Links::db_delim/, $alt_cats;
}
my @cats = ($cat_name,@category_alternates);
my @cat_ids = @cat_map{@cats};
my $bad_cats = 0;
for (0..$#cats) {
my $cat_id = $cat_ids[$_];
my $cat_name = $cats[$_];
unless (defined $cat_id) {
if ($$opt{create_missing_categories} and $cat_name) {
my @needed = my $fn = $cat_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) { # It exists
$count_cats_sth->finish;
last;
}
else {
$count_cats_sth->finish;
unshift @needed, $fn;
}
}
for (@needed) {
my ($name) = m[([^/]+)\Z];
unless ($name) {
warning "Unable to create category $_ because it is an invalid name.";
$bad_cats++;
last;
}
mild_warning("Creating category $_ as it is needed by link ID $id");
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$get_cat_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$get_cat_id_sth->errstr;
$father_id = $get_cat_id_sth->fetchrow_array;
$get_cat_id_sth->finish;
}
else { # Must be a root category
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$ins_id,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$cat_map{$_} = $ins_id;
$cat_id = $ins_id;
$missing_cats++;
}
}
else {
$bad_cats++;
}
}
}
if ($bad_cats == @cat_ids) { # Between the category and the alternate categories, there has to be at least ONE good one.
if (@cat_ids == 1) {
warning "Invalid category `$cat_ids[0]' for link $$row[0] (ID: $id, line $.). Link skipped";
next LINK;
}
else {
warning "No valid categories ($cat_name @category_alternates) for link $$row[0] (ID: $id, line $.). Link skipped";
next LINK;
}
}
my $username;
$user_count_sth->execute($contact_email) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_arrayref()->[0]) { # This e-mail address already exists
$user_mod_sth->execute($contact_name, ($receive_mail eq 'Yes' ? 'Yes' : 'No'), $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
$username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
$username = $username_sth->fetchrow_arrayref()->[0];
$username_sth->finish;
}
elsif ($contact_email) {
$user_ins_sth->execute('', ($contact_email) x 2, (defined $contact_name ? $contact_name : ""), ($receive_mail eq 'Yes' ? 'Yes' : 'No')) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')': ".$user_ins_sth->errstr);
$username = $contact_email;
}
else {
mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
$username = 'admin';
}
$user_count_sth->finish;
if ($insert_link_sth->execute($id,$username,'No',$date,$date,@$row)) {
for my $cat_id (@cat_ids) {
next if (! defined $cat_id);
$cat_links_sth->execute($id,$cat_id) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
$num_of_links[$cat_id]++;
}
import_print "$links_imported\n" unless ++$links_imported % 500;
}
else {
$Links_counter-- unless $$opt{straight_import};
warning("Unable to insert link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals'): ".$insert_link_sth->errstr);
}
}
import_print "$links_imported records from 'Validate' imported.\n";
}
import_print "$missing_cats categories have been created due to missing categories for links\n" if $missing_cats;
for (grep $num_of_links[$_], 0..$#num_of_links) {
$num_links_sth->execute($num_of_links[$_],$_) or warning "Unable to execute query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$num_links_sth->errstr;
}
}
$e_dbh->disconnect;
import_print "\nNOTE: You must run Rebuild Cat. tree, Repair Tables, and Rebuild Search after performing an import!\n";
}
# Takes 4 options: a glob ref containing an opened filehandle, a table name, a
# hash ref, a scalar delimiter, and (optionally) an array of fields to return.
# The table name should be 'Links', 'CatRelations', 'Category', or 'Sessions'.
# If you give it the fields, it will come back with an array (or array ref) of
# the values for those fields in the order specified.
# Otherwise, it will return a hash ref (or hash in list context) of the fields
# in column => value format.
#
# Call it as %rec = get_rec(\*FH, $table_name, \%db_def, $delimiter, \@fields);
# You can, if you prefer, also make the delimiter a scalar reference.
# The hash should be the %db_def used in Links 2.x.
sub get_rec {
defined wantarray or return; # Don't bother doing anything in void context
my $fh = shift;
my $table_name = shift;
my $db_def = shift;
my $delimiter = ref $_[0] eq 'SCALAR' ? ${shift()} : shift;
my ($fields,@fields,%fields);
if (@_) {
$fields = 1;
@fields = ref $_[0] eq 'ARRAY' ? @{shift()} : @_;
%fields = map { ($_ => 1) } @fields;
}
defined fileno($fh) or critical "Interal error: File handle passed to get_rec() is not an opened file";
my @mapping = sort { $db_def->{$a}[0] <=> $db_def->{$b}[0] } keys %$db_def;
local $/ = "\n";
my $line;
until (defined $line) {
$line = <$fh>;
return unless defined $line; # Catch the end of the file.
chomp $line;
$line ||= undef;
}
my $i = 0;
my @rec = split /\Q$delimiter/, $line, -1;
my %rec;
for (@rec) {
s/``/\n/g;
s/~~/|/g;
$_ = undef if $_ eq 'NULL';
}
for (0..$#rec) {
if (defined $mapping[$_] and (!$fields or $fields{$mapping[$_]})) { # Skip "extra" and unwanted records
$rec{$mapping[$_]} = $rec[$_];
}
}
if ($table_name eq 'Links') {
$rec{Category} =~ y/_/ / if $rec{Category};
$rec{Hits} ||= 0 if exists $rec{Hits}; # Fix for Links 2 database having the Hits table removed
}
elsif ($table_name eq 'Category') {
$rec{Name} =~ y/_/ / if $rec{Name};
$rec{Related} =~ y/_/ / if $rec{Related};
}
$fields or return wantarray ? %rec : \%rec;
my @ret = map $rec{$_}, @fields;
return wantarray ? @ret : \@ret;
}
# Converts a date. Returns false if the date is invalid.
sub convert_date ($) {
my $in = shift;
my ($day, $mon, $year) = split /-/, $in, 3;
my %months = qw(Jan 01 Feb 02 Mar 03 Apr 04 May 05 Jun 06 Jul 07 Aug 08 Sep 09 Oct 10 Nov 11 Dec 12);
# Any extra fields needed should be set like this:
# $months{Okt} = "10";
# $months{Mai} = "05";
# $months{Dez} = "12";
#
if ($year and $months{$mon} and $day) {
return sprintf("%04d-$months{$mon}-%02d", $year, $day);
} else {
warning "Invalid date `$in' encountered.";
return;
}
}
# Returns a random password of random length (20-25 characters).
sub random_pass () {
my @chars = ('a'..'z','A'..'Z',0..9,qw a _ [ ] { } ` ' " ! @ ^ * ( ) - _ = + : ; . / \ a,'#',',');
my $pass = join '', map { $chars[rand @chars] } 0..(20+rand(5));
}
"True or not true? That is the question."

View File

@ -0,0 +1,533 @@
# ==================================================================
# 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: RDFS2.pm,v 1.20 2005/04/07 19:34:41 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::Import::RDFS2;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use DBI;
use GT::SQL;
use GT::RDF;
sub critical {
$Critical_Code->(@_);
}
sub warning {
$Warning_Code->(@_);
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
my $e_prefix = $DB->prefix;
my $e_dbh;
{
my $table = $DB->table("Links");
$table->connect();
$e_dbh = $table->{driver}->connect();
}
my $Links_counter;
my $Category_counter;
my $odbc = 0;
if (($DB->table('Links')->{connect}{driver} || "") eq "ODBC") {
$odbc = 1;
# Set max read properties for DBI.
$e_dbh->{LongReadLen} = 1000000;
}
if ($$opt{clear_tables}) {
# Delete everything from all tables, EXCEPT for the `admin' user from the Users table.
# Also ignore --rdf-user if specified.
if ($$opt{rdf_user}) {
my $sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Username = ?") or critical "Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Username = ?': ".$e_dbh->errstr;
$sth->execute($$opt{rdf_user}) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Username = ?': ".$sth->errstr;
if ($sth->fetchrow_array) {
$e_dbh->do("DELETE FROM ${e_prefix}Users WHERE Username <> 'admin' AND Username <> ".$e_dbh->quote($$opt{rdf_user}));
}
else {
critical "The rdf username that you specified ($$opt{rdf_user}) does not exist. Please create the user.";
}
}
else {
$e_dbh->do("DELETE FROM ${e_prefix}Users WHERE Username <> 'admin'") or critical "Unable to delete all existing users: ".$e_dbh->errstr;
}
for (qw/Links Category CatLinks CatRelations Category_Score_List
Category_Word_List ClickTrack Editors EmailMailings EmailTemplates
Links_Score_List Links_Word_List MailingIndex MailingList
MailingListIndex Verify/) {
$e_dbh->do("DELETE FROM $e_prefix$_");
}
unless ($$opt{straight_import}) {
$Links_counter = $Category_counter = 0;
}
}
else {
my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
$sth->execute() or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
$Links_counter = $sth->fetchrow_array;
$sth->finish();
$sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$e_dbh->errstr;
$sth->execute() or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$sth->errstr;
$Category_counter = $sth->fetchrow_array;
$sth->finish();
}
my $gzip = $$opt{with_gzip};
my $need_gz = -B $$opt{source}; # Tests if the file is binary.
my $is_win = $^O =~ /win/i;
my $gzip_filename = $is_win ? "gzip.exe" : "gzip";
if (not $gzip and $need_gz) {
# Try to find gzip
my $dir_sep = $is_win ? ";" : ":";
my @locations = split /$dir_sep/, $ENV{PATH};
for (@locations) {
-x "$_/$gzip_filename" and $gzip = "$_/$gzip_filename", last;
}
$gzip or critical "\nUnable to locate gzip (Searched @locations). Please specify with --with-gzip=\"/path/to/gzip\"";
}
elsif ($gzip and -d $gzip) {
if (-x "$gzip/$gzip_filename") {
$gzip = "$gzip/$gzip_filename";
}
else {
critical "\nThe directory $gzip does not contain a valid gzip program";
}
}
if ($need_gz) {
-x $gzip or critical "\nUnable to find an executable gzip";
}
my $rdf = \do { local *FH; *FH };
if ($$opt{with_gzip} or $need_gz) {
import_print "\nOpening uncompressed stream from gzip compressed file `$$opt{source}' ...\n";
open $rdf, " $gzip -c -d $$opt{source} |" or critical "Unable to open `$gzip -c -d $$opt{source} |': $!";
}
else {
import_print "\nOpening stream from non-compressed file `$$opt{source}' ...\n";
open $rdf, "<$$opt{source}" or critical "Unable to open $$opt{source}: $!";
}
import_print "Stream opened.\n";
# Do the import
{
my $count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical "Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr;
my $cat_id_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical "Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr;
my $cat_ins_sth = $odbc
? $e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID, Number_of_Links, Has_New_Links, Has_Changed_Links) VALUES (?, ?, ?, ?, 0, 'No', 'No')")
: $e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID, Number_of_Links, Has_New_Links, Has_Changed_Links) VALUES (?, ?, ?, ?, 0, 'No', 'No')");
$cat_ins_sth or critical "Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr;
my $sub_cats_sth = $e_dbh->prepare("UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?': ".$e_dbh->errstr;
my $get_num_links_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}CatLinks WHERE CategoryID = ?") or critical "Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}CatLinks WHERE CategoryID = ?': ".$e_dbh->errstr;
my $num_links_sth = $e_dbh->prepare("UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$e_dbh->errstr;
my $insert_link_sth = $odbc
? $e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Links ON; INSERT INTO ${e_prefix}Links (ID, Title, URL, Add_Date, Mod_Date, Description, LinkOwner, Status, Votes, Rating, Hits) VALUES (?, ?, ?, ?, ?, ?, ".$e_dbh->quote($$opt{rdf_user} or "admin").",0,0,0,0)")
: $e_dbh->prepare("INSERT INTO ${e_prefix}Links (ID, Title, URL, Add_Date, Mod_Date, Description, LinkOwner, Status, Votes, Rating, Hits) VALUES (?, ?, ?, ?, ?, ?, ".$e_dbh->quote($$opt{rdf_user} or "admin").",0,0,0,0)");
$insert_link_sth or critical "Unable to prepare query `INSERT INTO ${e_prefix}Links (Title, URL, Add_Date, Mod_Date, Description, LinkOwner) VALUES (?, ?, ?, ?, ?, ?)': ".$e_dbh->errstr;
my $link_exists_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Links, ${e_prefix}CatLinks WHERE URL = ? AND CategoryID = ? AND ID = LinkID") or critical "Unable to prepare query `SELECT * FROM Links, CatLinks WHERE URL = ? AND CategoryID = ? AND ID = LinkID': ".$e_dbh->errstr;
my $cat_links_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$e_dbh->errstr;
my $count_links_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}CatLinks WHERE CategoryID = ?") or critical "Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}CatLinks WHERE CategoryID = ?': ".$e_dbh->errstr;
import_print "\nImporting from $$opt{source}\n";
my $links_imported = 0;
my $cats_imported = 0;
my %cat_needs_num; # $cat_needs_num{cat_id} = 1 if the category needs to have its Number_Of_Links updated
my $base_cat = $$opt{rdf_destination};
my $base_cat_id;
if (defined $base_cat) {
$base_cat =~ s|//+|/|g; # Remove doubled (and more) slashes
$base_cat =~ s|^/+||; # Remove any leading slashes
$base_cat =~ s|/+$||; # And any trailing ones
}
else {
$base_cat = "";
}
if (length $base_cat) {
$count_cats_sth->execute($base_cat) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) {
$cat_id_sth->execute($base_cat) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$cat_id_sth->errstr;
$base_cat_id = $cat_id_sth->fetchrow_array;
}
else { # Category doesn't exist
my @missing_cats = $base_cat;
mild_warning "$base_cat does not exist and is being created";
my $fn = $base_cat;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) { # It exists
last;
}
else {
unshift @missing_cats, $fn;
mild_warning "$fn is needed for base category $base_cat and does not exist. It will be created";
}
}
for (@missing_cats) {
my ($name) = m[([^/]+)\Z];
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$cat_id_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$cat_id_sth->errstr;
$father_id = $cat_id_sth->fetchrow_array;
}
else { # Must be the root category
$father_id = 0;
}
$cat_ins_sth->execute(++$Category_counter,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_sth->errstr;
$cats_imported++;
$sub_cats_sth->execute($Category_counter,"$_/%","$_/%/%") or critical "Unable to execute query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? and Full_Name NOT LIKE ?': ".$sub_cats_sth->errstr;
}
$base_cat_id = $Category_counter; # The last one inserted will be the category import base
}
}
else {
$base_cat_id = 0;
}
my $cat = $$opt{rdf_category};
# -------------------------------------------------------------------
# Main code, get a parser object and start parsing!
#
# New for 2.2.0 - XML::Parser-based code, which should be significantly faster.
# It should, however, still be considered experimental.
#
if ($$opt{xml_parser}) {
require XML::Parser;
my (%links, %want, %current, @in);
my $last_status = -1;
my $insert_cat = sub {
my $cat_name = $current{category};
# If we are under ODBC we need to reset the sth handle to avoid a "Invalid Cursor State" error.
$odbc and $count_cats_sth->finish;
# Check to make sure we haven't seen this category before
# Set $found to the category ID.
$count_cats_sth->execute($cat_name) or critical "Execute: $DBI::errstr";
if ($count_cats_sth->fetchrow_array) {
$cat_id_sth->finish if ($odbc); # Need to reset cursor under odbc.
$cat_id_sth->execute($cat_name) or critical "Execute: $DBI::errstr";
$current{cat_id} = $cat_id_sth->fetchrow_array;
}
else {
my ($father_name, $short_name) = $current{category} =~ m|(?:(.*)/)?([^/]+)$|;
my $father_id;
if ($father_name) {
$cat_id_sth->finish if ($odbc); # Need to reset cursor under odbc.
$cat_id_sth->execute($father_name) or critical "Execute: $DBI::errstr";
$father_id = $cat_id_sth->fetchrow_array;
}
else {
$father_id = 0;
}
$cat_ins_sth->execute(++$Category_counter, $short_name, $current{category}, $father_id) or critical "Execute: $DBI::errstr";
$cats_imported++;
$current{cat_id} = $Category_counter;
}
for my $link (keys %links) {
# Either append, or insert new link.
if ($$opt{rdf_update}) {
$link_exists_sth->execute($link, $current{cat_id}) or critical "Execute: $DBI::errstr";
next if $link_exists_sth->fetchrow;
}
# Title can only be 100 characters (ODBC fatals about data that is too long).
my $title = substr($links{$link}->{title} || $link, 0, 100);
$insert_link_sth->execute(++$Links_counter, $title, $link, $$opt{rdf_add_date}, $$opt{rdf_add_date}, $links{$link}->{description} || "") or critical "Execute: $DBI::errstr";
$cat_links_sth->execute($Links_counter, $current{cat_id}) or critical "Execute: $DBI::errstr";
$cat_needs_num{$current{cat_id}} = 1;
$links_imported++;
}
%links = ();
return scalar keys %links;
};
my $parser = XML::Parser->new(
Handlers => {
Start => sub {
my ($parser, $tag, %attr) = @_;
if ($tag eq 'Topic') {
{
my $disp_topic = $attr{'r:id'};
substr($disp_topic, 30) = '...' if length $disp_topic > 33;
my $padding = " " x (33 - length $disp_topic);
$disp_topic = "(L:$links_imported, C:$cats_imported) $disp_topic$padding";
import_print("\r$disp_topic");
}
if ($current{category}) {
my $cat_count = $insert_cat->();
import_print "$cat_count ";
}
my $dmoz_cat = $attr{'r:id'};
if ($dmoz_cat =~ /^\Q$cat\E(.*)/) {
my $topic = $base_cat . '/' . $1;
$topic =~ s|/{2,}|/|g;
$topic =~ s|^/||;
$topic =~ s|/$||;
$topic =~ y|_| |;
$current{category} = $topic;
}
else {
delete $current{category};
import_print "skipping";
}
}
elsif ($tag eq 'link' and $in[-1] eq 'Topic' and $current{category} and $attr{'r:resource'}) {
$links{$attr{'r:resource'}} = {};
}
elsif ($tag eq 'ExternalPage' and $current{category} and $attr{about}) {
$current{ExternalPage} = $attr{about};
}
elsif ($tag eq 'd:Title' and $in[-1] eq 'ExternalPage' and $current{ExternalPage}) {
$want{title} = \$links{$current{ExternalPage}}->{title};
}
elsif ($tag eq 'd:Description' and $in[-1] eq 'ExternalPage' and $current{ExternalPage}) {
$want{description} = \$links{$current{ExternalPage}}->{description};
}
push @in, $tag;
},
End => sub {
my ($parser, $tag) = @_;
pop @in;
if ($tag eq 'd:Description') {
delete $want{description};
}
elsif ($tag eq 'd:Title') {
delete $want{title};
}
},
Char => sub {
my ($parser, $text) = @_;
if ($want{title}) {
${$want{title}} = $text;
}
elsif ($want{description}) {
${$want{description}} = $text;
}
}
}
);
$parser->parse($rdf, ProtocolEncoding => 'ISO-8859-1');
}
else {
my $parse = GT::RDF->new (io => $rdf);
my ($found, $was_found, %links);
my $cat_count = 0;
my $first = 0;
while ($parse->parse) {
# Either we have a topic, external page or unknown.
if ($parse->{name} eq 'Topic') {
# Add extra links that did not have external page info.
if (defined $found) {
foreach my $link (keys %links) {
if ($$opt{rdf_update}) {
$link_exists_sth->execute($parse->{attribs}{about},$found) or critical "Execute: $DBI::errstr";
unless ($link_exists_sth->fetchrow_array) {
$insert_link_sth->execute(++$Links_counter, $link, $link, $$opt{rdf_add_date}, $$opt{rdf_add_date}, "") or critical "Execute: $DBI::errstr";
$cat_links_sth->execute($Links_counter, $found) or critical "Execute: $DBI::errstr";
$cat_needs_num{$found} = 1;
$links_imported++;
}
}
else {
$insert_link_sth->execute(++$Links_counter, $link, $link, $$opt{rdf_add_date}, $$opt{rdf_add_date}, "") or critical "Execute: $DBI::errstr";
$cat_links_sth->execute($Links_counter, $found) or critical "Execute: $DBI::errstr";
$cat_needs_num{$found} = 1;
$links_imported++;
}
}
import_print "$cat_count ";
}
else {
import_print "skipping" if $first++;
}
# Clear out our links hash, and set found to undef.
$cat_count = 0;
%links = ();
$was_found = $found;
$found = undef;
# We've finished a topic, start a new one.
my $dmoz_cat = $parse->{attribs}{'r:id'};
if ($dmoz_cat =~ /^\Q$cat\E(.*)/) {
my $topic = $base_cat . '/' . $1;
$topic =~ s|//+|/|g;
$topic =~ s|^/||;
$topic =~ s|/$||;
$topic =~ s|_| |g;
if ($topic) {
# If we are under ODBC we need to reset the sth handle to avoid a "Invalid Cursor State" error.
$odbc and ($count_cats_sth->finish);
# Check to make sure we haven't seen this category before
# Set $found to the category ID.
$count_cats_sth->execute($topic) or critical "Execute: $DBI::errstr";
if ($count_cats_sth->fetchrow_array) {
$cat_id_sth->finish if ($odbc); # Need to reset cursor under odbc.
$cat_id_sth->execute($topic) or critical "Execute: $DBI::errstr";
$found = $cat_id_sth->fetchrow_array;
}
else {
my ($short_name) = $topic =~ m|([^/]+)$|;
my ($father_name) = $topic =~ m|(.*)/|;
my $father_id;
if ($father_name) {
$cat_id_sth->finish if ($odbc); # Need to reset cursor under odbc.
$cat_id_sth->execute($father_name) or critical "Execute: $DBI::errstr";
$father_id = $cat_id_sth->fetchrow_array;
}
else {
$father_id = 0;
}
$cat_ins_sth->execute(++$Category_counter, $short_name, $topic, $father_id) or critical "Execute: $DBI::errstr";
$cats_imported++;
$found = $Category_counter;
}
}
}
{
my $disp_topic = $parse->{attribs}{'r:id'};
substr($disp_topic, 30) = '...' if length $disp_topic > 33;
my $padding = " " x (33 - length $disp_topic);
$disp_topic = "(L:$links_imported, C:$cats_imported) $disp_topic$padding";
import_print("\r$disp_topic");
}
if (defined $found) {
for (@{$parse->{tags}}) {
next unless ($_->{attribs}{'r:resource'});
$links{$_->{attribs}{'r:resource'}} = 1;
$cat_count++;
}
}
else {
return 1 if $was_found;
}
}
elsif (defined ($found) and $parse->{name} eq 'ExternalPage') {
# Remove from our simple link list.
delete $links{$parse->{attribs}{about}};
# Insert with description or title if it does not exist and we are not overwritting
my ($title, $desc);
for (@{$parse->{tags}}) {
if ($_->{name} eq 'd:Title' and $_->{data}) { $title = $_->{data} }
elsif ($_->{name} eq 'd:Description' and $_->{data}) { $desc = $_->{data} }
}
$title ||= $parse->{attribs}{about};
$desc ||= '';
# Either append, or insert new link.
if ($$opt{rdf_update}) {
$link_exists_sth->execute ($parse->{attribs}{about},$found) or critical "Execute: $DBI::errstr";
unless ($link_exists_sth->fetchrow_array) {
# Title can only be 100 characters (ODBC fatals about data that is too long).
$title = substr($title, 0, 100);
$insert_link_sth->execute(++$Links_counter, $title, $parse->{attribs}{about}, $$opt{rdf_add_date}, $$opt{rdf_add_date}, $desc) or critical "Execute: $DBI::errstr";
$cat_links_sth->execute($Links_counter, $found) or critical "Execute: $DBI::errstr";
$cat_needs_num{$found} = 1;
$links_imported++;
}
}
else {
# Title can only be 100 characters (ODBC fatals about data that is too long).
$title = substr($title, 0, 100);
$insert_link_sth->execute(++$Links_counter, $title, $parse->{attribs}{about}, $$opt{rdf_add_date}, $$opt{rdf_add_date}, $desc) or critical "Execute: $DBI::errstr";
$cat_links_sth->execute($Links_counter, $found) or critical "Execute: $DBI::errstr";
$cat_needs_num{$found} = 1;
$links_imported++;
}
}
elsif (defined $found) {
require GT::Dumper;
critical "STRANGE TAG: " . GT::Dumper::Dumper($parse) . "\n";
}
}
}
# Now we have to go through the categories to update each one's Number_Of_Links
for (keys %cat_needs_num) {
$count_links_sth->execute($_) or critical "Unable to count links for Category ID $_: ".$count_links_sth->errstr;
my $links = $count_links_sth->fetchrow_array;
$count_links_sth->finish if ($odbc);
$num_links_sth->execute($links,$_) or critical "Unable to update number of links for Category ID $_: ".$num_links_sth->errstr;
}
}
import_print "\nNOTE: You must run Rebuild Cat. tree, Repair Tables, and Rebuild Search after performing an import!\n";
$e_dbh->disconnect;
1;
}
'"I am lying," said the man. Was he?';

View File

@ -0,0 +1,802 @@
# ==================================================================
# 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: S1S2.pm,v 1.32 2005/04/16 02:11:50 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::Import::S1S2;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use DBI;
use GT::SQL;
sub critical {
$Critical_Code->(@_);
}
sub warning {
$Warning_Code->(@_);
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
for (qw/Category Links CategoryRelations CategoryAlternates Validate Users Subscribe/) {
local ($!,$@);
my $did = do "$$opt{source}/$_.def";
critical "Error parsing file $$opt{source}/$_: $@" if !$did and $@;
critical "Error reading file $$opt{source}/$_: $!" if !$did and $!;
}
# Check that all necessary databases have been loaded from the def files
my $DEBUG_counter = 0;
for ($Links::DBSQL::Category::db_driver,
$Links::DBSQL::Category::db_user,
$Links::DBSQL::Category::db_pass,
$Links::DBSQL::Category::db_host,
$Links::DBSQL::Category::db_table,
$Links::DBSQL::Category::db_name) {
defined $_ or critical "The source def files did not load correctly (Category)";
}
for ($Links::DBSQL::Links::db_driver,
$Links::DBSQL::Links::db_user,
$Links::DBSQL::Links::db_pass,
$Links::DBSQL::Links::db_host,
$Links::DBSQL::Links::db_table,
$Links::DBSQL::Links::db_name) {
defined $_ or critical "The source def files did not load correctly (Links)";
}
for ($Links::DBSQL::CategoryRelations::db_driver,
$Links::DBSQL::CategoryRelations::db_user,
$Links::DBSQL::CategoryRelations::db_pass,
$Links::DBSQL::CategoryRelations::db_host,
$Links::DBSQL::CategoryRelations::db_table,
$Links::DBSQL::CategoryRelations::db_name) {
defined $_ or critical "The source def files did not load correctly (CategoryRelations)";
}
for ($Links::DBSQL::CategoryAlternates::db_driver,
$Links::DBSQL::CategoryAlternates::db_user,
$Links::DBSQL::CategoryAlternates::db_pass,
$Links::DBSQL::CategoryAlternates::db_host,
$Links::DBSQL::CategoryAlternates::db_table,
$Links::DBSQL::CategoryAlternates::db_name) {
defined $_ or critical "The source def files did not load correctly (CategoryAlternates)";
}
for ($Links::DBSQL::Validate::db_driver,
$Links::DBSQL::Validate::db_user,
$Links::DBSQL::Validate::db_pass,
$Links::DBSQL::Validate::db_host,
$Links::DBSQL::Validate::db_table,
$Links::DBSQL::Validate::db_name) {
defined $_ or critical "The source def files did not load correctly (Validate)";
}
for ($Links::DBSQL::Users::db_driver,
$Links::DBSQL::Users::db_user,
$Links::DBSQL::Users::db_pass,
$Links::DBSQL::Users::db_host,
$Links::DBSQL::Users::db_table,
$Links::DBSQL::Users::db_name) {
defined $_ or critical "The source def files did not load correctly (Users)";
}
for ($Links::DBSQL::Subscribe::db_driver,
$Links::DBSQL::Subscribe::db_user,
$Links::DBSQL::Subscribe::db_pass,
$Links::DBSQL::Subscribe::db_host,
$Links::DBSQL::Subscribe::db_table,
$Links::DBSQL::Subscribe::db_name) {
defined $_ or critical "The source def files did not load correctly (Subscribe)";
}
my %i_dbh;
my $i_dbi_opts = { AutoCommit => 1, RaiseError => 0, PrintError => 0 };
{
my ($no_warning) = ($Links::DBSQL::Category::db_port,
$Links::DBSQL::Links::db_port,
$Links::DBSQL::Validate::db_port,
$Links::DBSQL::Users::db_port,
$Links::DBSQL::Subscribe::db_port,
$Links::DBSQL::CategoryRelations::db_port,
$Links::DBSQL::CategoryAlternates::db_port);
}
for ( ['Category', $Links::DBSQL::Category::db_name, $Links::DBSQL::Category::db_driver, $Links::DBSQL::Category::db_host, $Links::DBSQL::Category::db_port, $Links::DBSQL::Category::db_user, $Links::DBSQL::Category::db_pass ],
['Links', $Links::DBSQL::Links::db_name, $Links::DBSQL::Links::db_driver, $Links::DBSQL::Links::db_host, $Links::DBSQL::Links::db_port, $Links::DBSQL::Links::db_user, $Links::DBSQL::Links::db_pass ],
['Validate', $Links::DBSQL::Validate::db_name, $Links::DBSQL::Validate::db_driver, $Links::DBSQL::Validate::db_host, $Links::DBSQL::Validate::db_port, $Links::DBSQL::Validate::db_user, $Links::DBSQL::Validate::db_pass ],
['Users', $Links::DBSQL::Users::db_name, $Links::DBSQL::Users::db_driver, $Links::DBSQL::Users::db_host, $Links::DBSQL::Users::db_port, $Links::DBSQL::Users::db_user, $Links::DBSQL::Users::db_pass ],
['Subscribe',$Links::DBSQL::Subscribe::db_name,$Links::DBSQL::Subscribe::db_driver,$Links::DBSQL::Subscribe::db_host,$Links::DBSQL::Subscribe::db_port,$Links::DBSQL::Subscribe::db_user,$Links::DBSQL::Subscribe::db_pass],
['CategoryRelations',$Links::DBSQL::CategoryRelations::db_name,$Links::DBSQL::CategoryRelations::db_driver,$Links::DBSQL::CategoryRelations::db_host,$Links::DBSQL::CategoryRelations::db_port,$Links::DBSQL::CategoryRelations::db_user,$Links::DBSQL::CategoryRelations::db_pass],
['CategoryAlternates',$Links::DBSQL::CategoryAlternates::db_name,$Links::DBSQL::CategoryAlternates::db_driver,$Links::DBSQL::CategoryAlternates::db_host,$Links::DBSQL::CategoryAlternates::db_port,$Links::DBSQL::CategoryAlternates::db_user,$Links::DBSQL::CategoryAlternates::db_pass]) {
my $driver = $$_[2] || "mysql";
critical "The source def files did not load correctly (no \$db_name set for $$_[0] table)" unless $$_[1];
next if exists $i_dbh{$$_[1]};
my $dsn = "DBI:$driver:$$_[1]";
if ($driver eq "mysql") {
if ($$_[3]) {
$dsn .= ":$$_[3]";
if ($$_[4]) {
$dsn .= ":$$_[4]";
}
}
}
$i_dbh{$$_[1]} = DBI->connect($dsn,@$_[5,6],$i_dbi_opts) or critical("Couldn't connect to source $$_[0] db: ".$DBI::errstr);
}
my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
my $e_prefix = $DB->prefix;
my $e_dbh;
{
my $table = $DB->table("Links");
$table->connect();
$e_dbh = $table->{driver}->connect();
}
my %e_standard_cols = (
Category => { map { ($_ => 1) } qw/ID Name FatherID Full_Name Description Meta_Description Meta_Keywords Header Footer Category_Template Number_of_Links Has_New_Links Has_Changed_Links Newest_Link Timestmp Payment_Mode/},
Users => { map { ($_ => 1) } qw/Username Password Email Name Validation Status ReceiveMail/},
Links => { map { ($_ => 1) } qw/ID Title URL LinkOwner Add_Date Mod_Date Description Contact_Name Contact_Email Hits isNew isChanged isPopular isValidated Rating Votes Status Date_Checked Timestmp ExpiryDate ExpiryCounted ExpiryNotify/},
);
my %e_non_standard_cols;
for my $table (keys %e_standard_cols) {
my %cols = $DB->table($table)->cols;
for (grep !$e_standard_cols{$table}{$_}, keys %cols) {
$e_non_standard_cols{$table}{$_} = 1;
}
}
my %i_standard_cols = (
Category => { map { ($_ => 1) } qw/ID Name Description Meta_Description Meta_Keywords Header Footer Number_of_Links Has_New_Links Has_Changed_Links Newest_Link/},
Users => { map { ($_ => 1) } qw/Username Password Email Validation Status/},
Links => { map { ($_ => 1) } qw/ID Title URL Add_Date Mod_Date CategoryID Description Contact_Name Contact_Email Hits isNew isChanged isPopular Rating Votes ReceiveMail Status Date_Checked/},
Validate => { map { ($_ => 1) } qw/ID Title URL Add_Date Mod_Date CategoryID Description Contact_Name Contact_Email Hits isNew isChanged isPopular Rating Votes ReceiveMail Status Date_Checked LinkID Mode/},
);
my %i_non_standard_cols;
$i_non_standard_cols{Category} = { map { !$i_standard_cols{Category}{$_} ? ($_ => 1) : () } keys %Links::DBSQL::Category::db_def } if keys %Links::DBSQL::Category::db_def;
$i_non_standard_cols{Users} = { map { !$i_standard_cols{Users}{$_} ? ($_ => 1) : () } keys %Links::DBSQL::Users::db_def } if keys %Links::DBSQL::Users::db_def;
$i_non_standard_cols{Links} = { map { !$i_standard_cols{Links}{$_} ? ($_ => 1) : () } keys %Links::DBSQL::Links::db_def } if keys %Links::DBSQL::Links::db_def;
$i_non_standard_cols{Validate} = { map { !$i_standard_cols{Validate}{$_} ? ($_ => 1) : () } keys %Links::DBSQL::Validate::db_def } if keys %Links::DBSQL::Validate::db_def;
my $Links_counter;
my $Category_counter;
my $odbc = 0;
if ($DB->table('Links')->{connect}->{driver} eq 'ODBC') {
$odbc = 1;
$i_dbh{$Links::DBSQL::Links::db_name}->{LongReadLen} = 1000000;
}
if ($$opt{clear_tables}) {
# Delete everything from all tables, EXCEPT for the `admin' user from the Users table
$e_dbh->do("DELETE FROM ${e_prefix}Users WHERE Username <> 'admin'") or critical "Unable to delete all existing users: ".$e_dbh->errstr;
for (qw/Links Category CatLinks CatRelations Category_Score_List
Category_Word_List ClickTrack Editors EmailMailings EmailTemplates
Links_Score_List Links_Word_List MailingIndex MailingList
MailingListIndex Sessions Verify/) {
$e_dbh->do("DELETE FROM $e_prefix$_");
}
unless ($$opt{straight_import}) {
$Links_counter = $Category_counter = 0;
}
}
else {
my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
$Links_counter = $sth->fetchrow_array;
$sth->finish();
$sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$sth->errstr;
$Category_counter = $sth->fetchrow_array;
$sth->finish();
}
# Users
{
my $get_cols = "Username, Password, Email, Validation, Status";
my $ins_cols = "(Name, Username, Password, Email, Validation, Status";
my $ins_vals = "(?, ?, ?, ?, ?, ?";
for (keys %{$e_non_standard_cols{"${e_prefix}Users"}}) {
if ($i_non_standard_cols{Users}{$_}) {
$ins_cols .= ", $_";
$ins_vals .= ", ?";
$get_cols .= ", $_";
}
else {
mild_warning("Custom destination column `${e_prefix}Users.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep +(not $e_standard_cols{"${e_prefix}Users"}{$_} and not $e_non_standard_cols{"${e_prefix}Users"}{$_}), keys %{$i_non_standard_cols{Users}}) {
next if $e_non_standard_cols{"${e_prefix}Users"}{$_};
if ($opt->{create_columns}) {
mild_warning("Custom import column `Users.$_' had no destination equivelant. A column will be created");
my $editor = $DB->editor("Users");
my @def = @{$Links::DBSQL::Users::db_def{$_}};
$editor->add_col(
$_,
{
type => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
size => $def[3],
($def[4] ? (not_null => 1) : ()),
($def[5] ? (default => $def[5]) : ()),
($def[6] ? (regex => $def[6]) : ()),
($def[7] ? (weight => $def[7]) : ())
}
);
$ins_cols .= ", $_";
$ins_vals .= ", ?";
$get_cols .= ", $_";
$e_non_standard_cols{"${e_prefix}Users"}{$_} = 1;
}
else {
warning("Custom import column `Users.$_' has no destination equivelant. It will be ignored");
}
}
$ins_cols .= ")";
$ins_vals .= ")";
my $sth = $i_dbh{$Links::DBSQL::Users::db_name}->prepare("SELECT $get_cols FROM $Links::DBSQL::Users::db_table") or critical("Unable to prepare query `SELECT $get_cols FROM $Links::DBSQL::Users::db_table': ".$i_dbh{$Links::DBSQL::Users::db_name}->errstr);
$sth->execute() or critical("Unable to execute query `SELECT $get_cols FROM $Links::DBSQL::Users::db_table': ".$sth->errstr);
my $ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Users $ins_cols VALUES $ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Users $ins_cols VALUES $ins_vals': ".$e_dbh->errstr);
my $user_count_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
my $username_sth = $e_dbh->prepare("SELECT Username FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT Username FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
# What other than the Name and ReceiveMail can be updated here?
my $user_mod_sth = $e_dbh->prepare("UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$e_dbh->errstr;
while (my $row = $sth->fetchrow_arrayref) {
$user_count_sth->execute($$row[2]) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_array) { # This e-mail address already exists, so skip it
next;
}
$ins_sth->execute(@$row[0,0],($$row[1] or random_pass()),@$row[2..$#$row]) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users $ins_cols VALUES $ins_vals' ($$row[0]): ".$ins_sth->errstr),next;
}
}
# Subscribe users - these users receive the newsletter.
{
my $get_subscribers = $i_dbh{$Links::DBSQL::Subscribe::db_name}->prepare("SELECT Name, Email FROM $Links::DBSQL::Subscribe::db_table") or warning("Unable to prepare query `SELECT Name, Email FROM $Links::DBSQL::Subscribe::db_table': ".$i_dbh{$Links::DBSQL::Subscribe::db_name}->errstr);
$get_subscribers->execute();
my $count_users = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?");
my $add_user = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Name, Username, Password, Email, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Yes', 'Registered')");
my $give_newsletter = $e_dbh->prepare("UPDATE ReceiveMail = 'Yes' WHERE Email = ?");
my $sub_imported = 0;
import_print "\nImporting Subscribed users (users who receive the newsletter) ...\n";
while (my $row = $get_subscribers->fetchrow_arrayref) {
# If we are under ODBC we need to reset the sth handle to avoid a "Invalid Cursor State" error.
$odbc and ($count_users->finish);
$count_users->execute($$row[1]) or warning("Unable to count users with email $$row[1]: ".$count_users->errstr), next;
if ($count_users->fetchrow_array) {
$give_newsletter->execute($$row[1]) or warning("Unable to set Newsletter = 'Yes' for user with e-mail $$row[1]: ".$give_newsletter->errstr),--$sub_imported;
}
else { # User doesn't already exist
$add_user->execute($$row[0], $$row[1], random_pass(), $$row[1]) or warning("Unable to insert user $$row[1]: ".$add_user->errstr),--$sub_imported;
}
import_print "$sub_imported\n" unless ++$sub_imported % 500;
}
import_print "$sub_imported Subscribed users imported.\n";
}
# Everything else (in most cases including even more users)
{
# Category select statements
my $cat_get_cols = "ID, Name, Description, Meta_Description, Meta_Keywords, " .
"Header, Footer, Number_of_Links, Has_New_Links, Has_Changed_Links, Newest_Link";
my $cat_ins_cols = "(ID, Name, FatherID, Full_Name, Description, Meta_Description, Meta_Keywords, " .
"Header, Footer, Number_of_Links, Has_New_Links, Has_Changed_Links, Newest_Link";
my $cat_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";
# Links select statements
my $links_get_cols = "ID, Contact_Name, Contact_Email, ReceiveMail, CategoryID, " .
"Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
"isChanged, isPopular, Rating, Votes, Status, Date_Checked";
my $links_ins_cols = "(ID, LinkOwner, isValidated, Contact_Name, Contact_Email, " .
"Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
"isChanged, isPopular, Rating, Votes, Status, Date_Checked";
my $links_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";
# Validate select statements
my $validate_get_cols = "ID, Contact_Name, Contact_Email, ReceiveMail, CategoryID, " .
"Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
"isChanged, isPopular, Rating, Votes, Status, Date_Checked";
my $validate_ins_cols = "(ID, LinkOwner, isValidated, Contact_Name, Contact_Email, " .
"Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
"isChanged, isPopular, Rating, Votes, Status, Date_Checked";
my $validate_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";
# Build up extra fields that exist in both old and new Category tables
for (keys %{$e_non_standard_cols{"${e_prefix}Category"}}) {
if ($i_non_standard_cols{Category}{$_}) {
$cat_ins_cols .= ", $_";
$cat_ins_vals .= ", ?";
$cat_get_cols .= ", $_";
}
else {
mild_warning("Custom destination column `${e_prefix}Category.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep !$e_non_standard_cols{"${e_prefix}Category"}{$_}, keys %{$i_non_standard_cols{Category}}) {
next if $e_non_standard_cols{"${e_prefix}Category"}{$_};
if ($opt->{create_columns}) {
mild_warning("Custom import column `Category.$_' had no destination equivelant. A destination column will be created");
my $editor = $DB->editor("Category");
my @def = @{$Links::DBSQL::Category::db_def{$_}};
$editor->add_col(
$_,
{
type => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
size => $def[3],
($def[4] ? (not_null => 1) : ()),
($def[5] ? (default => $def[5]) : ()),
($def[6] ? (regex => $def[6]) : ()),
($def[7] ? (weight => $def[7]) : ())
}
);
$cat_ins_cols .= ", $_";
$cat_ins_vals .= ", ?";
$cat_get_cols .= ", $_";
$e_non_standard_cols{"${e_prefix}Category"}{$_} = 1;
}
else {
warning("Custom import column `Category.$_' has no destination equivelant. It will be ignored");
}
}
$cat_ins_cols .= ")";
$cat_ins_vals .= ")";
for (keys %{$e_non_standard_cols{"${e_prefix}Links"}}) {
if ($i_non_standard_cols{Links}{$_}) {
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
$links_get_cols .= ", $_";
}
else {
mild_warning("Custom destination column `${e_prefix}Links.$_' has no equivelant import column. It will contain the default values for the column");
}
}
for (grep !$e_non_standard_cols{"${e_prefix}Links"}{$_}, keys %{$i_non_standard_cols{Links}}) {
next if $e_non_standard_cols{"${e_prefix}Links"}{$_};
if ($opt->{create_columns}) {
mild_warning("Custom import column `Links.$_' had no destination equivelant. A destination column will be created");
my $editor = $DB->editor("Links");
my @def = @{$Links::DBSQL::Links::db_def{$_}};
$editor->add_col(
$_,
{
type => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
size => $def[3],
($def[4] ? (not_null => 1) : ()),
($def[5] ? (default => $def[5]) : ()),
($def[6] ? (regex => $def[6]) : ()),
($def[7] ? (weight => $def[7]) : ())
}
);
$links_ins_cols .= ", $_";
$links_ins_vals .= ", ?";
$links_get_cols .= ", $_";
$e_non_standard_cols{"${e_prefix}Links"}{$_} = 1;
}
else {
warning("Custom import column `Links.$_' has no destination equivelant. It will be ignored");
}
}
$links_ins_cols .= ")";
$links_ins_vals .= ")";
for (keys %{$e_non_standard_cols{"${e_prefix}Links"}}) {
if ($i_non_standard_cols{Validate}{$_}) {
$validate_ins_cols .= ", $_";
$validate_ins_vals .= ", ?";
$validate_get_cols .= ", $_";
}
else {
mild_warning("Custom destination column `${e_prefix}Links.$_' has no equivelant Validate import column. It will contain the default values for the column");
}
}
for (grep !$e_non_standard_cols{"${e_prefix}Links"}{$_}, keys %{$i_non_standard_cols{Validate}}) {
next if $e_non_standard_cols{"${e_prefix}Links"}{$_};
if ($opt->{create_columns}) {
mild_warning("Custom import column `Validate.$_' had no destination Links equivelant. A destination column will be created");
my $editor = $DB->editor("Links");
my @def = @{$Links::DBSQL::Validate::db_def{$_}};
$editor->add_col(
$_,
{
type => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
size => $def[3],
($def[4] ? (not_null => 1) : ()),
($def[5] ? (default => $def[5]) : ()),
($def[6] ? (regex => $def[6]) : ()),
($def[7] ? (weight => $def[7]) : ())
}
);
$validate_ins_cols .= ", $_";
$validate_ins_vals .= ", ?";
$validate_get_cols .= ", $_";
$e_non_standard_cols{"${e_prefix}Links"}{$_} = 1;
}
else {
warning("Custom import column `Validate.$_' has no destination equivelant. It will be ignored");
}
}
$validate_ins_cols .= ")";
$validate_ins_vals .= ")";
my $cat_sth = $i_dbh{$Links::DBSQL::Category::db_name}->prepare("SELECT $cat_get_cols FROM $Links::DBSQL::Category::db_table ORDER BY Name") or critical("Unable to prepare query `SELECT $cat_get_cols FROM $Links::DBSQL::Category::db_table ORDER BY Name': ".$i_dbh{$Links::DBSQL::Category::db_name}->errstr);
$cat_sth->execute() or critical("Unable to execute query `SELECT $cat_get_cols FROM $Links::DBSQL::Category::db_table ORDER BY Name': ".$cat_sth->errstr);
my $get_cat_relations = $i_dbh{$Links::DBSQL::CategoryRelations::db_name}->prepare("SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table") or critical "Unable to prepare query `SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table': ".$i_dbh{$Links::DBSQL::CategoryRelations::db_name}->errstr;
my $add_cat_relation = $e_dbh->prepare("INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)': ".$e_dbh->errstr;
my @cat_map; # $cat_map[old_id] = new_id; Don't need this with --straight-import enabled
my $count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);
my $get_cat_alts = $i_dbh{$Links::DBSQL::CategoryAlternates::db_name}->prepare("SELECT CategoryID FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?") or critical "Unable to prepare query `SELECT * FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?': ".$i_dbh{$Links::DBSQL::CategoryAlternates::db_name}->errstr;
my $cat_ins_sth = $odbc ?
($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr)) :
($e_dbh->prepare("INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr));
my $cat_ins_simple_sth = $odbc ?
($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr)) :
($e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr));
my $user_ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Username, Email, Password, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')': ".$e_dbh->errstr);
my $cat_links_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$e_dbh->errstr);
my $insert_link_sth = $odbc ?
($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Links ON; INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr)) :
($e_dbh->prepare("INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr));
my $insert_vlink_sth = $odbc ?
($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Links ON; INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals': ".$e_dbh->errstr)) :
($e_dbh->prepare("INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals': ".$e_dbh->errstr));
my $father_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);
my $get_links_sth = $i_dbh{$Links::DBSQL::Links::db_name}->prepare("SELECT $links_get_cols FROM $Links::DBSQL::Links::db_table WHERE CategoryID = ?") or critical("Unable to prepare query `SELECT $links_get_cols FROM $Links::DBSQL::Links::db_table WHERE CategoryID = ?': ".$i_dbh{$Links::DBSQL::Links::db_name}->errstr);
my $get_vlinks_sth = $i_dbh{$Links::DBSQL::Validate::db_name}->prepare("SELECT $validate_get_cols FROM $Links::DBSQL::Validate::db_table WHERE CategoryID = ?") or critical("Unable to prepare query `SELECT $validate_get_cols FROM $Links::DBSQL::Validate::db_table WHERE CategoryID = ?': ".$i_dbh{$Links::DBSQL::Validate::db_name}->errstr);
my $user_count_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
my $username_sth = $e_dbh->prepare("SELECT Username FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT Username FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
# What other than the Name and ReceiveMail can be updated here?
my $user_mod_sth = $e_dbh->prepare("UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$e_dbh->errstr;
my $num_links_sth = $e_dbh->prepare("UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$e_dbh->errstr;
import_print "\nImporting Categories and Links ...\n";
my $links_imported = 0;
my $cats_imported = 0;
my @missing_cats; # contains the Full_Name's of missing categories.
my %missing_cats; # contains Full_name => true for missing categories.
# Have to go through hoops here as ODBC can only run one sth at a time.
my $sub;
if ($odbc) {
my $results = $cat_sth->fetchall_arrayref;
$cat_sth->finish;
import_print "\n\tImporting ", scalar @$results, " categories ..\n";
$sub = sub { return shift @$results; }
}
else {
$sub = sub { $cat_sth->fetchrow_arrayref; }
}
while(my $row = $sub->()) {
$row = [@$row];
my $old_id = shift @$row;
my $new_id = $$opt{straight_import} ? $old_id : ++$Category_counter;
my ($name) = (my $full_name = shift @$row) =~ m[([^/]*)\Z];
unless (defined $name and length $name) {
$Category_counter-- unless $$opt{straight_import};
warning "Cannot insert Category $full_name because it is an invalid name";
next;
}
my ($father_full_name) = $full_name =~ m[\A(.*)/];
my $father_id;
if (not defined $father_full_name) {
$father_id = 0;
}
else {
$odbc and $father_sth->finish;
$father_sth->execute($father_full_name) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$father_sth->errstr;
if (my $ar = $father_sth->fetchrow_arrayref()) {
$father_id = $ar->[0] || 0;
}
else {
if ($$opt{create_missing_categories}) {
if ($missing_cats{$father_full_name}++) {
mild_warning "$father_full_name is needed for category $full_name and is already in the list of categories to be created";
}
else {
my $ins_pos = @missing_cats;
splice @missing_cats, $ins_pos, 0, $father_full_name;
mild_warning "$father_full_name is needed for category $full_name and does not exist. It will be created";
my $fn = $father_full_name;
while ($fn =~ s[/[^/]*\Z][]) {
$count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
if ($count_cats_sth->fetchrow_array) { # It exists
last;
}
else {
splice @missing_cats, $ins_pos, 0, $fn;
mild_warning "$fn is needed for category $full_name and does not exist. It will be created";
}
}
}
}
else {
warning "No father row found for $full_name! This may be a serious error as $full_name should probably have a father category";
}
$father_id = 0;
}
}
if ($$opt{data_integrity}) {
$odbc and $count_cats_sth->finish;
$count_cats_sth->execute($full_name) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
unless ($count_cats_sth->fetchrow_array) {
unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
$Category_counter-- unless $$opt{straight_import};
warning "Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr;
next;
}
elsif (not $$opt{straight_import}) {
$cat_map[$old_id] = $new_id;
}
}
else {
--$Category_counter unless $$opt{straight_import};
mild_warning("Duplicate category found ($full_name) and skipped");
next;
}
}
else {
unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
--$Category_counter unless $$opt{straight_import};
warning("Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr);
next;
}
elsif (not $$opt{straight_import}) {
$cat_map[$old_id] = $new_id;
}
}
import_print "$cats_imported Categories imported\n" unless ++$cats_imported % 500;
my $num_of_links = 0;
my $link_sub;
$get_links_sth->execute($old_id) or critical "Unable to execute query: ".$get_links_sth->errstr;
if ($odbc) {
my $links_results = $get_links_sth->fetchall_arrayref;
$get_links_sth->finish;
$link_sub = sub { return shift @$links_results; }
}
else {
$link_sub = sub { $get_links_sth->fetchrow_arrayref; }
}
while(my $row = $link_sub->()) {
$row = [@$row];
my ($id, $contact_name, $contact_email, $receive_mail, $cat_id) = splice @$row,0,5;
unshift @$row, $contact_name, $contact_email;
$get_cat_alts->execute($id) or critical "Unable to execute query `SELECT CategoryID FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?': ".$get_cat_alts->errstr;
my @alt_ids;
while (my $row = $get_cat_alts->fetchrow_arrayref) {
push @alt_ids, ($$opt{straight_import} ? $$row[0] : $cat_map[$$row[0]]);
}
$id = ++$Links_counter unless $$opt{straight_import};
my $username;
$odbc and $user_count_sth->finish;
$user_count_sth->execute($contact_email) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_array) { # This e-mail address already exists
$user_mod_sth->execute($contact_name, ($receive_mail eq 'Yes' ? 'Yes' : 'No'), $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
$odbc and $username_sth->finish;
$username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
$username = $username_sth->fetchrow_arrayref()->[0];
}
elsif ($contact_email) {
$user_ins_sth->execute(($contact_email) x 2, '', (defined $contact_name ? $contact_name : ""), ($receive_mail eq 'Yes' ? 'Yes' : 'No')) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Password, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Registered')': ".$user_ins_sth->errstr);
$username = $contact_email;
}
else {
mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
$username = 'admin';
}
if ($insert_link_sth->execute($id,$username,'Yes',@$row)) {
for ($new_id,@alt_ids) {
if (! defined $_) { next; }
$cat_links_sth->execute($id,$_) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
}
$num_of_links++;
import_print "$links_imported Links imported\n" unless ++$links_imported % 500;
}
else {
$Links_counter-- unless $$opt{straight_import};
warning("Unable to insert validated link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals'): ".$insert_link_sth->errstr);
}
}
{
# Even with a straight import, Validate ID's cannot stay the same because they would conflict with link ID's.
my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
$Links_counter = $sth->fetchrow_array;
$sth->finish;
if ($$opt{straight_import}) {
# For a straight import, we need to make sure that the link ID's used
# for non-validated links start after the highest old Link ID.
$sth = $i_dbh{$Links::DBSQL::Links::db_name}->prepare("SELECT MAX(ID) FROM $Links::DBSQL::Users::db_table") or critical "Unable to prepare query `SELECT MAX(ID) FROM $Links::DBSQL::Users::db_table: ".$i_dbh{$Links::DBSQL::Links::db_name}->errstr;
$sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM $Links::DBSQL::Users::db_table: ".$sth->errstr;
my $old_max = $sth->fetchrow_array;
$sth->finish;
$Links_counter = $old_max if $old_max > $Links_counter;
}
}
$get_vlinks_sth->execute($old_id) or critical "Unable to execute query: ".$get_vlinks_sth->errstr;
if ($odbc) {
my $links_results = $get_vlinks_sth->fetchall_arrayref;
$get_vlinks_sth->finish;
$link_sub = sub { return shift @$links_results }
}
else {
$link_sub = sub { $get_vlinks_sth->fetchrow_arrayref }
}
while(my $row = $link_sub->()) {
$row = [@$row]; # Get rid of a peculiar read-only aliasing in DBI
my ($id, $contact_name, $contact_email, $receive_mail, $cat_id) = splice @$row,0,5;
unshift @$row, $contact_name, $contact_email;
$get_cat_alts->execute($id) or critical "Unable to execute query `SELECT CategoryID FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?': ".$get_cat_alts->errstr;
my @alt_ids;
while (my $row = $get_cat_alts->fetchrow_arrayref) {
push @alt_ids, ($$opt{straight_import} ? $$row[0] : $cat_map[$$row[0]]);
}
$id = ++$Links_counter;
my $username;
$user_count_sth->execute($contact_email) or warning("Unable to execute query: ".$user_count_sth->errstr);
if ($user_count_sth->fetchrow_array) { # Exists
$user_mod_sth->execute($contact_name, ($receive_mail eq 'Yes' ? 'Yes' : 'No'), $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
$username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
$username = $username_sth->fetchrow_arrayref()->[0];
}
elsif ($contact_email) { # Doesn't exist, but we can make the e-mail address into a username
$user_ins_sth->execute(($contact_email) x 2, '', (defined $contact_name ? $contact_name : ""), ($receive_mail eq 'Yes' ? 'Yes' : 'No')) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail) VALUES (?, ?, ?, ?, ?)': ".$user_ins_sth->errstr);
$username = $contact_email;
}
else { # Can't make a user; use the `admin' user.
mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
$username = 'admin';
}
if ($insert_vlink_sth->execute($id,$username,'No',@$row)) {
for ($id,@alt_ids) {
$cat_links_sth->execute($_,$new_id) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
}
$num_of_links++;
import_print "$links_imported Links imported\n" unless ++$links_imported % 500;
}
else {
$Links_counter--;
warning("Unable to insert non-validated link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals'): ".$insert_vlink_sth->errstr);
}
}
$num_links_sth->execute($num_of_links,$new_id) or warning "Unable to execute query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$num_links_sth->errstr;
}
my $missing_cats;
if ($$opt{create_missing_categories} and @missing_cats) {
my $counter = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category");
$counter->execute();
my $count = $counter->fetchrow_array();
my $update_sub_cats = $e_dbh->prepare("UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?': ".$e_dbh->errstr;
for (@missing_cats) {
my ($name) = m[([^/]+)\Z];
my ($father_full) = m[\A(.*)/];
my $father_id;
if ($father_full) {
$father_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$father_sth->errstr;
$father_id = $father_sth->fetchrow_array;
}
else { # Must be a root category
$father_id = 0;
}
$cat_ins_simple_sth->execute(++$count,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
$update_sub_cats->execute($count,"$_/%","$_/%/%") or critical "Unable to execute query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? and Full_Name NOT LIKE ?': ".$update_sub_cats->errstr;
$missing_cats++;
}
}
import_print "$cats_imported Categories imported";
import_print ", $missing_cats missing categories created" if $missing_cats;
import_print ".\n";
import_print "$links_imported Links imported.\n";
# Category Relations:
if ($$opt{straight_import}) {
$get_cat_relations->execute or critical "Unable to execute query `SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table': ".$get_cat_relations->errstr;
while (my $row = $get_cat_relations->fetchrow_arrayref) {
$add_cat_relation->execute(@$row) or warning "Unable to add category relation for categories with ID's $$row[0] and $$row[1]. Reason: ".$add_cat_relation->errstr;
}
}
else {
$get_cat_relations->execute or critical "Unable to execute query `SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table': ".$get_cat_relations->errstr;
while (my $row = $get_cat_relations->fetchrow_arrayref) {
$add_cat_relation->execute(@cat_map[@$row]) or warning "Unable to add category relation for categories with ID's: (new: $cat_map[$$row[0]], old: $$row[0]) and (new: $cat_map[$$row[1]], old: $$row[1]). Reason: ".$add_cat_relation->errstr;
}
}
}
for (keys %i_dbh) {
$i_dbh{$_}->disconnect;
}
$e_dbh->disconnect;
import_print "\nNOTE: You must run Rebuild Cat. tree, Repair Tables, and Rebuild Search after performing an import!\n";
1;
}
# Returns a random password of random length (20-25 characters).
sub random_pass () {
my @chars = ('a'..'z','A'..'Z',0..9,qw a _ [ ] { } ` ' " ! @ ^ * ( ) - _ = + : ; . / \ a,'#',',');
my $pass = join '', map { $chars[rand @chars] } 0..(20+rand(5));
}
1;

View File

@ -0,0 +1,152 @@
# ==================================================================
# 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: S2BK.pm,v 1.13 2009/05/09 06:35:25 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::Import::S2BK;
use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;
use GT::SQL;
use Links qw/$CFG/;
sub critical {
$Critical_Code->(@_);
}
sub warning {
$Warning_Code->(@_);
}
sub mild_warning {
ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}
sub import_print {
if (ref $Print_Out eq 'CODE') {
$Print_Out->(@_);
}
else {
print @_;
}
}
# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
my $opt = shift;
return if ref $opt ne 'HASH';
{
my $warning = shift;
return if ref $warning ne 'CODE';
$Warning_Code = $warning;
my $critical = shift;
return if ref $critical ne 'CODE';
$Critical_Code = $critical;
my $mild = shift;
$Mild_Code = $mild if ref $mild eq 'CODE';
my $output = shift;
$Print_Out = $output if ref $output eq 'CODE';
}
my $DB = new GT::SQL(def_path => $$opt{source}, subclass => 0);
my $prefix = $DB->prefix || "";
my $delimiter = $$opt{delimiter};
critical "Invalid delimiter `".(defined$delimiter?$delimiter:'')."' for a delimited file!"
unless defined $delimiter and length $delimiter == 1 and $delimiter ne '\\';
my @tables;
opendir (D, "$CFG->{admin_root_path}/defs") or critical "unable to opendir $CFG->{admin_root_path}/defs ($!)";
while (defined (my $def = readdir(D))) {
next unless $def =~ /^\Q$prefix\E(.*)\.def$/;
push @tables, $1 if $1 !~ /_(?:Word|Score)_List$/;
}
local ($,,$\,*EXPORT_FH);
open EXPORT_FH, "> $$opt{destination}" or critical "Unable to open $$opt{destination} for writing: $!";
binmode EXPORT_FH; # this is NOT a text file.
print EXPORT_FH "Links SQL 2 backup. This backup was generated at " . gmtime() . " UTC. THIS FILE IS NOT A TEXT FILE. You should NOT attempt to edit this file as you will end up corrupting the data contained in it.\0";
=pod
Schematic for the file:
- Newline delimiter is changed to \0 (hex and ascii 0).
- Each line starting with '\\\\' starts off a new table.
- The first line following the '\\\\' is the table name by itself (NOT prefixed).
- The first character of the line after that is the delimiter for that table, and
the rest of that line is the columns of the table delimited by the delimiter.
- All subsequent lines (until another '\\\\') are individual records.
- All fields (headers and records) are escaped where needed in '\\XX' format
(where 'XX' is the hexadecimal representation of the character).
- All lines until the first '\\\\' are treated as comments and are ignored.
- Everything following '\\\\' is treated as a comment and is ignored.
=cut
for my $t (@tables) {
$GT::SQL::error = '';
my $table = $DB->table($t);
my $count = $table->count;
next if $GT::SQL::error;
import_print "Exporting $prefix$t ...\n";
print EXPORT_FH "\\\\ The following is table $t".($prefix ? " (from prefixed table $prefix$t)" : "")."\0";
print EXPORT_FH "$t\0";
print EXPORT_FH $delimiter; # The first character on this line is the delimiter
local ($a,$b);
print EXPORT_FH join($delimiter, sort { $table->{schema}{cols}{$a}{pos} <=> $table->{schema}{cols}{$b}{pos} } map BK_escape($_,$delimiter), keys %{$table->cols}),"\0";
my $sth;
my $printed = 0;
for my $i (0 .. $count/1000) {
$sth = $table->prepare("SELECT * FROM $prefix$t LIMIT ".($i * 1000).", 1000") or critical "Unable to prepare query `SELECT * FROM $prefix$t LIMIT ".($i * 1000).", 1000': ".$sth->errstr;
$sth->execute();
while (my $row = $sth->fetchrow_arrayref) {
print EXPORT_FH join($delimiter, map BK_escape($_,$delimiter), @$row),"\0";
unless (++$printed % 500) {
import_print "$printed records from $prefix$t exported ...\n";
}
}
}
import_print "$printed records from $prefix$t exported.\n",
"All records from $prefix$t have been exported.\n\n";
}
close EXPORT_FH;
}
# Takes two parameters: The field to escape, and the delimiter. It will return
# the escaped form of the field.
sub BK_escape ($$) {
return unless defined wantarray;
my $field = shift;
my $delimiter = shift;
$delimiter = "" unless defined $delimiter;
critical "Bad delimiter `$delimiter'" unless length $delimiter == 1 and $delimiter ne '\\';
my $escape_chr = '\\';
if (not defined $field) {
return 'NULL';
}
elsif ($field eq 'NULL') {
return 'NUL\4C'; # If it is the actual string 'NULL' this will keep it
} # from being recognized as a NULL field when it is read in again.
$field =~ s/([\Q$delimiter$escape_chr\E\x00-\x1f])/sprintf '\%02X', ord $1/ge;
$field;
}
"Once upon a time, in a galaxy far, far away . . . There was a true value";

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,622 @@
# ==================================================================
# 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: Newsletter.pm,v 1.15 2007/09/06 01:43:45 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
# Notes about the Newsletter code:
# ================================
# Example category structure:
# a
# b
# c
# d
# If a user is subscribed to a category (eg. category a), then they will
# be automatically subscribed to all the subcategories of that category
# (ie. b, c, d). If the user is already subscribed to a subcategory
# (eg. b), then that subscription will be removed when they subscribe to
# a parent category (ie. a). This keeps listing subscribed categories
# simple.
#
# Remember that the root category (0) is a special category and needs to be
# handled appropriately. It is not a real category as it does not exist in
# the Category table.
package Links::Newsletter;
use strict;
use Links qw/:objects/;
use Links::SiteHTML;
use GT::Dumper;
sub handle {
# ---------------------------------------------------
# Determine what to do.
#
my $res;
my $action = lc $IN->param('action');
require Links::Build;
my $mtl = Links::Build::build('title', Links::language('LINKS_NEWSLETTER'), "$CFG->{db_cgi_url}/subscribe.cgi");
# Custom lists
if ($IN->param('list')) {
my $email = $IN->param('email');
if ($email and $action eq 'subscribe') {
$res = $PLG->dispatch('custom_list_subscribe', \&custom_list_subscribe);
}
elsif ($email and $action eq 'unsubscribe') {
$res = $PLG->dispatch('custom_list_unsubscribe', \&custom_list_unsubscribe);
}
else {
$res = { error => Links::language('SUBSCRIBE_ERROR') };
}
$res->{main_title_loop} ||= $mtl;
print $IN->header();
print Links::SiteHTML::display('newsletter', $res);
}
# With the old Newsletter code, anyone could sign up to it. This is bad since
# no e-mail validation is performed. The new code will only allow signed up
# users to sign up.
unless ($USER) {
print $IN->redirect(Links::redirect_login_url('subscribe'));
return;
}
my $page;
if ($CFG->{newsletter_global_subscribe}) {
$page = 'newsletter_global';
if ($action eq 'subscribe') {
$res = $PLG->dispatch('newsletter_global_sub', \&global_subscribe);
}
elsif ($action eq 'unsubscribe') {
$res = $PLG->dispatch('newsletter_global_unsub', \&global_unsubscribe);
}
}
elsif ($action eq 'list') {
$page = 'newsletter_list';
}
elsif ($action eq 'unsubscribe') {
$res = $PLG->dispatch('newsletter_unsubscribe', \&unsubscribe);
$page = $IN->param('page') || 'newsletter';
}
elsif ($action eq 'subscribe') {
$res = $PLG->dispatch('newsletter_subscribe', \&subscribe);
$page = 'newsletter';
}
elsif ($action eq 'update') {
$res = $PLG->dispatch('newsletter_update', \&update_subscription);
$page = 'newsletter_browse';
}
else {
$page = 'newsletter_browse';
}
$res->{main_title_loop} ||= $mtl;
print $IN->header();
print Links::SiteHTML::display($page, $res);
}
sub custom_list_subscribe {
# ---------------------------------------------------
# Subscribe to a custom list
#
my $list = $IN->param('list');
my $email = $IN->param('email');
my $mli = $DB->table('MailingListIndex');
my $ml = $DB->table('MailingList');
unless ($mli->count({ Name => $list })) {
return { error => Links::language('SUBSCRIBE_INVALIDLIST', $list) };
}
my $id = $mli->select('ID', { Name => $list })->fetchrow;
if ($ml->count({ Email => $email, ID => $id })) {
return { error => Links::language('SUBSCRIBE_ALREADYSUB') };
}
$ml->insert({ Email => $email, ID => $id });
return { message => Links::language('SUBSCRIBE_SUCCESS') };
}
sub custom_list_unsubscribe {
# ---------------------------------------------------
# Unsubscribe from a custom list
#
my $list = $IN->param('list');
my $email = $IN->param('email');
my $mli = $DB->table('MailingListIndex');
my $ml = $DB->table('MailingList');
unless ($mli->count({ Name => $list })) {
return { error => Links::language('SUBSCRIBE_INVALIDLIST', $list) };
}
my $id = $mli->select('ID', { Name => $list })->fetchrow;
unless ($ml->count({ Email => $email, ID => $id })) {
return { error => Links::language('SUBSCRIBE_NOTSUB') };
}
$ml->delete({ Email => $email, ID => $id });
return { message => Links::language('SUBSCRIBE_UNSUBSUCCESS') };
}
sub global_subscribe {
# ---------------------------------------------------
# Global subscribe to the newsletter. If the admin option is enabled, then this
# will behave like the newsletter did in 2.x, where there is only one global
# newsletter. The only difference is that only registered users can subscribe.
#
my $ns = $DB->table('NewsletterSubscription');
if ($ns->count({ UserID => $USER->{Username}, CategoryID => 0 })) {
return { error => Links::language('NEWSLETTERERR_ALREADYSUB') };
}
_subscribe(0);
return { message => Links::language('NEWSLETTER_SUBSCRIBED') };
}
sub global_unsubscribe {
# ---------------------------------------------------
# Unsubscribe from the newsletter.
#
my $ns = $DB->table('NewsletterSubscription');
if ($ns->count({ UserID => $USER->{Username}, CategoryID => 0 })) {
_unsubscribe(0);
return { message => Links::language('NEWSLETTER_UNSUBSCRIBED') };
}
return { error => Links::language('NEWSLETTERERR_NOTSUB') };
}
sub global_subscribe_info {
# ---------------------------------------------------
# Returns information about the user's newsletter subscription.
#
return { subscribed => $DB->table('NewsletterSubscription')->count({ UserID => $USER->{Username}, CategoryID => 0 }) };
}
sub list_subscribed {
# ---------------------------------------------------
# Returns a list of categories they are subscribed to.
#
my $ns = $DB->table('NewsletterSubscription');
my $nsc = $DB->table('NewsletterSubscription', 'Category');
if ($ns->count({ UserID => $USER->{Username}, CategoryID => 0 })) {
return { subscribed => [_root()] };
}
$nsc->select_options("ORDER BY Full_Name");
my $list = $nsc->select({ UserID => $USER->{Username} })->fetchall_hashref;
return { subscribed => $list };
}
sub unsubscribe {
# ---------------------------------------------------
# Unsubscribe from one or more categories.
#
my @unsub = $IN->param('ID');
@unsub = @_ unless @unsub;
return { error => Links::language('NEWSLETTERERR_NOCATSUB') } unless @unsub;
_unsubscribe(@unsub);
return { message => Links::language('NEWSLETTER_CATUNSUB') };
}
sub subscribe {
# ---------------------------------------------------
# Subscribe to one or more categories.
#
my @sub = $IN->param('ID');
@sub = @_ unless @sub;
return { error => Links::language('NEWSLETTER_NOCATUNSUB') } unless @sub;
_subscribe(@sub);
return { message => Links::language('NEWSLETTER_CATSUB') };
}
sub update_subscription {
# ---------------------------------------------------
# Update a User's category subscriptions from their browse selection.
#
# These should be the original subscribe states of the categories. S<ID> are the
# categories which they wish to be subscribed to.
my @presub = $IN->param('subscribed');
my @preunsub = $IN->param('unsubscribed');
my (@sub, @unsub);
for (@presub) {
next if $_ =~ /\D/;
push @unsub, $_ unless defined $IN->param("S$_");
}
_unsubscribe(@unsub);
for (@preunsub) {
next if $_ =~ /\D/;
push @sub, $_ if defined $IN->param("S$_");
}
_subscribe(@sub);
return { message => Links::language('NEWSLETTER_CATUPDATED') };
}
sub browse {
# ---------------------------------------------------
# Browse the categories.
#
my $root = $IN->param('root') || 0;
my $cat = $DB->table('Category');
my $ns = $DB->table('NewsletterSubscription');
if ($root != 0 and not $cat->count({ ID => $root })) {
$root = 0;
}
my $root_cat;
if ($root == 0) {
$root_cat = _root();
$root_cat->{CatDepth} = -1;
}
else {
$root_cat = $cat->select({ ID => $root })->fetchrow_hashref or return { error => $GT::SQL::error };
}
my $tree = $cat->tree;
my $cats;
# When root = 0, max_depth is kind of weird because there isn't actually a Category with ID = 0.
# Because of this GT::SQL::Tree doesn't handle the case where max_depth = 1 and root = 0, so
# we'll handle it ourselves.
if ($root == 0 and $CFG->{newsletter_max_depth} == 1) {
$cat->select_options("ORDER BY Full_Name");
$cats = $cat->select({ FatherID => 0 })->fetchall_hashref;
}
else {
$cats = $tree->children(id => $root, max_depth => ($root == 0 ? $CFG->{newsletter_max_depth} - 1 : $CFG->{newsletter_max_depth}), sort_col => 'Full_Name');
}
# Insert the root category as the first element
splice @$cats, 0, 0, $root_cat;
my @parents;
my %catids;
for (0 .. $#$cats) {
my $c = $cats->[$_];
# ID to $cats index mapping
$catids{$c->{ID}}->{index} = $_;
# List of children (only ones which are shown in the trimmed tree)
$catids{$c->{ID}}->{children} = [];
# Fix CatDepth to be relative to $root
if ($_) {
$c->{CatDepth} -= $root_cat->{CatDepth};
}
# Keep track of categories which could have sub categories (that are past max_depth)
if ($CFG->{newsletter_max_depth} > 0 and $c->{CatDepth} == $CFG->{newsletter_max_depth}) {
$catids{$c->{ID}}->{check_child}++;
}
else {
$c->{HasMoreChildren} = 0;
}
$c->{Subscribed} = 0;
# Find all the children
while (@parents and @parents > $c->{CatDepth}) {
my $p = pop @parents;
for (@parents) {
push @{$catids{$_}->{children}}, $p;
}
}
push @parents, $c->{ID};
}
while (@parents) {
my $p = pop @parents;
for (@parents) {
push @{$catids{$_}->{children}}, $p;
}
}
$cats->[0]->{CatDepth} = 0;
if (%catids) {
for (keys %catids) {
$cats->[$catids{$_}->{index}]->{Children} = $catids{$_}->{children};
}
# Figure out which categories the user has subscribed to
my @subscribed = $ns->select('CategoryID', { UserID => $USER->{Username}, CategoryID => [keys %catids] })->fetchall_list;
for (@subscribed) {
$cats->[$catids{$_}->{index}]->{Subscribed}++;
}
# Check to see which categories have sub categories
my @check = grep $catids{$_}->{check_child}, keys %catids;
if (@check) {
my $subcats = $tree->child_ids(id => \@check);
for (keys %$subcats) {
$cats->[$catids{$_}->{index}]->{HasMoreChildren} = @{$subcats->{$_}};
}
}
}
my %previous = (PPID => '');
my $parent_subscribed;
if ($root != 0) {
my @parents = @{$tree->parent_ids(id => $root)};
splice(@parents, 0, 0, 0);
$parent_subscribed = $ns->count({ UserID => $USER->{Username}, CategoryID => \@parents });
my $parent;
if (@parents < $CFG->{newsletter_max_depth}) {
$parent = $parents[0];
}
else {
$parent = $parents[-$CFG->{newsletter_max_depth}];
}
# Get the previous parent's info
if ($parent == 0) {
$parent = _root();
}
else {
$parent = $cat->select({ ID => $parent })->fetchrow_hashref;
}
%previous = map { "PP" . $_ => $parent->{$_} } keys %$parent;
}
return { %previous, category => $cats, parent_subscribed => $parent_subscribed };
}
sub admin_browse {
# ---------------------------------------------------
# Browse the categories (admin side).
#
my $root = $IN->param('root') || 0;
my $cat = $DB->table('Category');
my $ns = $DB->table('NewsletterSubscription');
if ($root != 0 and not $cat->count({ ID => $root })) {
$root = 0;
}
my $root_cat;
if ($root == 0) {
$root_cat = _root();
$root_cat->{CatDepth} = -1;
}
else {
$root_cat = $cat->select({ ID => $root })->fetchrow_hashref or return { error => $GT::SQL::error };
}
my $tree = $cat->tree;
my $cats;
# root (0) isn't a 'real' category in the tree, so we have to select it ourselves
if ($root == 0 and $CFG->{newsletter_max_depth} == 1) {
$cat->select_options("ORDER BY Full_Name");
$cats = $cat->select({ FatherID => 0 })->fetchall_hashref;
}
else {
$cats = $tree->children(id => $root, max_depth => ($root == 0 ? $CFG->{newsletter_max_depth} - 1 : $CFG->{newsletter_max_depth}), sort_col => 'Full_Name');
}
# Insert the root category as the first element of the list of categories
splice @$cats, 0, 0, $root_cat;
my %catids;
for (0 .. $#$cats) {
my $c = $cats->[$_];
# ID to $cats index mapping
$catids{$c->{ID}}->{index} = $_;
# List of children (only ones which are shown in the trimmed tree)
$catids{$c->{ID}}->{children} = [];
# Fix CatDepth to be relative to $root
if ($_) {
$c->{CatDepth} -= $root_cat->{CatDepth};
}
# Keep track of categories which could have sub categories (that are past max_depth)
if ($CFG->{newsletter_max_depth} > 0 and $c->{CatDepth} == $CFG->{newsletter_max_depth}) {
$catids{$c->{ID}}->{check_child}++;
}
else {
$c->{HasMoreChildren} = 0;
}
$c->{DirectSubscribers} = 0;
}
$cats->[0]->{CatDepth} = 0;
# Get a list of the root's parents (this is used twice below)
my @root_parents = $root == 0 ? () : (0, @{$tree->parent_ids(id => $root)});
if (%catids) {
# Calculate the number of direct subscribers for each category
my %subscribers;
$ns->select_options("GROUP BY CategoryID");
my $sth = $ns->select('CategoryID', 'COUNT(*)', { CategoryID => [@root_parents, keys %catids] });
while (my ($catid, $count) = $sth->fetchrow_array) {
if (exists $catids{$catid}) {
$cats->[$catids{$catid}->{index}]->{DirectSubscribers} = $count;
}
# Save the counts to calculate the total subscribers
$subscribers{$catid} = $count;
}
# Calculate the number of subscribers for each category (if a newsletter was
# sent to this category, it would go to this many people)
my $parents = $tree->parent_ids(id => [keys %catids]);
for my $catid (keys %$parents) {
for (@{$parents->{$catid}}, $catid) {
$cats->[$catids{$catid}->{index}]->{Subscribers} += $subscribers{$_};
}
$cats->[$catids{$catid}->{index}]->{Subscribers} += $subscribers{0} if $catid;
}
# Check to see which categories have sub categories
my @check = grep $catids{$_}->{check_child}, keys %catids;
if (@check) {
my $subcats = $tree->child_ids(id => \@check);
for (keys %$subcats) {
$cats->[$catids{$_}->{index}]->{HasMoreChildren} = @{$subcats->{$_}};
}
}
}
my %previous = (PPID => '');
if ($root != 0) {
my $parent;
if (@root_parents < $CFG->{newsletter_max_depth}) {
$parent = $root_parents[0];
}
else {
$parent = $root_parents[-$CFG->{newsletter_max_depth}];
}
# Get the previous parent's info
if ($parent == 0) {
$parent = _root();
}
else {
$parent = $cat->select({ ID => $parent })->fetchrow_hashref;
}
%previous = map { "PP" . $_ => $parent->{$_} } keys %$parent;
}
return { %previous, category => $cats };
}
sub subscriber_info {
# ---------------------------------------------------
# Returns information about the subscribers of a category.
#
my $catid = $IN->param('ID');
my $direct = $IN->param('direct');
my $cat = $DB->table('Category');
my $nsu = $DB->table('NewsletterSubscription', 'Users');
if (not defined $catid or not ($catid == 0 or $cat->count({ ID => $catid }))) {
return { error => 'Invalid ID' };
}
my $tree = $cat->tree;
my @parents = $direct || $catid == 0 ? ($catid) : (0, @{$tree->parent_ids(id => $catid)}, $catid);
$nsu->select_options("ORDER BY Username");
my $subscribers = $nsu->select({ CategoryID => \@parents })->fetchall_hashref;
return { subscribers => $subscribers };
}
sub subscription_info {
# ---------------------------------------------------
# Returns subscription information about a category.
# 0 = not subscribed
# 1 = indirectly subscribed (parent is subscribed)
# 2 = directly subscribed
#
my $catid = $IN->param('ID') || shift;
my $ns = $DB->table('NewsletterSubscription');
my $tree = $DB->table('Category')->tree;
if ($ns->count({ UserID => $USER->{Username}, CategoryID => $catid })) {
return { SubscriptionStatus => 2 };
}
if ($catid == 0) {
return { SubscriptionStatus => 0 };
}
my @parents = (0, @{$tree->parent_ids(id => $catid)});
my @pids = $ns->select('CategoryID', { UserID => $USER->{Username}, CategoryID => \@parents })->fetchall_list;
if (@pids) {
return { SubscriptionStatus => 1 };
}
return { SubscriptionStatus => 0 };
}
sub _root {
# ---------------------------------------------------
# Since there is no real root category, return what a select from the Category
# table would return.
#
my $ns = $DB->table('NewsletterSubscription');
return {
ID => 0,
Name => Links::language('NEWSLETTER_ROOTCAT'),
CatDepth => 0,
Full_Name => Links::language('NEWSLETTER_ROOTCAT'),
Description => '',
Subscribed => $USER->{Username} ? $ns->count({ UserID => $USER->{Username}, CategoryID => 0 }) : 0,
};
}
sub _subscribe {
# ---------------------------------------------------
# Subscribe to the categories passed in.
#
my @sub = @_;
return 0 unless @sub;
my $cat = $DB->table('Category');
my $ns = $DB->table('NewsletterSubscription');
my $tree = $cat->tree;
# Already subscribed to root category
if ($ns->count({ UserID => $USER->{Username}, CategoryID => 0 })) {
return 0;
}
@sub = sort { $a <=> $b } @sub;
if ($sub[0] == 0) {
@sub = (0);
}
else {
# Filter out the invalid category ID's
my @s = $cat->select('ID', { ID => \@sub })->fetchall_list;
# Filter out categories which are already subscribed to by being a subcat
@sub = ();
my $parents = $tree->parent_ids(id => \@s);
for (@s) {
unless (@{$parents->{$_}} and $ns->count({ UserID => $USER->{Username}, CategoryID => $parents->{$_} })) {
push @sub, $_;
}
}
}
return 0 unless @sub;
# Subscribing to the root, subscribes you to all, so remove any existing subscriptions.
$ns->delete({ UserID => $USER->{Username} }) if $sub[0] == 0;
$ns->insert_multiple([qw/UserID CategoryID/], map { [$USER->{Username}, $_] } @sub);
# Remove any subscribed subcats of the ones we just added
if ($sub[0] != 0) {
my $c = $tree->child_ids(id => \@sub);
my @subcats = map { @{$c->{$_}} } keys %$c;
if (@subcats) {
$ns->delete({ UserID => $USER->{Username}, CategoryID => \@subcats });
}
}
# FIXME need to take into account how many were deleted
return scalar @sub;
}
sub _unsubscribe {
# ---------------------------------------------------
# Unsubscribe from categories passed in. Returns the number of categories
# unsubscribed from.
#
my @unsub = @_;
return 0 unless @unsub;
return $DB->table('NewsletterSubscription')->delete({ UserID => $USER->{Username}, CategoryID => \@unsub });
}
1;

View File

@ -0,0 +1,284 @@
# ==================================================================
# 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: Parallel.pm,v 1.8 2005/03/05 01:29:09 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::Parallel;
# ==================================================================
# A way to get parallel work for ceartain tasks (not thread based).
#
use strict;
sub new {
#------------------------------------------------------------
# creats a new class, be sure to take a look at how it can
# be configured
#
my $class = shift;
my %p;
ref $_[0] ? (%p = %{$_[0]} ) : (%p = @_);
my $self = {};
bless $self, $class;
$self->{max_workunit} = defined ( $p{max_workunit} ) ? $p{max_workunit} : 10;
$self->{min_workunit} = defined ( $p{min_workunit} ) ? $p{min_workunit} : 3;
$self->{max_children} = defined ( $p{max_children} ) ? $p{max_children} : 3;
$self->{child_path} = defined ( $p{child_path} ) ? $p{child_path} : "./child.pl";
$self->{path_to_perl} = defined ( $p{path_to_perl} ) ? $p{path_to_perl} : "/usr/local/bin/perl";
$self->{max_children} = defined ( $p{max_children} ) ? $p{max_children} : 3;
$self->{spawn_delay} = defined ( $p{spawn_delay} ) ? $p{spawn_delay} : 2;
$self->{to_check} = defined ( $p{to_check} ) ? $p{to_check} : [];
$self->{on_response} = defined ( $p{on_response} ) ? $p{on_response} : sub { };
# for statistics
$self->{start_time} = 0;
$self->{end_time} = 0;
$self->{threads_spawned}= 0;
$self->{threads_stats} = {};
return $self;
}
sub wait {
#------------------------------------------------------------
# the main loop that waits until the subset is checked.
#
my $self = shift;
my $max_children = $self->{max_children};
my @active_units = ();
$self->{start_time} = time;
my $temp = 0;
# while there's stuff to check
while ( ( @{$self->{to_check}} ) or ( @active_units) ) {
# create work units
my $spawned = 0;
while ( ( ($#active_units+1) < $max_children ) and ( @{$self->{to_check}} ) ) {
# if we've already spawned a child, wait one so we don't
# spike the load
sleep $self->{spawn_delay} if $spawned;
$spawned++;
push @active_units, $self->new_work_unit ();
};
# wait for any connections, blocking call.
my $rin = fhbits ( \@active_units );
select ( $rin, undef, undef, undef );
# find out who is has input
my ( $i, $wild_protect );
$i = 0; $wild_protect = 0;
while ( $i <= $#active_units ) {
# if a unit requires attention, get input or kill it
if ( $active_units[$i]->has_input () ) {
$wild_protect++;
my ( $id, $code, $message ) = $active_units[$i]->get_input ();
if ( defined $id ) {
&{$self->{on_response}}( $id, $code, $message );
} else {
my ( $unit_id, $start_time, $number_checked, $time_taken ) = $active_units[$i]->get_stats();
${$self->{thread_stats}}{$unit_id} = [$start_time, $number_checked, $time_taken];
$active_units[$i]->end_unit();
# in case the child aborted abnormally, push the remaining
# urls to be checked onto the stack
push @{$self->{to_check}}, @{$active_units[$i]->{to_check}};
splice ( @active_units, $i, 1 );
next;
};
};
$i++;
};
# protect against wild looping
$wild_protect || die "Error in verifier, looping wildly";
};
$self->{end_time} = time;
}
sub get_stats {
#------------------------------------------------------------
# Return stats for the thread information.
#
my $self = shift;
return [ $self->{threads_spawned}, $self->{end_time} - $self->{start_time}, $self->{thread_stats} ];
}
sub new_work_unit {
#------------------------------------------------------------
# allocates a new work unit for the chilren
# there are some optimization routines that should at some
# point be implemented (for better allocation of
# work units
#
my $self = shift;
my $num_units = $#{$self->{to_check}}+1;
my $max_children= $self->{max_children};
my $unit_size = int ( $num_units / ( $max_children + 1 ) );
($unit_size > $self->{max_workunit}) and $unit_size = $self->{max_workunit};
($unit_size < $self->{min_workunit}) and $unit_size = $self->{min_workunit};
($unit_size > $num_units) and $unit_size = $num_units;
my @to_check = @{$self->{to_check}}[0..$unit_size-1];
splice ( @{$self->{to_check}}, 0, $unit_size );
$self->{threads_spawned}++;
return Links::Parallel::WorkUnit->new ( $self->{path_to_perl}, $self->{child_path}, $self->{child_args}, \@to_check );
}
sub fhbits {
#------------------------------------------------------------
# to set the fhandle bits for the impending select call
#
my ($work_units, $bits) = @_;
defined $bits or ($bits = '');
foreach (@$work_units) {
vec($bits,$_->fno(),1) = 1;
};
return $bits;
}
#####################################################
package Links::Parallel::WorkUnit;
use FileHandle;
use strict;
my $clwork_units = 0;
sub new {
#------------------------------------------------------------
# creates a new work unit, starts up the child process
# and encapsulats all the required data...,
#
my ($class, $perlpath, $child, $cmdline, $to_check, $verbosity) = @_;
my $self = {};
$self->{verbosity} = $verbosity || 1;
($self->{istream}, $self->{pid}) = new_handle ( $perlpath, $child, $cmdline, $to_check );
@{$self->{to_check}} = @$to_check;
@{$self->{checked}} = ();
$self->{unitid} = $clwork_units++;
$self->{start_time} = time;
$self->{number_checked} = 0;
bless $self, $class;
return $self;
}
sub new_handle {
#------------------------------------------------------------
# the function that actually creates a new child process
#
my ($perlpath, $child, $cmdline, $to_check, $verbosity) = @_;
$verbosity ||= 1;
my $newfh = new FileHandle;
my $pid = 0;
$cmdline ||= '';
$, = "|";
if ($verbosity) {
print "Launching new child ... ";
}
if (-e $child) {
$pid = $newfh->open ( "$perlpath $child $cmdline @$to_check |" );
if ((!$pid)or($?)) { die "Error launching child '$perlpath $child $cmdline'. Status: $?"; }
} else {
die "Child ($child) must exist";
}
print "ok ($pid)\n";
return ( $newfh, $pid );
}
sub fno {
#------------------------------------------------------------
# returns the file handle, useful when using the
# "select" call
#
my $self = shift;
return fileno ( $self->{istream} );
}
sub has_input {
#------------------------------------------------------------
# returns whether or not this workunit has anything to
# report to the parent
#
my $self = shift;
my $rin = '';
vec ( $rin, $self->fno(), 1 ) = 1;
my $s = select ( $rin, undef, undef, 0 );
return $s;
}
sub get_input {
#------------------------------------------------------------
# process the local input.
# this is only here because we want to make sure that
# the work unit keeps track of it's own work pool
# this frees the task administrator to do it's real
# work and helps with crash recovery
#
my $self = shift;
my $fh = $self->{istream};
$fh || die "not defined!";
my $str = <$fh>;
if ( defined ( $str ) ) {
chop $str;
$str =~ /\s*([0-9]+)\t([-0-9]*)\t(.*)/;
push @{$self->{checked}}, $1;
splice @{$self->{to_check}}, 0, 1;
$self->{number_checked}++;
return ( $1, $2, $3 );
} else {
$self->end_unit ();
return;
};
}
sub get_stats {
#------------------------------------------------------------
# Display statistic information.
#
my $self = shift;
return ( $self->{unitid}, $self->{start_time}, $self->{number_checked}, time-$self->{start_time} );
}
sub end_unit {
#------------------------------------------------------------
# prepares the WorkUnit for deallocation. Note how
# there is a force -9 kill, without that, perl will wait
# until the child finishes on it's own, which might be
# soon, later or in a 100 years
#
my $self = shift;
my $fh = $self->{istream};
kill 9, ( $self->{pid} );
$self->{istream}->close ();
}
sub DESTROY {
#------------------------------------------------------------
# deallocs the object
# we want perl to force kill the child so we can ensure we exit
# quickly
my $self = shift;
$self->end_unit;
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
# ==================================================================
# 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: AuthorizeDotNet.pm,v 1.3 2005/03/05 01:29:09 brewt Exp $
#
# Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# Glue between Gossamer Links and Authorize.Net payment interface
package Links::Payment::Direct::AuthorizeDotNet;
use strict;
# Make sure the payment module is available
use GT::Payment::Direct::AuthorizeDotNet;
use Links qw/$IN $CFG $DB/;
use vars qw/%INVALID %EMPTY/;
sub required {
# -----------------------------------------------------------------------------
# Returns a list of required field names. Each field name will be looked for
# in the language file, prefixed with 'PAYMENT_DIRECT_AuthorizeDotNet_', for
# the title of the field, and 'PAYMENT_DIRECT_DESC_AuthorizeDotNet_' for a
# description of the field's contents.
# Note that these are just required SETUP fields, so things like credit card
# number, billing name, etc. are NOT included.
return
account_username => { type => 'TEXT', valid => '^\w+$' }, # FIXME - I have no idea what this can be
account_key => { type => 'TEXT', valid => '^\w+$' };
}
sub optional {
# -----------------------------------------------------------------------------
my @currencies;
for (sort {
$a eq 'USD' ? -1 : $b eq 'USD' ? 1 : $a eq 'CAD' ? -1 : $b eq 'CAD' ? 1 :
$GT::Payment::Direct::AuthorizeDotNet::CURRENCY{$a} cmp
$GT::Payment::Direct::AuthorizeDotNet::CURRENCY{$b}
} keys %GT::Payment::Direct::AuthorizeDotNet::CURRENCY) {
push @currencies, $_ => $GT::Payment::Direct::AuthorizeDotNet::CURRENCY{$_};
}
return
currency => {
type => 'SELECT',
options => \@currencies
},
account_password => { type => 'TEXT', size => 40, valid => '.' }, # An optionally-required account password
confirmation_merchant => { type => 'TEXT', size => 40, valid => '.@.' }, # A merchant confirmation e-mail address
confirmation_confirm => { type => 'YESNO' }, # Whether or not to send a customer confirmation e-mail.
test_mode => { type => 'YESNO' }
}
sub payment_info {
# -----------------------------------------------------------------------------
# Returns a hash of various parameters used to figure out how to display the
# payment form for this payment method.
return {
no_cc_brand => 1,
fields => [
grep ! /^(?:account|capture|currency|test)/, keys %GT::Payment::Direct::AuthorizeDotNet::VALID
],
billing_phone_required => 1
}
}
sub verify {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns 1 on success, or an array ref of invalid keys
# on failure.
_collect_data();
if (keys %INVALID or keys %EMPTY) {
my ($i, %order);
for (@{$GT::Payment::Direct::AuthorizeDotNet::REQUIRED{AUTHORIZE}}) { $order{$_} = $i++ }
return [ # Error
[sort { ($order{$a} || 0x7fff_ffff) <=> ($order{$b} || 0x7fff_ffff) } keys %INVALID],
[sort { ($order{$a} || 0x7fff_ffff) <=> ($order{$b} || 0x7fff_ffff) } keys %EMPTY]
];
}
else {
return 1; # Success
}
}
sub complete {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns (1, $message) on success, (0, $reason) on
# declined, or (-1, $errormsg) on error.
my $pay = _collect_data() or return;
# Set the admin-specified fields
while (my ($k, $v) = each %{$CFG->{payment}->{direct}->{used}->{AuthorizeDotNet}}) {
$pay->$k($v) or return (-1, "Payment configuration error (Invalid $k)");
}
$pay->check('sale') or return (-1, $pay->error);
my $ret = $pay->sale;
if (not defined $ret) { # An error occured in the module
return (-1, $pay->error);
}
else { # The request at least got through to Authorize.Net
my $response = $pay->response;
if ($ret == 1) { # Approved!
my @receipt = @{$response->{receipt}};
my $receipt = "Transaction approved\n\n";
while (@receipt) {
my ($k, $v) = splice @receipt, 0, 2;
$receipt .= "$k: $v\n";
}
return (1, $response->{reason_text}, $receipt);
}
elsif ($ret == 0) { # Declined
return (0, $response->{reason_text});
}
else { # An error was generated by Authorize.Net
return (-1, $response->{reason_text});
}
}
}
sub _collect_data {
# -----------------------------------------------------------------------------
# Collect data from the payment data saved in the admin, and any valid columns
# in $IN. Anything from $IN is checked for validity, and $INVALID{column} is
# set if invalid.
%INVALID = %EMPTY = ();
return unless $CFG->{payment}->{direct}->{used}->{AuthorizeDotNet};
my %data = %{$CFG->{payment}->{direct}->{used}->{AuthorizeDotNet}};
my $pay = GT::Payment::Direct::AuthorizeDotNet->new();
my %required = map { $_ => 1 } @{$GT::Payment::Direct::AuthorizeDotNet::REQUIRED{AUTHORIZE}};
for my $field (keys %GT::Payment::Direct::AuthorizeDotNet::VALID) {
# The account_*, capture_*, currency_*, etc. fields should not be user-settable.
next if exists $data{$field} or $field =~ /^(?:account|capture|currency|test)/;
if (my $value = $IN->param($field)) {
if ($pay->$field($value)) {
$data{$field} = $value;
}
else {
$INVALID{$field}++;
$data{$field} = undef;
}
}
elsif ($required{$field}) {
$EMPTY{$field}++;
$data{$field} = undef;
}
}
$pay->billing_ip($ENV{REMOTE_ADDR}) if $ENV{REMOTE_ADDR} and $ENV{REMOTE_ADDR} ne '127.0.0.1';
return if keys %INVALID or keys %EMPTY;
return $pay;
}
1;

View File

@ -0,0 +1,152 @@
# ==================================================================
# 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: Moneris.pm,v 1.2 2005/03/05 01:29:09 brewt Exp $
#
# Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# Glue between Gossamer Links and Moneris payment interface
package Links::Payment::Direct::Moneris;
use strict;
# Make sure the payment module is available
use GT::Payment::Direct::Moneris 1.007; # CVS Versions < 1.7 were for the old, defunct Moneris payment system
use Links qw/$IN $CFG $DB/;
use vars qw/%INVALID %EMPTY/;
my @FIELDS = (
keys %GT::Payment::Direct::Moneris::NAME_MAP,
qw/ credit_card_number credit_card_expiry_month credit_card_expiry_year
billing_country billing_email charge_total/
);
sub required {
# -----------------------------------------------------------------------------
# Returns a list of required field names. Each field name will be looked for
# in the language file, prefixed with 'PAYMENT_DIRECT_Moneris_', for the title
# of the field, and 'PAYMENT_DIRECT_DESC_Moneris_' for a description of the
# field's contents.
# Note that these are just required SETUP fields, so things like credit card
# number, billing name, etc. are NOT included.
return
account_token => { type => 'TEXT', valid => '^\w+$' },
account_token2 => { type => 'TEXT', valid => '^\w+$' };
}
sub optional {
return
test_mode => { type => 'YESNO' }
}
sub payment_info {
# -----------------------------------------------------------------------------
# Returns a hash of various parameters used to figure out how to display the
# payment form for this payment method.
return {
fields => [
grep ! /^(?:account|capture|currency|test)/, @FIELDS
],
no_cc_brand => 1
};
}
sub verify {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns 1 on success, or an array ref of invalid and
# empty keys array references (i.e. [\@invalid, \@empty]) on failure.
_collect_data();
if (keys %INVALID or keys %EMPTY) {
my ($i, %order);
for (@{$GT::Payment::Direct::Moneris::REQUIRED{AUTHORIZE}}) { $order{$_} = $i++ }
return [ # Error
[sort { ($order{$a} || 0x7fff_ffff) <=> ($order{$b} || 0x7fff_ffff) } keys %INVALID],
[sort { ($order{$a} || 0x7fff_ffff) <=> ($order{$b} || 0x7fff_ffff) } keys %EMPTY]
];
}
else {
return 1; # Success
}
}
sub complete {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns (1, $message) on success, (0, $reason) on
# declined, or (-1, $errormsg) on error.
my $pay = _collect_data() or return;
# Set the admin-specified fields
while (my ($k, $v) = each %{$CFG->{payment}->{direct}->{used}->{Moneris}}) {
$pay->$k($v) or return (-1, "Payment configuration error (Invalid $k)");
}
$pay->check('sale') or return (-1, $pay->error);
my $ret = $pay->sale;
if (not defined $ret) { # An error occured in the module
return (-1, $pay->error);
}
else { # The request at least got through to Moneris
if ($ret == 1) { # Approved!
my $resp_text;
my @receipt = $pay->receipt();
my $receipt = "Transaction approved\n\n";
while (@receipt) {
my ($k, $v) = splice @receipt, 0, 2;
$receipt .= "$k: $v\n";
$resp_text = $v if $k eq 'Status';
}
return (1, $resp_text, $receipt);
}
elsif ($ret == 0) { # Declined
return (0, $pay->error);
}
else { # An error was generated by Moneris
return (-1, $pay->error);
}
}
}
sub _collect_data {
# -----------------------------------------------------------------------------
# Collect data from the payment data saved in the admin, and any valid columns
# in $IN. Anything from $IN is checked for validity, and $INVALID{column} is
# set if invalid.
%INVALID = %EMPTY = ();
return unless $CFG->{payment}->{direct}->{used}->{Moneris};
my %data = %{$CFG->{payment}->{direct}->{used}->{Moneris}};
return unless keys %data;
my $pay = GT::Payment::Direct::Moneris->new(debug_level => $CFG->{debug});
my %required = map { $_ => 1 } @{$GT::Payment::Direct::Moneris::REQUIRED{AUTHORIZE}};
for my $field (@FIELDS) {
# The account_*, capture_*, currency_*, etc. fields should not be user-settable.
next if exists $data{$field} or $field =~ /^(?:account|capture|currency|test)/;
if (my $value = $IN->param($field)) {
if ($pay->$field($value)) {
$data{$field} = $value;
}
else {
$INVALID{$field}++;
$data{$field} = undef;
}
}
elsif ($required{$field}) {
$EMPTY{$field}++;
$data{$field} = undef;
}
}
return if keys %INVALID or keys %EMPTY;
return $pay;
}
1;

View File

@ -0,0 +1,122 @@
# ==================================================================
# 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: 2CheckOut.pm,v 1.13 2006/08/22 23:07:53 brewt Exp $
#
# Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# Glue between Gossamer Links and 2CheckOut payment interface
package Links::Payment::Remote::2CheckOut;
use strict;
# Make sure the payment module is available
use GT::Payment::Remote::2CheckOut;
use Links qw/:objects/;
use Links::Payment qw/:status :log/;
use Links::SiteHTML;
use vars qw/%INVALID %EMPTY/;
sub required {
# -----------------------------------------------------------------------------
# Returns a list of required field names. Each field name will be looked for
# in the language file, prefixed with 'PAYMENT_REMOTE_2CheckOut_', for the
# title of the field, and 'PAYMENT_REMOTE_DESC_2CheckOut_' for a description of
# the field's contents.
# Note that these are just required SETUP fields, so things like credit card
# number, billing name, etc. are NOT included.
return
seller_id => { type => 'TEXT', valid => '^\d{1,10}$' },
secret_word => { type => 'TEXT', valid => '^(?!tango$).+$' };
}
sub optional {
# -----------------------------------------------------------------------------
return
demo => { type => 'YESNO' };
}
sub payment_info {
# -----------------------------------------------------------------------------
# Returns a hashref of payment hints
#
my @fields = qw/seller_id secret_word demo/;
my $ret = {
fields => \@fields
};
if (my $info = $CFG->{payment}->{remote}->{used}->{'2CheckOut'}) {
for (@fields) {
$ret->{$_} = $info->{$_};
}
}
return $ret;
}
sub verify {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns 1 on success, or an array ref of invalid keys
# on failure. For Remote payment methods, this has no real effect.
return 1;
}
sub postback {
# -----------------------------------------------------------------------------
my $pay = $DB->table('Payments');
my $log = $DB->table('PaymentLogs');
my $unique = $IN->param('cart_order_id');
my $payment = $pay->select({ payments_id => $unique })->fetchrow_hashref
or return; # Whatever it is, we didn't create it.
GT::Payment::Remote::2CheckOut::process(
param => $IN,
sellerid => $CFG->{payment}->{remote}->{used}->{'2CheckOut'}->{seller_id},
password => $CFG->{payment}->{remote}->{used}->{'2CheckOut'}->{secret_word},
demo => $CFG->{payment}->{remote}->{used}->{'2CheckOut'}->{demo},
on_valid => sub {
return unless $IN->param('total') >= $payment->{payments_amount};
return if $payment->{payments_status} == COMPLETED;
my $cond = GT::SQL::Condition->new();
$cond->add(paylogs_payments_id => '=' => $unique);
$cond->add(paylogs_type => '=' => LOG_ACCEPTED);
$cond->add(paylogs_text => LIKE => "%\n2CheckOut order number: " . $IN->param('order_number') . "%\n");
my $found = $log->count($cond);
return if $found;
$pay->update(
{ payments_status => COMPLETED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ACCEPTED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_APPROVED') => '2CheckOut') . "\n" .
"2CheckOut order number: " . $IN->param('order_number') . "\n" .
"Amount: $payment->{payments_amount}\n"
)
});
Links::Payment::process_payment($payment->{payments_linkid}, $payment->{payments_term});
}
);
print $IN->header;
print Links::SiteHTML::display('payment_success');
1;
}
1;

View File

@ -0,0 +1,70 @@
# ==================================================================
# 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: Manual.pm,v 1.3 2005/03/05 01:46:06 brewt Exp $
#
# Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# Glue between Gossamer Links and Manual payment interface
package Links::Payment::Remote::Manual;
use strict;
# Make sure the payment module is available
use Links qw/:objects/;
use Links::Payment qw/:status :log/;
use Links::SiteHTML;
use vars qw/%INVALID %EMPTY/;
sub required {
# -----------------------------------------------------------------------------
# No required parameters available
return;
}
sub optional {
# -----------------------------------------------------------------------------
# No optional parameters available.
return;
}
sub payment_info {
# -----------------------------------------------------------------------------
# Returns a hashref of payment hints
#
return;
}
sub insert_log {
# -----------------------------------------------------------------------------
#
my $unique = shift;
my $pay = $DB->table('Payments');
my $log = $DB->table('PaymentLogs');
my $payment = $pay->select({ payments_id => $unique })->fetchrow_hashref or return; # return if the payment doesn't exist.
return if $payment->{payments_status} == COMPLETED;
my $cond = GT::SQL::Condition->new(
paylogs_payments_id => '=' => $unique,
paylogs_type => '=' => LOG_ACCEPTED
);
my $found = $log->count($cond);
return if $found;
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_MANUAL,
paylogs_time => time,
paylogs_text => (
"This payment will be manually approved by admin.\n" .
"Amount: $payment->{payments_amount}\n"
)
});
return;
}
1;

View File

@ -0,0 +1,296 @@
# ==================================================================
# 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: PayPal.pm,v 1.16 2006/12/01 00:31:56 brewt Exp $
#
# Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# Glue between Gossamer Links and PayPal IPN payment interface
package Links::Payment::Remote::PayPal;
use strict;
# Make sure the payment module is available
use GT::Payment::Remote::PayPal;
use Links qw/:objects/;
use Links::Payment qw/:status :log/;
use Links::SiteHTML;
use vars qw/%INVALID %EMPTY/;
sub required {
# -----------------------------------------------------------------------------
# Returns a list of required field names. Each field name will be looked for
# in the language hash, prefixed with 'PAYMENT_REMOTE_PayPal_', for the title
# of the field, and 'PAYMENT_REMOTE_DESC_PayPal_' for a description of the
# field's contents.
# Note that these are just required SETUP fields, so things like credit card
# number, billing name, etc. are NOT included.
my @currencies;
for (qw/USD CAD AUD EUR GBP JPY NZD CHF HKD SGD SEK DKK PLN NOK HUF CZK/) {
push @currencies, $_ => Links::language('PAYMENT_CURRENCY_' . $_);
}
my @buttons;
for (qw/23 cc 02 03 01 9 5 6/) {
push @buttons, "x-click-but$_.gif" => qq|<img src="https://www.paypal.com/images/x-click-but$_.gif">|;
}
my $custom = qq|Custom image:<br><input type="text" name="button_custom" size="60"|;
if ($CFG->{payment}->{remote}->{used}->{PayPal} and $CFG->{payment}->{remote}->{used}->{PayPal}->{button_custom}) {
$custom .= qq| value="$CFG->{payment}->{remote}->{used}->{PayPal}->{button_custom}"|;
}
$custom .= '>';
push @buttons, "custom" => $custom;
return
business_email => { type => 'TEXT', valid => '.@[a-zA-Z0-9-]' },
currency => {
type => 'SELECT',
options => \@currencies
},
button => {
type => 'RADIO',
options => \@buttons,
custom => 1,
valid => '^https?://[a-zA-Z0-9-]' # Only applies to the custom value
}
}
sub optional {
# -----------------------------------------------------------------------------
return
image_url => { type => 'TEXT', size => 60, value => '^https?://[a-zA-Z0-9-]' },
notify_url => { type => 'TEXT', size => '60', value => '^https?://[a-zA-Z0-9-]' },
note => { type => 'TEXT', size => 30, value => '^.{1,30}$' },
color => {
type => 'SELECT',
options => [
white => Links::language('PAYMENT_REMOTE_PayPal_color_white'),
black => Links::language('PAYMENT_REMOTE_PayPal_color_black')
]
},
to_email => { type => 'TEXT', valid => '.@[a-zA-Z0-9-]' },
sandbox => { type => 'YESNO' };
}
sub payment_info {
# -----------------------------------------------------------------------------
# Returns a hash of payment hints
#
my @fields = qw/business_email to_email currency button button_custom image_url notify_url note color sandbox/;
my $ret = {
fields => \@fields
};
if (my $pp = $CFG->{payment}->{remote}->{used}->{PayPal}) {
for (@fields) {
$ret->{$_ eq 'image_url' ? 'pp_image_url' : $_} = $pp->{$_};
}
}
return $ret;
}
sub verify {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns 1 on success, or an array ref of invalid keys
# on failure.
return 1;
}
sub postback {
# -----------------------------------------------------------------------------
# Handle PayPal postback
my $unique = $IN->param('invoice');
my $pay = $DB->table('Payments');
my $log = $DB->table('PaymentLogs');
my $payment = $pay->get($unique) or return;
GT::Payment::Remote::PayPal::process(
param => $IN,
sandbox => $CFG->{payment}->{remote}->{used}->{PayPal}->{sandbox},
on_valid => sub {
# If taxes or shipping was added, then mc_gross may be greater than payments_amount.
if ($IN->param('mc_gross') < $payment->{payments_amount}) {
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ERROR,
paylogs_time => time,
paylogs_text => "Invalid payment (payment amount is less than original charge): " .
$IN->param('mc_gross') . " < " . $payment->{payments_amount}
});
return;
}
elsif ($IN->param('mc_currency') ne $CFG->{payment}->{remote}->{used}->{PayPal}->{currency}) {
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ERROR,
paylogs_time => time,
paylogs_text => "Invalid payment (different currency): " .
$IN->param('mc_currency') . " != " . $CFG->{payment}->{remote}->{used}->{PayPal}->{currency}
});
return;
}
return if $payment->{payments_status} == COMPLETED;
$pay->update(
{ payments_status => COMPLETED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ACCEPTED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_APPROVED') => 'PayPal') . "\n" .
"Transaction ID: " . $IN->param('txn_id') . "\n" .
"Amount: " . $IN->param('mc_currency') . " " . $IN->param('mc_gross') . " (Fee: "
. $IN->param('mc_currency') . " " . $IN->param('mc_fee') . ")\n" .
"Payer Email: " . $IN->param('payer_email') . "\n"
)
});
Links::Payment::process_payment($payment->{payments_linkid}, $payment->{payments_term});
},
on_pending => sub {
$pay->update({ payments_last => time }, { payments_id => $unique });
my $match = Links::language('PAYLOG_PayPal_' . $IN->param('pending_reason'));
my $str = $match ? Links::language('PAYLOG_PayPal_' . $IN->param('pending_reason')) : '';
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_INFO,
paylogs_time => time,
paylogs_text => (
"Transaction ID: " . $IN->param('txn_id') . "\n" .
"Pending: " . ($match ? $str : scalar $IN->param('pending_reason'))
)
});
},
on_refund => sub {
$pay->update({ payments_last => time }, { payments_id => $unique });
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_INFO,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_REFUND') => 'PayPal') . "\n" .
"Transaction ID: " . $IN->param('txn_id') . "\n"
)
});
},
on_failed => sub {
$pay->update(
{ payments_status => DECLINED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_DECLINED,
paylogs_time => time,
paylogs_text => "Transaction ID: " . $IN->param('txn_id')
});
},
on_denied => sub {
$pay->update(
{ payments_status => DECLINED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_DECLINED,
paylogs_time => time,
paylogs_text => "Transaction ID: " . $IN->param('txn_id')
});
},
duplicate => sub {
my $id = $IN->param('txn_id');
my $cond = GT::SQL::Condition->new();
$cond->add(paylogs_payments_id => '=' => $unique);
$cond->add(paylogs_type => '=' => LOG_ACCEPTED);
$cond->add(paylogs_text => LIKE => "%\nTransaction ID: $id\n%");
my $found = $log->count($cond);
return $found ? undef : 1; # True if everything checks out; undef if a duplicate was found
},
email => sub {
my $email = shift;
return lc $email eq lc $CFG->{payment}->{remote}->{used}->{PayPal}->{business_email}
},
on_error => sub {
my $errmsg = shift;
$pay->update(
{ payments_status => ERROR, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ERROR,
paylogs_time => time,
paylogs_text => $errmsg
});
},
on_recurring => sub {
if ($IN->param('mc_gross') < $payment->{payments_amount}) {
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ERROR,
paylogs_time => time,
paylogs_text => "Invalid payment (payment amount is less than original charge): " .
$IN->param('mc_gross') . " < " . $payment->{payments_amount}
});
return;
}
elsif ($IN->param('mc_currency') ne $CFG->{payment}->{remote}->{used}->{PayPal}->{currency}) {
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ERROR,
paylogs_time => time,
paylogs_text => "Invalid payment (different currency): " .
$IN->param('mc_currency') . " != " . $CFG->{payment}->{remote}->{used}->{PayPal}->{currency}
});
return;
}
$pay->update(
{ payments_status => COMPLETED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ACCEPTED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_APPROVED') => 'PayPal') . "\n" .
"Transaction ID: " . $IN->param('txn_id') . "\n" .
"Amount: " . $IN->param('mc_currency') . " " . $IN->param('mc_gross') . " (Fee: "
. $IN->param('mc_currency') . " " . $IN->param('mc_fee') . ")\n" .
"Payer Email: " . $IN->param('payer_email') . "\n" .
"Subscription ID: " . $IN->param('subscr_id') . "\n"
)
});
Links::Payment::process_payment($payment->{payments_linkid}, $payment->{payments_term}, 1);
}
);
# There is no way to distinguish between PayPal sending the user back, and
# PayPal posting the IPN, so we print a payment confirmation page.
print $IN->header;
print Links::SiteHTML::display('payment_success');
1;
}
1;

View File

@ -0,0 +1,207 @@
# ==================================================================
# 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: WorldPay.pm,v 1.13 2006/08/22 23:05:13 brewt Exp $
#
# Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# Glue between Links and WorldPay payment interface
package Links::Payment::Remote::WorldPay;
use strict;
# Make sure the payment module is available
use GT::Payment::Remote::WorldPay;
use Links qw/:objects/;
use Links::Payment qw/:status :log/;
use Links::SiteHTML;
use vars qw/%INVALID %EMPTY/;
sub required {
# -----------------------------------------------------------------------------
# Returns a list of required field names. Each field name will be looked for
# in the language file, prefixed with 'PAYMENT_REMOTE_WorldPay_', for the title
# of the field, and 'PAYMENT_REMOTE_DESC_WorldPay_' for a description of the
# field's contents.
# Note that these are just required SETUP fields, so things like credit card
# number, billing name, etc. are NOT included.
my @currencies;
for (qw/USD CAD EUR GBP AFA ALL DZD AON ARS AWG AUD BSD BHD BDT BBD BZD BMD BOB BAD BWP BRL BND BGL XOF BIF KHR
XAF CVE KYD CLP CNY COP KMF CRC HRK CUP CYP CZK DKK DJF XCD DOP TPE ECS EGP SVC EEK ETB FKP FJD XPF GMD GHC
GIP GTQ GNF GWP GYD HTG HNL HKD HUF ISK INR IDR IRR IQD ILS JMD JPY JOD KZT KES KRW KPW KWD KGS LAK LVL LBP
LSL LRD LYD LTL MOP MKD MGF MWK MYR MVR MTL MRO MUR MXN MNT MAD MZM MMK NAD NPR ANG NZD NIO NGN NOK OMR PKR
PAB PGK PYG PEN PHP PLN QAR ROL RUR RWF WST STD SAR SCR SLL SGD SKK SIT SBD SOS ZAR LKR SHP SDP SRG SZL SEK
CHF SYP TWD TJR TZS THB TOP TTD TND TRL UGX UAH AED UYU VUV VEB VND YER YUM ZRN ZMK ZWD/) {
push @currencies, $_ => Links::language('PAYMENT_CURRENCY_' . $_);
}
return
installation_id => { type => 'TEXT', valid => '^\d{1,16}$' },
callback_password => { type => 'TEXT' },
md5_password => { type => 'TEXT' },
currency => {
type => 'SELECT',
options => \@currencies
}
}
sub optional {
# -----------------------------------------------------------------------------
return
test_mode => { type => 'SELECT', options => [100 => 'Test mode: Always approved', 101 => 'Test mode: Always declined'] }
}
sub payment_info {
# -----------------------------------------------------------------------------
# Returns a hashref of payment hints
#
my @fields = qw/currency installation_id md5_password test_mode/;
my $ret = {
fields => \@fields
};
if (my $pp = $CFG->{payment}->{remote}->{used}->{WorldPay}) {
for (@fields) {
$ret->{$_ eq 'image_url' ? 'pp_image_url' : $_} = $pp->{$_};
}
}
return $ret;
}
sub verify {
# -----------------------------------------------------------------------------
# Checks that $IN, combined with the saved admin settings, makes up all of the
# required information. Returns 1 on success, or an array ref of invalid keys
# on failure. For Remote payment methods, this has no real effect.
return 1;
}
sub postback {
# -----------------------------------------------------------------------------
my $pay = $DB->table('Payments');
my $log = $DB->table('PaymentLogs');
my $unique = $IN->param('cartId');
my $payment = $pay->select({ payments_id => $unique })->fetchrow_hashref
or return; # Whatever it is, we didn't create it.
my $end = 1; # Returned after processing - if true, a blank page will be displayed,
# if false, a worldpay receipt page.
GT::Payment::Remote::WorldPay::process(
param => $IN,
password => $CFG->{payment}->{remote}->{used}->{WorldPay}->{callback_password},
test_mode => $CFG->{payment}->{remote}->{used}->{WorldPay}->{test_mode},
on_valid => sub {
# A one-time payment (or the initial payment, in the case of recurring payments)
return unless $IN->param('amount') >= $payment->{payments_amount};
return if $payment->{payments_status} == COMPLETED;
$pay->update(
{ payments_status => COMPLETED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ACCEPTED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_APPROVED') => 'WorldPay') . "\n" .
"Transaction ID: " . $IN->param('transId') . "\n" .
"Amount: " . $IN->param('amountString') . " (" . $IN->param('authAmountString') . ")\n" .
($IN->param('futurePayId') ? "FuturePay ID: " . $IN->param('futurePayId') . "\n" : '') .
"Authorization Message: " . $IN->param('rawAuthMessage') . "\n"
)
});
Links::Payment::process_payment($payment->{payments_linkid}, $payment->{payments_term});
$end = 0;
},
on_cancel => sub {
# The user clicked "cancel payment"
$pay->update(
{ payments_status => DECLINED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_DECLINED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_CANCELLED') => 'WorldPay') . "\n" .
"Amount: " . $IN->param('amountString') . " (" . $IN->param('authAmountString') . ")\n"
)
});
},
on_invalid_password => sub {
$pay->update(
{ payments_status => ERROR, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ERROR,
paylogs_time => time,
paylogs_text => sprintf(Links::language('PAYMENT_REMOTE_INVALIDPW') => 'WorldPay') . "\n"
});
},
on_recurring => sub {
# A recurring payment, NOT counting the original payment
$pay->update(
{ payments_status => COMPLETED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_ACCEPTED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_RECURRING_ACCEPTED') => 'WorldPay') . "\n" .
"Transaction ID: " . $IN->param('transId') . "\n" .
"Amount: " . $IN->param('amountString') . " (" . $IN->param('authAmountString') . ")\n" .
"FuturePay ID: " . $IN->param('futurePayId') . "\n" .
"Authorization Message: " . $IN->param('rawAuthMessage') . "\n"
)
});
# The "1" gives them an extra day for recurring payments.
Links::Payment::process_payment($payment->{payments_linkid}, $payment->{payments_term}, 1);
},
on_recurring_failed => sub {
$pay->update(
{ payments_status => DECLINED, payments_last => time },
{ payments_id => $payment->{payments_id} }
);
$log->insert({
paylogs_payments_id => $payment->{payments_id},
paylogs_type => LOG_DECLINED,
paylogs_time => time,
paylogs_text => (
sprintf(Links::language('PAYMENT_REMOTE_RECURRING_DECLINED') => 'WorldPay') . "\n" .
"Amount: " . $IN->param('amountString') . " (" . $IN->param('authAmountString') . ")\n"
)
});
}
);
print $IN->header;
unless ($end) {
print Links::SiteHTML::display('payment_success');
}
1;
}
1;

View File

@ -0,0 +1,166 @@
# ==================================================================
# 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: Plugins.pm,v 1.48 2005/04/14 01:08:49 jagerman 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::Plugins;
# ==================================================================
use strict;
use Links qw/$IN $CFG/;
# ------------------------------------------------------------------------------------------------- #
# Plugin config #
# ------------------------------------------------------------------------------------------------- #
sub get_plugin_user_cfg {
# --------------------------------------------------------------
# Returns the user config hash for a given plugin.
#
my $class = ($_[0] eq 'Links::Plugins') ? shift : '';
my $plugin_name = shift || return;
my $cfg = GT::Plugins->load_cfg ( $CFG->{admin_root_path} . '/Plugins' );
exists $cfg->{$plugin_name} or return {};
(ref $cfg->{$plugin_name}->{user} eq 'ARRAY') or return {};
my $opts = {};
foreach my $opt (@{$cfg->{$plugin_name}->{user}}) {
$opts->{$opt->[0]} = $opt->[1];
}
return $opts;
}
sub set_plugin_user_cfg {
# --------------------------------------------------------------
# Takes a plugin name and config hash and saves it.
#
my $class = ($_[0] eq 'Links::Plugins') ? shift : '';
my $plugin_name = shift || return;
my $hash = shift || return;
my $cfg = GT::Plugins->load_cfg ( $CFG->{admin_root_path} . '/Plugins' );
exists $cfg->{$plugin_name} or return;
(ref $cfg->{$plugin_name}->{user} eq 'ARRAY') or return {};
foreach my $opt (@{$cfg->{$plugin_name}->{user}}) {
$opt->[1] = $hash->{$opt->[0]};
}
return GT::Plugins->save_cfg ( $CFG->{admin_root_path} . '/Plugins', $cfg );
}
sub get_plugin_registry {
# --------------------------------------------------------------
# Returns the user config hash for a given plugin.
#
my $class = ($_[0] eq 'Links::Plugins') ? shift : '';
my $plugin_name = shift || return;
my $cfg = GT::Plugins->load_cfg ( $CFG->{admin_root_path} . '/Plugins' );
exists $cfg->{$plugin_name} or return {};
return ( $cfg->{$plugin_name}->{registry} || {} );
}
sub set_plugin_registry {
# --------------------------------------------------------------
# Takes a plugin name and config hash and saves it.
#
my $class = ($_[0] eq 'Links::Plugins') ? shift : '';
my $plugin_name = shift || return;
my $hash = shift || return;
my $cfg = GT::Plugins->load_cfg ( $CFG->{admin_root_path} . '/Plugins' );
exists $cfg->{$plugin_name} or return;
my $registry = ( $cfg->{$plugin_name}->{registry} ||= {} );
foreach my $opt ( keys %{$hash} ) {
$registry->{$opt} = $hash->{$opt};
}
return GT::Plugins->save_cfg ( $CFG->{admin_root_path} . '/Plugins', $cfg );
}
# ------------------------------------------------------------------------------------------------- #
# Displaying #
# ------------------------------------------------------------------------------------------------- #
sub admin_menu {
# -----------------------------------------------------------------
# Displays the admin menu with the plugin options shown.
#
require GT::Plugins::Manager;
my $man = new GT::Plugins::Manager (
cgi => $IN,
tpl_root => $CFG->{admin_root_path} . "/templates/admin",
plugin_dir => $CFG->{admin_root_path} . "/Plugins",
prog_name => 'lsql',
prog_ver => $CFG->{version},
prog_reg => $CFG->{reg_number},
base_url => 'admin.cgi?do=page&page=plugin_manager.html',
path_to_perl => $CFG->{path_to_perl},
perl_args => "-cw -I$CFG->{admin_root_path}"
);
return $man->admin_menu;
}
# ------------------------------------------------------------------------------------------------- #
# Wizard #
# ------------------------------------------------------------------------------------------------- #
sub wizard {
# -------------------------------------------------------------------
# Manages the plugin wizard, basically just creates a wizard object,
# and returns the output. Real work is done in GT::Plugins::Wizard.
#
require GT::Plugins::Wizard;
my $wiz = GT::Plugins::Wizard->new(
cgi => $IN,
tpl_root => $CFG->{admin_root_path} . "/templates/admin",
plugin_dir => $CFG->{admin_root_path} . "/Plugins",
prog_ver => $CFG->{version},
install_header => 'use Links qw/:objects/;',
initial_indent => '',
dirs => {
user_cgi => '$CFG->{admin_root_path}/..',
admin_cgi => '$CFG->{admin_root_path}'
},
oo => '$PLG'
);
return $wiz->process;
}
# ------------------------------------------------------------------------------------------------- #
# Manager #
# ------------------------------------------------------------------------------------------------- #
sub manager {
# -------------------------------------------------------------------
# Manages the plugin installer, basically just creates an installerobject,
# and returns the output. Real work is done in GT::Plugins::Installer
#
require GT::Plugins::Manager;
my $man = GT::Plugins::Manager->new(
cgi => $IN,
tpl_root => $CFG->{admin_root_path} . "/templates/admin",
plugin_dir => $CFG->{admin_root_path} . "/Plugins",
prog_name => 'lsql',
prog_ver => $CFG->{version},
prog_init => $CFG->{admin_root_path},
prog_reg => $CFG->{reg_number},
base_url => 'admin.cgi?do=page&page=plugin_manager.html',
path_to_perl => $CFG->{path_to_perl},
perl_args => "-cw -I$CFG->{admin_root_path}"
) or return "Error loading plugin manager: $GT::Plugins::error";
return $man->process;
}
1;

View File

@ -0,0 +1,586 @@
# ==================================================================
# 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: SQL.pm,v 1.141 2007/11/16 07:15:00 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.
# ==================================================================
# Contains the default table structure for Gossamer Links tables.
package Links::SQL;
use strict;
use vars qw/@TABLES/;
use Links qw/:payment $DB/;
@TABLES = qw(
Users Links Changes Category CatPrice Reviews CatLinks CatRelations
Editors Verify Sessions EmailTemplates EmailMailings MailingIndex
MailingList MailingListIndex ClickTrack Payments PaymentLogs
Bookmark_Folders Bookmark_Links SearchLogs NewsletterSubscription
);
sub tables {
# ------------------------------------------------------------------
# Defines the SQL tables.
#
my $action = shift || 'warn';
my $output = '';
my $ok = Links::language('dialog_ok');
# --------- Users Table ----------------
create_table(\$output, 'Users', $action,
cols => [
Username => { type => 'CHAR', size => 50, not_null => 1, form_display => Links::language('prompt_Username') },
Password => { type => 'CHAR', binary => 1, size => 25, not_null => 1, form_display => Links::language('prompt_Password') },
Email => { type => 'CHAR', size => 75, not_null => 1, regex => '^(?:.+\@.+\..+|\s*)$', form_display => Links::language('prompt_Email') },
Name => { type => 'CHAR', size => 75, form_display => Links::language('prompt_Name') },
Validation => { type => 'CHAR', size => 20, , form_display => Links::language('prompt_Validation') },
Status => { type => 'ENUM', values => ['Not Validated', 'Registered', 'Administrator'], not_null => 1, default => 'Registered', form_display => Links::language('prompt_Status') },
ReceiveMail => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'Yes', form_display => Links::language('prompt_ReceiveMail') },
SortField => { type => 'VARCHAR', size => 255, not_null => 1, regex => '^[\s\w]+$', default => 'Title', form_display => Links::language('prompt_SortField') },
SortOrd => { type => 'ENUM', values => ['ASC', 'DESC'], not_null => 1, default => 'ASC', form_display => Links::language('prompt_SortOrd') },
PerPage => { type => 'INT', not_null => 1, unsigned => 1, default => 15, form_display => Links::language('prompt_PerPage') },
Grouping => { type => 'TINYINT', not_null => 1, unsigned => 1, default => 0, form_display => Links::language('prompt_Grouping') },
],
index => {
emailndx => ['Email']
},
pk => 'Username',
subclass => {
table => { Users => 'Links::Table::Users' },
html => { Users => 'Links::HTML::Users' }
}
);
# --------- Links Table ----------------
create_table(\$output, 'Links', $action,
cols => [
ID => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_ID') },
Title => { type => 'CHAR', size => 100, not_null => 1, weight => 3, form_display => Links::language('prompt_Title') },
URL => { type => 'CHAR', size => 255, not_null => 1, weight => 1, default => 'http://', regex => '^\w+:', form_display => Links::language('prompt_URL') },
LinkOwner => { type => 'CHAR', size => 50, not_null => 1, default => 'admin', form_display => Links::language('prompt_LinkOwner') },
Add_Date => { type => 'DATE', not_null => 1, form_display => Links::language('prompt_Add_Date') },
Mod_Date => { type => 'DATE', not_null => 1, form_display => Links::language('prompt_Mod_Date') },
Description => { type => 'TEXT', weight => 1, form_display => Links::language('prompt_Description') },
Contact_Name => { type => 'CHAR', size => 255, form_display => Links::language('prompt_Contact_Name') },
Contact_Email => { type => 'CHAR', size => 255, form_display => Links::language('prompt_Contact_Email') },
Hits => { type => 'INT', not_null => 1, default => 0, regex => '^\d+$', form_display => Links::language('prompt_Hits') },
isNew => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_isNew') },
isChanged => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_isChanged') },
isPopular => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_isPopular') },
isValidated => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'Yes', form_display => Links::language('prompt_isValidated') },
Rating => { type => 'DECIMAL', precision => 4, scale => 2, not_null => 1, default => 0, regex => '^(?:10(?:\.0*)?|\d(?:\.\d*)?)$', form_display => Links::language('prompt_Rating') },
Votes => { type => 'SMALLINT', unsigned => 1, not_null => 1, default => 0, regex => '^\d+$', form_display => Links::language('prompt_Votes') },
Status => { type => 'SMALLINT', not_null => 1, default => 0, regex => '^-?\d+$', form_display => Links::language('prompt_Status') },
Date_Checked => { type => 'DATETIME', form_display => Links::language('prompt_Date_Checked') },
Timestmp => { type => 'TIMESTAMP', time_check => 1, form_display => Links::language('prompt_Timestmp') },
ExpiryDate => { type => 'INT', not_null => 1, default => FREE, form_display => Links::language('prompt_ExpiryDate'), form_size => 35 }, # See FREE, UNPAID & UNLIMITED constants in Links.pm
ExpiryCounted => { type => 'TINYINT', not_null => 1, default => 0, form_display => Links::language('prompt_ExpiryCounted'), form_type => 'hidden' },
ExpiryNotify => { type => 'TINYINT', not_null => 1, default => 0, form_display => Links::language('prompt_ExpiryNotify'), form_type => 'hidden' },
LinkExpired => { type => 'INT', form_display => Links::language('prompt_LinkExpired'), form_type => 'hidden' },
],
pk => 'ID',
ai => 'ID',
fk => {
Users => { LinkOwner => 'Username' }
},
index => {
urlndx => ['URL'],
stndx => ['Status'],
valexpndx => [qw/isValidated ExpiryDate/],
newndx => ['isNew'],
popndx => ['isPopular'],
userndx => ['LinkOwner'],
expiryndx => [qw/ExpiryDate ExpiryNotify/],
expcntndx => [qw/ExpiryCounted ExpiryDate/]
},
subclass => {
table => { Links => 'Links::Table::Links' },
html => { Links => 'Links::HTML::Links' }
}
);
# --------- Changes Table ----------------
create_table(\$output, 'Changes', $action,
cols => [
LinkID => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$' },
Username => { type => 'CHAR', size => 50, not_null => 1, default => 'admin' },
ChgRequest => { type => 'TEXT' },
Timestmp => { type => 'TIMESTAMP' }
],
fk => {
Links => { LinkID => 'ID' },
Users => { Username => 'Username' }
}
);
# --------- Category Table ----------------
my $new_category = create_table(\$output, 'Category', $action,
cols => [
ID => { type => 'INT', not_null => 1, unsigned => 1, form_display => Links::language('prompt_ID') },
Name => { type => 'CHAR', size => 255, not_null => 1, weight => 3, regex => '^[^/]+$', form_display => Links::language('prompt_Name') },
FatherID => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_size => 1, form_display => Links::language('prompt_FatherID') },
CatRoot => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_type => 'hidden' },
CatDepth => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_type => 'hidden' },
Full_Name => { type => 'CHAR', size => 255, form_display => Links::language('prompt_Full_Name') },
Description => { type => 'TEXT', weight => 1, form_display => Links::language('prompt_Description') },
Meta_Description => { type => 'TEXT', form_display => Links::language('prompt_Meta_Description') },
Meta_Keywords => { type => 'TEXT', form_display => Links::language('prompt_Meta_Keywords') },
Header => { type => 'TEXT', form_display => Links::language('prompt_Header') },
Footer => { type => 'TEXT', form_display => Links::language('prompt_Footer') },
Category_Template => { type => 'CHAR', size => 20, form_display => Links::language('prompt_Category_Template') },
Number_of_Links => { type => 'INT', not_null => 1, default => 0, form_display => Links::language('prompt_Number_of_Links') },
Direct_Links => { type => 'INT', not_null => 1, default => 0, form_display => Links::language('prompt_Direct_Links') },
Has_New_Links => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_Has_New_Links') },
Has_Changed_Links => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_Has_Changed_Links') },
Newest_Link => { type => 'DATE', form_display => Links::language('prompt_Newest_Link') },
Timestmp => { type => 'TIMESTAMP', time_check => 1, form_display => Links::language('prompt_Timestmp') },
Payment_Mode => { type => 'TINYINT', not_null => 1, default => 0, form_size => 1, form_names => [GLOBAL,NOT_ACCEPTED,OPTIONAL,REQUIRED], form_values => ['Use global settings','Not accepted','Optional','Required'], form_type => 'SELECT', form_display => Links::language('prompt_Payment_Mode') },
Payment_Description => { type => 'TEXT', form_display => Links::language('prompt_Payment_Description') },
],
subclass => {
table => { Category => 'Links::Table::Category' },
html => { Category => 'Links::HTML::Category' }
},
pk => 'ID',
ai => 'ID',
index => {
catndx => ['Name'],
namndx => ['Full_Name'],
fthrindex => ['FatherID'],
rootndx => ['CatRoot'],
c_p => ['Payment_Mode'],
}
);
# --------- Category Tree -------------------------
$output .= "Creating Category tree... ";
my $e = $DB->editor('Category');
if ($e->add_tree(father => "FatherID", root => "CatRoot", depth => "CatDepth", force => ($new_category ? 'force' : 'check'))) {
$output .= "okay\n";
}
else {
$output .= "failed ($GT::SQL::error)\n";
}
# --------- CatPrice Table ----------------
create_table(\$output, 'CatPrice', $action,
cols => [
cp_id => { type => 'INT', not_null => 1, unsigned => 1 },
cp_cat_id_fk => { type => 'INT', not_null => 1, unsigned => 1 },
cp_term => { type => 'CHAR', not_null => 1, size => 10 }, # e.g. 8d, 1m, 2y, 3w, unlimited, etc.
cp_cost => { type => 'DOUBLE', not_null => 1 },
cp_type => { type => 'TINYINT', not_null => 1, unsigned => 1 }, # 0 = signup, 1 = renewal, 2 = recurring
cp_description => { type => 'TEXT' }
],
pk => 'cp_id',
ai => 'cp_id',
fk => {
Category => { cp_cat_id_fk => 'ID' }
}
);
# --------- Reviews Table ----------------
create_table(\$output, 'Reviews', $action,
cols => [
ReviewID => { type => 'INT', not_null => 1, unsigned => 1, form_display => Links::language('prompt_ReviewID') },
Review_LinkID => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_Review_LinkID') },
Review_Owner => { type => 'CHAR', size => 50, not_null => 1, form_display => Links::language('prompt_Review_Owner') },
Review_Rating => { type => 'SMALLINT', unsigned => 1, not_null => 1, default => 0, regex => '^\d+$', form_display => Links::language('prompt_Review_Rating') },
Review_Date => { type => 'DATETIME', not_null => 1, form_display => Links::language('prompt_Review_Date') },
Review_ModifyDate => { type => 'DATETIME', not_null => 1, form_display => Links::language('prompt_Review_ModifyDate') },
Review_Subject => { type => 'CHAR', size => 100, not_null => 1, form_display => Links::language('prompt_Review_Subject') },
Review_Contents => { type => 'TEXT', not_null => 1, form_display => Links::language('prompt_Review_Contents') },
Review_ByLine => { type => 'CHAR', size => 50, form_display => Links::language('prompt_Review_ByLine') },
Review_WasHelpful => { type => 'INT', unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_Review_WasHelpful') },
Review_WasNotHelpful => { type => 'INT', unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_Review_WasNotHelpful') },
Review_Validated => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_Review_Validated') },
Review_GuestName => { type => 'CHAR', size => 75, form_display => Links::language('prompt_Review_GuestName') },
Review_GuestEmail => { type => 'CHAR', size => 75, regex => '^(?:(?:.+\@.+\..+)|\s*)$', form_display => Links::language('prompt_Review_GuestEmail') },
],
pk => 'ReviewID',
ai => 'ReviewID',
subclass => {
table => { Reviews => 'Links::Table::Reviews' }
},
index => {
rownerndx => ['Review_Owner'],
rdatendx => ['Review_Date'],
rlinkndx => ['Review_LinkID']
},
fk => {
Links => { Review_LinkID => 'ID' },
Users => { Review_Owner => 'Username' }
}
);
# --------- CatLinks Table ----------------
create_table(\$output, 'CatLinks', $action,
cols => [
LinkID => { type => 'INT', not_null => 1, unsigned => 1 },
CategoryID => { type => 'INT', not_null => 1, unsigned => 1 }
],
subclass => {
table => { CatLinks => 'Links::Table::CatLinks' }
},
index => {
lndx => ['LinkID']
},
unique => {
cl_cl_q => [qw/CategoryID LinkID/]
},
fk => {
Links => { LinkID => 'ID' },
Category => { CategoryID => 'ID' }
}
);
# --------- CatRelations Table ----------------
create_table(\$output, 'CatRelations', $action,
cols => [
CategoryID => { type => 'INT', not_null => 1, unsigned => 1 },
RelatedID => { type => 'INT', not_null => 1, unsigned => 1 },
RelationName => { type => 'VARCHAR', size => 255 }
],
index => {
catid => ['CategoryID']
},
fk => {
Category => { CategoryID => 'ID', RelatedID => 'ID' }
}
);
# --------- User Editors Table ----------------
create_table(\$output, 'Editors', $action,
cols => [
Username => { type => 'CHAR', size => 50, not_null => 1 },
CategoryID => { type => 'INT', unsigned => 1, not_null => 1 },
CanAddCat => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanModCat => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanDelCat => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanMoveCat => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanAddLink => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanDelLink => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanModLink => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanCopyLink => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanMoveLink => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanValLink => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanModReview => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanAddRel => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
CanAddEdit => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
],
unique => {
edituserndx => ['Username', 'CategoryID']
},
fk => {
Users => { Username => 'Username' },
Category => { CategoryID => 'ID' }
}
);
# --------- Verify History Table ----------------
create_table(\$output, 'Verify', $action,
cols => [
LinkID => { type => 'INT', unsigned => 1, not_null => 1 },
Status => { type => 'SMALLINT', not_null => 1, default => 0 },
Date_Checked => { type => 'DATE' }
],
index => {
veriflndx => ['LinkID']
},
fk => {
Links => { LinkID => 'ID' }
}
);
# --------- Session Table ----------------
create_table(\$output, 'Sessions', $action,
cols => [
session_id => { type => 'CHAR', size => 32, not_null => 1, binary => 1 },
session_user_id => { type => 'CHAR', size => 50, not_null => 1 },
session_date => { type => 'INT', not_null => 1 },
session_expires => { type => 'TINYINT', default => 1 },
session_data => { type => 'TEXT' }
],
pk => 'session_id',
fk => {
Users => { session_user_id => 'Username' }
}
);
# --------- Email Template Table ----------------
create_table(\$output, 'EmailTemplates', $action,
cols => [
Name => { type => 'CHAR', size => 50, not_null => 1, regex => '\S' },
MsgFrom => { type => 'TEXT', not_null => 1, regex => '\A(?:\S+\@[a-zA-Z0-9][a-zA-Z0-9-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9-]*)+)\Z' },
MsgFromName => { type => 'TEXT', not_null => 1 },
Subject => { type => 'TEXT', not_null => 1 },
Message => { type => 'MEDIUMTEXT', not_null => 1 },
MessageFormat => { type => 'ENUM', values => [qw[text html]], not_null => 1, default => 'text' },
LinkTemplate => { type => 'MEDIUMTEXT' }
],
pk => 'Name'
);
# --------- Email Mailings Table -------------
create_table(\$output, 'EmailMailings', $action,
cols => [
ID => { type => 'INT', unsigned => 1, not_null => 1 },
Mailing => { type => 'INT', unsigned => 1, not_null => 1 },
Email => { type => 'TEXT', size => 75, not_null => 1 },
Sent => { type => 'TINYINT', default => 0, not_null => 1 },
LinkID => { type => 'INT', unsigned => 1 } # If this is a sending to link owners, this will hold the Link ID
],
pk => 'ID',
ai => 'ID'
);
# --------- Email Mailing Index Table --------
create_table(\$output, 'MailingIndex', $action,
cols => [
Mailing => { type => 'INT', unsigned => 1, not_null => 1 },
extra => { type => 'TINYTEXT', not_null => 1 },
done => { type => 'INT' },
mailfrom => { type => 'TEXT', not_null => 1 },
name => { type => 'TEXT', not_null => 1 },
subject => { type => 'TEXT', not_null => 1 },
message => { type => 'MEDIUMTEXT', not_null => 1 },
messageformat => { type => 'ENUM', values => [qw[text html]], not_null => 1, default => 'text' },
],
pk => 'Mailing',
ai => 'Mailing'
);
# --------- MailingList Table ----------------
create_table(\$output, 'MailingList', $action,
cols => [
ID => { type => 'INT', not_null => 1 },
Email => { type => 'CHAR', size => 255, not_null => 1 }
],
index => {
maillistndx => ['Email']
}
);
# --------- MailingListIndex Table ----------------
create_table(\$output, 'MailingListIndex', $action,
cols => [
ID => { type => 'INT', unsigned => 1, not_null => 1 },
Name => { type => 'CHAR', size => 255, not_null => 1 },
DateModified => { type => 'INT', not_null => 1 },
DateCreated => { type => 'INT', not_null => 1 }
],
pk => 'ID',
ai => 'ID'
);
# --------- ClickTrack Table ----------------
create_table(\$output, 'ClickTrack', $action,
cols => [
LinkID => { type => 'INT', not_null => 1 },
IP => { type => 'CHAR', size => 16, not_null => 1 },
ClickType => { type => 'ENUM', values => ['Rate', 'Hits','Review'], not_null => 1 },
ReviewID => { type => 'INT', not_null => 1, default => 0},
Created => { type => 'TIMESTAMP' }
],
subclass => {
table => { ClickTrack => 'Links::Table::ClickTrack' }
},
unique => {
ct_licr => ['LinkID', 'IP', 'ClickType','ReviewID']
},
index => {
cndx => ['Created']
}
);
# --------- Payments Table ----------------
create_table(\$output, 'Payments', $action,
cols => [
payments_id => { type => 'CHAR', not_null => 1, size => 16 },
payments_linkid => { type => 'INT', unsigned => 1, not_null => 1 },
payments_status => { type => 'INT', not_null => 1, default => 0, unsigned => 1 }, # 0 = pending, 1 = completed, 2 = declined, 3 = error
payments_method => { type => 'CHAR', not_null => 1, size => 25 },
payments_type => { type => 'TINYINT', not_null => 1, unsigned => 1 }, # 0 = initial payment, 1 = renewal payment, 2 = recurring payment
payments_amount => { type => 'DOUBLE', not_null => 1 },
payments_term => { type => 'CHAR', not_null => 1, size => 10 }, # e.g. 8d, 1m, 2y, 3w, unlimited, etc.
payments_start => { type => 'INT', not_null => 1, unsigned => 1 },
payments_last => { type => 'INT', not_null => 1, unsigned => 1 },
],
pk => 'payments_id',
fk => {
Links => { payments_linkid => 'ID' }
},
index => {
p_sl => ['payments_status', 'payments_last'],
p_ll => ['payments_linkid', 'payments_last'],
p_al => ['payments_amount', 'payments_last'],
}
);
# --------- Payment Logs Table ----------------
create_table(\$output, 'PaymentLogs', $action,
cols => [
paylogs_id => { type => 'INT', not_null => 1, unsigned => 1 },
paylogs_payments_id => { type => 'CHAR', not_null => 1, size => 16 },
paylogs_type => { type => 'INT', not_null => 1, default => 0, unsigned => 1 }, # 0 = info, 1 = accepted, 2 = declined, 3 = error
paylogs_time => { type => 'INT', not_null => 1, unsigned => 1 },
paylogs_viewed => { type => 'TINYINT', not_null => 1, default => 0, unsigned => 1 },
paylogs_text => { type => 'TEXT' },
],
pk => 'paylogs_id',
ai => 'paylogs_id',
fk => {
Payments => { paylogs_payments_id => 'payments_id' }
},
index => {
pl_yt => ['paylogs_type', 'paylogs_time'],
pl_t => ['paylogs_time']
}
);
# --------- Bookmark Folders Table ----------------
create_table(\$output, 'Bookmark_Folders', $action,
cols => [
my_folder_id => { type => 'INT', not_null => 1, unsigned => 1 },
my_folder_name => { type => 'VARCHAR', not_null => 1, size => 255 },
my_folder_description => { type => 'VARCHAR', size => 255 },
my_folder_user_username_fk => { type => 'VARCHAR', size => 50 },
my_folder_default => { type => 'TINYINT', not_null => 1, default => 0, unsigned => 1 },
my_folder_public => { type => 'TINYINT', not_null => 1, default => 0, unsigned => 1 }
],
pk => 'my_folder_id',
ai => 'my_folder_id',
fk => {
Users => { my_folder_user_username_fk => 'Username' }
}
);
# --------- Bookmark Links Table ----------------
create_table(\$output, 'Bookmark_Links', $action,
cols => [
my_id => { type => 'INT', not_null => 1, unsigned => 1 },
my_link_id_fk => { type => 'INT', not_null => 1, unsigned => 1 },
my_user_username_fk => { type => 'VARCHAR', size => 50 },
my_folder_id_fk => { type => 'INT', not_null => 1, unsigned => 1 },
my_comment => { type => 'VARCHAR', size => '255' }
],
pk => 'my_id',
ai => 'my_id',
fk => {
Users => { my_user_username_fk => 'Username' },
Bookmark_Folders => { my_folder_id_fk => 'my_folder_id' },
Links => { my_link_id_fk => 'ID' },
}
);
# --------- SearchLogs Table ----------------
create_table(\$output, 'SearchLogs', $action,
cols => [
slog_query => { type => 'VARCHAR', not_null => 1, size => 255 },
slog_count => { type => 'INT', not_null => 1, default => 0 },
slog_hits => { type => 'INT', not_null => 1, default => 0 },
slog_time => { type => 'FLOAT' },
slog_last => { type => 'INT', not_null => 1, default => 0 },
],
pk => 'slog_query'
);
# --------- Newsletter Subscription Table ----------------
create_table(\$output, 'NewsletterSubscription', $action,
cols => [
UserID => { type => 'CHAR', size => 50 },
CategoryID => { type => 'INT', not_null => 1 },
],
unique => {
ns_uc => ['UserID', 'CategoryID']
},
fk => {
Users => { UserID => 'Username' },
Category => { CategoryID => 'ID' }
}
);
return $output;
}
sub create_table {
my ($output, $table, $action, @def) = @_;
$$output .= Links::language('dialog_create', $table);
my $c = $DB->creator($table);
$c->clear_schema() if $action eq 'force';
@def % 2 and die "Odd number of table defs passed to create_table()";
while (@def) {
my ($meth, $arg) = splice @def, 0, 2;
$c->$meth($arg);
}
if ($c->create($action)) {
$$output .= Links::language('dialog_ok');
return 1;
}
else {
$$output .= Links::language($GT::SQL::errcode eq 'TBLEXISTS' ? ('error_failed_exists') : ('error_failed_other', $GT::SQL::error));
$GT::SQL::errcode if 0; # silence "used only once" warning
$c->set_defaults;
$c->save_schema;
return 0;
}
}
sub load_from_sql {
# ---------------------------------------------------------------
# Creates def files based on existing tables.
#
my ($output, $return);
foreach my $table (@TABLES) {
$output .= "$table .. ";
my $c = $DB->creator($table);
$return = $c->load_table($table);
if ($return) {
if ($table eq 'Links' or $table eq 'Users' or $table eq 'Category') {
$c->subclass(
table => { $table => "Links::Table::$table" },
html => { $table => "Links::HTML::$table" }
);
}
elsif ($table eq 'CatLinks' or $table eq 'ClickTrack') {
$c->subclass(
table => { $table => "Links::Table::$table" }
);
}
$output .= "ok!\n";
$c->save_schema();
}
else {
$output .= "failed: $GT::SQL::error\n";
}
}
return $output;
}
sub load {
# ---------------------------------------------------------------
# Return a hash of current connection settings.
#
my %h = ();
$h{prefix} = $DB->prefix();
$h{database} = $DB->{connect}->{database};
$h{login} = $DB->{connect}->{login};
$h{password} = $DB->{connect}->{password};
$h{host} = $DB->{connect}->{host};
$h{host} .= ":" . $DB->{connect}->{port} if $DB->{connect}->{port};
$h{driver} = $DB->{connect}->{driver};
return \%h;
}
1;

View File

@ -0,0 +1,313 @@
# ==================================================================
# 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: SiteHTML.pm,v 1.89 2008/04/29 04:02:34 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::SiteHTML;
# ==================================================================
use strict;
use Links qw/:objects :payment/;
sub display {
# -----------------------------------------------------------------
# Returns a specified template parsed.
#
my ($template, $vars, $opts) = @_;
my $code = exists $Links::SiteHTML::{"site_html_$template"} ? $Links::SiteHTML::{"site_html_$template"} : _compile("site_html_$template");
defined $code or die "Invalid method: site_html_$template called.";
$PLG->dispatch("site_html_$template", $code, $vars, $opts);
}
sub tags {
# -----------------------------------------------------------------
# Returns the tags needed to properly include a template in another template,
# instead of returning parsed HTML like display(). Currently only supports
# 'link' for formatted link information.
#
my ($sub, $vars, $opts) = @_;
my $code = exists $Links::SiteHTML::{"site_tags_$sub"} && $Links::SiteHTML::{"site_tags_$sub"};
defined $code or die "Invalid method: site_tags_$sub called.";
$PLG->dispatch("site_tags_$sub", $code, $vars, $opts);
}
# All the templates are auto-loaded, except for the ones below which need
# to do some special stuff.
sub site_tags_link {
# --------------------------------------------------------
# Format the tags for a single link.
#
my ($vars, $cat_id) = @_;
my %block = $Links::GLOBALS ? map { $_ => 1 } keys %$Links::GLOBALS : ();
my %rec = map { exists $block{$_} ? () : ($_ => $vars->{$_}) } keys %$vars;
$rec{Add_Date_time} = GT::Date::timelocal(GT::Date::parse_format($rec{Add_Date}, GT::Date::FORMAT_DATE));
$rec{Mod_Date_time} = GT::Date::timelocal(GT::Date::parse_format($rec{Mod_Date}, GT::Date::FORMAT_DATE));
# Convert the date formats.
if (GT::Date::FORMAT_DATE ne $CFG->{date_user_format}) {
Links::init_date();
$rec{Add_Date} = GT::Date::date_transform($rec{Add_Date}, GT::Date::FORMAT_DATE, $CFG->{date_user_format}) || $rec{Add_Date};
$rec{Mod_Date} = GT::Date::date_transform($rec{Mod_Date}, GT::Date::FORMAT_DATE, $CFG->{date_user_format}) || $rec{Mod_Date};
}
# Set new and pop to either 1 or undef for templates.
$rec{isNew} = ($rec{isNew} and ($rec{isNew} eq 'Yes' or $rec{isNew} eq '1')) ? 1 : 0;
$rec{isChanged} = ($rec{isChanged} and ($rec{isChanged} eq 'Yes' or $rec{isChanged} eq '1')) ? 1 : 0;
$rec{isPopular} = ($rec{isPopular} and ($rec{isPopular} eq 'Yes' or $rec{isPopular} eq '1')) ? 1 : 0;
$rec{isLinkOwner} = ($USER and defined $USER->{Username} and $rec{LinkOwner} eq $USER->{Username}) ? 1 : 0;
$rec{paymentsEnabled} = 0; # The payment url is disabled by default
if ($CFG->{payment}->{enabled}) {
my $catp;
# Fetch payment information for the category the link is in (used below to determine if the payment data should be shown)
my @cid = $DB->table('CatLinks')->select('CategoryID', { LinkID => $rec{ID} })->fetchall_list;
require Links::Payment;
$catp = Links::Payment::load_cat_price(\@cid);
# Add various extra tags regarding payment if the current user is the link owner:
if ($rec{isLinkOwner} and $rec{ExpiryDate} != UNLIMITED and $catp->{payment_mode} != NOT_ACCEPTED) {
my $expiry_date = $rec{ExpiryDate};
my $notify_date = time + $CFG->{payment}->{expiry_notify} * (24*60*60);
$rec{paymentsEnabled} = 1;
$rec{isUnpaid} = $expiry_date == UNPAID;
$rec{isFree} = $expiry_date == FREE;
$rec{isExpired} = ($expiry_date > UNPAID and $expiry_date < time or $rec{isFree} and $rec{LinkExpired});
$rec{wasPaid} = ($expiry_date > UNPAID and $expiry_date < FREE or $rec{isFree} and $rec{LinkExpired});
$rec{ExpiryDateFormatted} = ($expiry_date > UNPAID and $expiry_date < FREE)
? GT::Date::date_get($expiry_date, $CFG->{date_expiry_format})
: ($rec{isFree} and $rec{LinkExpired})
? GT::Date::date_get($rec{LinkExpired}, $CFG->{date_expiry_format})
: '';
$rec{isNotify} = ($expiry_date >= time and $expiry_date <= $notify_date) ? 1 : 0;
}
$rec{isPaidLink} = 0;
$rec{isFreeLink} = 0;
if ($rec{ExpiryDate} >= time and $rec{ExpiryDate} < FREE) {
$rec{isPaidLink} = 1;
}
elsif ($rec{ExpiryDate} == FREE) {
$rec{isFreeLink} = 1;
}
}
my $links = $DB->table('Links');
if ($CFG->{build_detailed}) {
my $detailed;
# Generate the detailed url for a specific the category that we're in (a link may be in multiple categories)
if ($cat_id) {
$detailed = $links->category_detailed_url($cat_id, $rec{ID});
}
else {
$detailed = $links->detailed_url($rec{ID});
}
$rec{detailed_url} = "$CFG->{build_detail_url}/$detailed";
}
# Load any reviews, if not already done
$links->add_reviews(\%rec) unless exists $rec{Review_Loop};
\%rec;
}
sub site_html_link {
# --------------------------------------------------------
# Format and return the HTML for a single link.
#
# Note that this method is deprecated in favour of generating all the html in
# the templates. Instead, you should be doing:
# <%Links::Utils::load_link_info%><%include link.html%>
#
my $rec = tags(link => @_);
# Set the template set to use.
my $opts = { dynamic => 0 };
if ($rec->{Category_Template} and $rec->{Category_Template} =~ /^[\w-]+(\.[\w-]+)?$/ and (not $1 or ($1 ne '.html' and $1 ne '.htm'))) {
$opts->{template} = delete $rec->{Category_Template};
}
# Parse the template.
return Links::user_page('link.html', $rec, $opts);
}
sub site_html_detailed {
# --------------------------------------------------------
# Return parsed detailed page (one link per html page).
#
my $rec = shift;
$rec->{Add_Date_time} = GT::Date::timelocal(GT::Date::parse_format($rec->{Add_Date}, GT::Date::FORMAT_DATE));
$rec->{Mod_Date_time} = GT::Date::timelocal(GT::Date::parse_format($rec->{Mod_Date}, GT::Date::FORMAT_DATE));
# Convert the date formats.
if (GT::Date::FORMAT_DATE ne $CFG->{date_user_format}) {
Links::init_date();
$rec->{Add_Date_orig} = $rec->{Add_Date};
$rec->{Add_Date} = GT::Date::date_transform($rec->{Add_Date}, GT::Date::FORMAT_DATE, $CFG->{date_user_format}) || $rec->{Add_Date};
$rec->{Mod_Date_orig} = $rec->{Mod_Date};
$rec->{Mod_Date} = GT::Date::date_transform($rec->{Mod_Date}, GT::Date::FORMAT_DATE, $CFG->{date_user_format}) || $rec->{Mod_Date};
}
# Set new and pop to either 1 or undef for templates.
$rec->{isNew} = ($rec->{isNew} and ($rec->{isNew} eq 'Yes' or $rec->{isNew} eq '1')) ? 1 : 0;
$rec->{isChanged} = ($rec->{isChanged} and ($rec->{isChanged} eq 'Yes' or $rec->{isChanged} eq '1')) ? 1 : 0;
$rec->{isPopular} = ($rec->{isPopular} and ($rec->{isPopular} eq 'Yes' or $rec->{isPopular} eq '1')) ? 1 : 0;
$rec->{isLinkOwner} = ($USER and defined $USER->{Username} and $rec->{LinkOwner} eq $USER->{Username}) ? 1 : 0;
if ($CFG->{payment}->{enabled}) {
$rec->{isPaidLink} = 0;
$rec->{isFreeLink} = 0;
if ($rec->{ExpiryDate} >= time and $rec->{ExpiryDate} < FREE) {
$rec->{isPaidLink} = 1;
}
elsif ($rec->{ExpiryDate} == FREE) {
$rec->{isFreeLink} = 1;
}
}
# Set the template set to use.
my $opts = { dynamic => 1 };
if ($rec->{Category_Template} and $rec->{Category_Template} =~ /^[\w-]+(\.[\w-]+)?$/ and (not $1 or ($1 ne '.html' and $1 ne '.htm'))) {
$opts->{template} = delete $rec->{Category_Template};
}
my $output = Links::user_page('detailed.html', $rec, $opts);
return $output;
}
sub site_html_category {
# --------------------------------------------------------
# Return parsed category page.
#
my $tags = shift;
$tags->{build_links_per_page} = $CFG->{build_links_per_page};
($tags->{category_first}) = $tags->{'category_name'} =~ m,/?([^/]+)$,;
my $opts = { dynamic => 1 };
# Find the proper template.
my $template = 'category.html';
# If the Category_Template ends with .htm or .html, then use that file as a template, otherwise, use it as a template set.
if ($tags->{Category_Template} and $tags->{Category_Template} =~ /^[\w-]+(\.[\w-]+)?$/ and (not $1 or ($1 ne '.html' and $1 ne '.htm'))) {
$opts->{template} = delete $tags->{Category_Template};
}
elsif ($tags->{Category_Template}) {
$template = $tags->{Category_Template};
}
my $output = Links::user_page($template, $tags, $opts);
return $output;
}
sub site_html_print_cat {
# --------------------------------------------------------
# This routine prints out a list of categories.
#
# Note that this method has been deprecated in favour of using loops and
# performing html generation in the templates. If you need to modify
# the category data, use the build_category_loop plugin hook.
#
my @subcat = @{$_[0]};
my $parent_cat = shift @subcat;
my $breakpoint = int(@subcat / $CFG->{build_category_columns});
$breakpoint++ if @subcat % $CFG->{build_category_columns};
my $table_head = $CFG->{build_category_table} || '';
my $width = int(100 / $CFG->{build_category_columns});
my $output = '';
my $i = 0;
my $cat_db = $DB->table('Category');
my $opts = { dynamic => 0 };
# Print Header.
if ($CFG->{build_category_columns}) {
$output = qq|<div class="margin"><table $table_head><tr><td class="catlist" width="$width%" valign="top">\n|;
}
# Figure out if we should use a different template.
if ($parent_cat->{Category_Template} and $parent_cat->{Category_Template} =~ /^[\w-]+(\.[\w-]+)?$/ and (not $1 or ($1 ne '.html' and $1 ne '.htm'))) {
$opts->{template} = delete $parent_cat->{Category_Template};
}
# Go through each subcategory and print its template.
for my $cat_r (@subcat) {
$cat_r->{Short_Name} = $cat_r->{Name} =~ m,.*/([^/]+)$, ? $1 : $cat_r->{Name};
$cat_r->{URL} ||= $CFG->{build_root_url} . "/" . $cat_db->as_url($cat_r->{Full_Name}) . "/" . ($CFG->{build_index_include} ? $CFG->{build_index} : '');
# Set the short name.
if ($cat_r->{Related}) {
if ($cat_r->{RelationName}) {
$cat_r->{Short_Name} = $cat_r->{RelationName};
}
else {
if (exists $parent_cat->{Name} and ($cat_r->{Short_Name} eq $parent_cat->{Name})) {
my ($short) = $cat_r->{Full_Name} =~ m,([^/]+)/[^/]*$,;
$short and ($cat_r->{Short_Name} = $short);
}
else {
$cat_r->{Short_Name} = $cat_r->{Short_Name};
}
}
}
# We check to see if we are half way through, if so we stop this table cell
# and begin a new one (this lets us have category names in two columns).
if ($CFG->{build_category_columns}) {
$output .= qq|</td>\n<td valign="top" width="$width%" class="catlist">\n| if $i > 0 and not $i % $breakpoint;
$i++;
}
$output .= Links::user_page('subcategory.html', $cat_r, $opts);
}
# Don't forget to end the table properly ..
if ($CFG->{build_category_columns}) {
$output .= "</td></tr></table></div>\n";
}
return $output;
}
sub site_html_error {
# --------------------------------------------------------
# Print out the error page
#
my ($vars, $opts) = @_;
$opts ||= { dynamic => 1 };
unless (exists $vars->{main_title_loop}) {
require Links::Build;
$vars->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_ERROR'), $CFG->{build_root_url} . "/" . ($CFG->{build_home} || ($CFG->{build_index_include} ? $CFG->{build_index} : '')));
}
return Links::user_page('error.html', $vars, $opts);
}
sub _compile {
# -------------------------------------------------------------------
# Compile dynamically creates site_html routines if a template file
# exists.
#
my $sub = shift;
my ($file) = $sub =~ /^site_html_([\w-]+)$/;
$file or return sub { display('error', { error => "Invalid SiteHTML method: '" . $IN->html_escape($sub) . "'." }) };
$file .= '.html';
my $template_set = Links::template_set();
unless (Links::template_exists($template_set, $file)) {
return sub { display('error', { error => "Invalid SiteHTML method: $sub ($file). The template does not exist in '$template_set'." }) };
}
my $code = sub { my ($vars, $opts) = @_; $opts ||= { dynamic => 1 }; return Links::user_page($file, $vars, $opts) };
$Links::SiteHTML::{$sub} = $code;
$code;
}
1;

View File

@ -0,0 +1,95 @@
# ==================================================================
# 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: CatLinks.pm,v 1.4 2006/03/25 01:13:35 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::Table::CatLinks;
# ==================================================================
use strict;
use Links qw/:payment :objects/;
use GT::SQL;
use GT::SQL::Table;
use vars qw /@ISA $ERROR_MESSAGE @DELETING/;
@ISA = qw/GT::SQL::Table/;
$ERROR_MESSAGE = 'GT::SQL';
@DELETING = (); # Used by Links::Table::Links
sub delete {
# -----------------------------------------------------------------------------
# We override the default CatLinks delete to delete any links that will no
# longer be referenced as a result of the deletion.
#
my ($self, $cond) = @_;
ref $cond or return $self->fatal(BADARGS => '$catlinks->delete(condition)');
# Get the CatLinks rows that are about to be deleted
my (%delete, %links);
my $sth = $self->select($cond);
while (my $row = $sth->fetchrow_hashref) {
$delete{$row->{LinkID}}++;
if (exists $links{$row->{LinkID}}) {
push @{$links{$row->{LinkID}}}, $row->{CategoryID};
}
else {
$links{$row->{LinkID}} = [$row->{CategoryID}];
}
}
# Delete the CatLinks rows
my $ret = $self->SUPER::delete($cond) or return;
# Get the links that still exist in the CatLinks table after the delete (ie.
# links that were in multiple categories). These are the links that shouldn't
# be deleted from the Links table.
my @remaining = keys %delete ? $self->select('LinkID', { LinkID => [keys %delete] })->fetchall_list : ();
for (@remaining, @DELETING) {
delete $delete{$_};
}
# Non-validated links don't increment Category counts.
my @notval = keys %links ? $DB->table('Links')->select('ID', { ID => [keys %links], isValidated => 'No' })->fetchall_list : ();
for (@notval, @DELETING) {
delete $links{$_};
}
# Any links in %delete have no references to it from CatLinks
if (keys %delete) {
$DB->table('Links')->delete({ ID => [keys %delete] });
}
# Build a list of categories that need their counts updated
my %cats;
for (keys %links) {
for (@{$links{$_}}) {
$cats{$_}++;
}
}
# Update the Category link counts
if (keys %cats) {
my $category = $DB->table('Category');
my %change;
while (my ($catid, $count) = each %cats) {
push @{$change{-$count}}, $catid;
}
$category->link_count(\%change);
while (my ($change, $ids) = each %change) {
$category->update({ Direct_Links => \("Direct_Links - " . abs $change) }, { ID => $ids });
}
}
$ret;
}
1;

View File

@ -0,0 +1,638 @@
# ==================================================================
# 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: Category.pm,v 1.29 2009/05/11 05:57:45 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::Table::Category;
# ==================================================================
use strict;
use Links qw/:payment :objects/;
use GT::SQL;
use GT::SQL::Table;
use GT::Lock qw/lock unlock LOCK_TRY/;
use vars qw /@ISA $ERRORS $ERROR_MESSAGE/;
@ISA = qw/GT::SQL::Table/;
$ERROR_MESSAGE = 'GT::SQL';
$ERRORS = {
BADCATNAME => "Invalid category name: %s",
BADCATID => "Invalid category id: %s",
BADCATSUG => "There is no category with that name. Perhaps you meant: %s",
CATEXISTS => "A category with the name '%s' already exists.",
};
# We wrap new() to handle updating Number_of_Links - but only once: the first
# time a Category table object is created.
sub new {
my $self = shift->SUPER::new(@_) or return;
return $self if $STASH{expired_links}++;
my $links = $DB->table('Links');
my $cond;
if ($CFG->{payment}->{enabled}) {
$cond = GT::SQL::Condition->new(
ExpiryCounted => '=' => 0,
ExpiryDate => '<' => time,
isValidated => '=' => 'Yes'
);
}
else {
$cond = GT::SQL::Condition->new(
ExpiryCounted => '=' => 1,
isValidated => '=' => 'Yes'
);
}
# Don't select the ID's here because we haven't established a lock. Since
# most requests won't catch expired links, doing a count here to avoid
# needing the lock is going to be slightly slower occassionally, but
# usually faster.
return $self unless $links->count($cond);
# We've now determined that there _are_ links that have expired that
# haven't been counted yet, so we establish a lock (to prevent a race
# condition), and then update the links counts for categories that have
# newly-expired links. If getting the lock fails, simply return - this is
# only likely to happen when another process has the lock and is performing
# the updates already, or when a previous process with a lock died - the
# 120 should make sure that such a condition doesn't last longer than 2
# minutes.
lock cat_link_count => 1, LOCK_TRY, 120
or return $self;
my @links = $links->select(ID => $cond)->fetchall_list;
unless (@links) { # Despite the above count, there might not be links now if we had to wait for a lock
unlock 'cat_link_count';
return $self;
}
if ($CFG->{payment}->{expired_is_free}) {
# This gets a bit hairy - expired links need to become free but NOT in
# required categories. On the other hand, links in non-required
# categories don't affect the count.
my %req_links = map { $_ => 1 } $DB->table('Category', 'CatLinks')->select(LinkID => { LinkID => \@links, Payment_Mode => $CFG->{payment}->{mode} == REQUIRED ? [GLOBAL, REQUIRED] : REQUIRED })->fetchall_list;
my @to_free = grep !$req_links{$_}, @links;
if (@to_free) {
$DB->table('Links')->update({ LinkExpired => \'ExpiryDate' }, { ID => \@to_free });
$DB->table('Links')->update({ ExpiryDate => FREE }, { ID => \@to_free });
}
@links = keys %req_links;
unless (@links) {
unlock 'cat_link_count';
return $self;
}
}
my $catlinks = $DB->table('CatLinks');
$catlinks->select_options('GROUP BY CategoryID');
my %cats = $catlinks->select(CategoryID => 'COUNT(*)' => { LinkID => \@links })->fetchall_list; # FIXME this query can be huge and will fail (the select() will fail and return undef)
my %adjust;
my %direct_adj;
my $parents = $self->parents([keys %cats]);
for my $cat_id (keys %cats) {
$adjust{$cat_id} ||= 0;
$adjust{$cat_id} += $cats{$cat_id};
$direct_adj{$cat_id} ||= 0;
$direct_adj{$cat_id} += $cats{$cat_id};
for (@{$parents->{$cat_id}}) {
$adjust{$_} ||= 0;
$adjust{$_} += $adjust{$cat_id};
}
}
my %change;
while (my ($id, $change) = each %adjust) {
push @{$change{$CFG->{payment}->{enabled} ? -$change : $change}}, $id;
}
my %change_direct;
while (my ($id, $change) = each %direct_adj) {
push @{$change_direct{$CFG->{payment}->{enabled} ? -$change : $change}}, $id;
}
while (my ($adjust, $ids) = each %change) {
$self->update({ Number_of_Links => \("Number_of_Links " . ($adjust >= 0 ? '+' : '-') . ' ' . abs $adjust) }, { ID => $ids });
}
while (my ($adjust, $ids) = each %change_direct) {
$self->update({ Direct_Links => \("Direct_Links " . ($adjust >= 0 ? '+' : '-') . ' ' . abs $adjust) }, { ID => $ids });
}
$links->update({ ExpiryCounted => $CFG->{payment}->{enabled} ? 1 : 0 }, { ID => \@links });
unlock 'cat_link_count';
return $self;
}
sub add {
# -------------------------------------------------------------------
# Adds a category, but passes it through the plugin system.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('add_category', sub { return $self->_plg_add(@_) }, $p);
}
sub _plg_add {
# -------------------------------------------------------------------
# Add a category.
#
my ($self, $p) = @_;
$self->can_add($p) or return;
# If successful, we need to update timestamps of parents to denote a change.
if (my $id = $self->SUPER::add($p)) {
if ($p->{FatherID}) {
$self->update(
{ Timestmp => \"NOW()" },
{ ID => $self->parents($id) },
{ GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
);
}
return $id;
}
else {
return;
}
}
sub can_add {
# -------------------------------------------------------------------
# Confirms that a category can be added.
#
my $self = shift;
my $p = $self->common_param(@_) or return $self->warn(BADARGS => 'Usage: $table->add(HASH or HASH_REF or CGI)');
# Checks that the FatherID exists and set the full name.
$p->{FatherID} ||= 0;
if ($p->{FatherID} =~ /\D/) {
my $sth = $self->select(ID => Full_Name => { Full_Name => $p->{FatherID} });
if (my @row = $sth->fetchrow) {
$p->{FatherID} = $row[0];
$p->{Full_Name} = "$row[1]/$p->{Name}";
}
else {
my $names = $self->suggestions($p->{FatherID});
return $self->warn(
@$names
? (BADCATSUG => '<ul>' . join('', map "<li>$_</li>", @$names) . '</ul>')
: (BADCATNAME => $p->{FatherId})
);
}
}
elsif ($p->{FatherID} != 0) {
my $full_name = $self->get_name_from_id($p->{FatherID}) or return $self->warn(BADCATID => $p->{FatherID});
$p->{Full_Name} = "$full_name/$p->{Name}";
}
else {
$p->{Full_Name} = $p->{Name};
}
# Checks that there is no other category with the same (Name, FatherID)
return $self->warn(CATEXISTS => $p->{Name})
if $self->count({ Name => $p->{Name}, FatherID => $p->{FatherID} });
return 1;
}
sub delete {
# -------------------------------------------------------------------
# Deletes a category, but passes through the plugin system.
#
my ($self, $where) = @_;
if (not ref $where or ref $where eq 'ARRAY') {
$where = { ID => $where };
}
return $self->fatal(BADARGS => 'Usage: $category->delete(condition)')
unless ref $where eq 'HASH' or UNIVERSAL::isa($where, 'GT::SQL::Condition');
my $ret;
my %cats = $self->select(qw/ID Direct_Links/ => $where)->fetchall_list;
if ($PLG->active_plugins('delete_category')) {
for my $id (keys %cats) {
my $r = $PLG->dispatch('delete_category', sub { return $self->SUPER::delete(@_) }, { ID => $id });
$ret += $r if defined $r;
}
$ret = '0 but true' if (defined $ret and $ret == 0) or not keys %cats;
}
else {
$ret = $self->SUPER::delete($where);
}
return $ret unless $ret;
# Clear out the cache as the hierarchy has changed.
$self->_clear_cache;
$ret;
}
sub modify {
# -------------------------------------------------------------------
# Modifies a category, but passes through the plugin system.
#
my ($self, $cat) = @_;
$PLG->dispatch('modify_category', sub { return $self->_plg_modify(@_) }, $cat);
}
sub _plg_modify {
# -------------------------------------------------------------------
# Modify a single category.
#
my $self = shift;
my $set = shift or return $self->error('BADARGS', 'FATAL', "Usage: \$cat->modify( { col => value ... } ).");
my $id = $set->{ID} or return $self->error('BADARGS', 'FATAL', "No primary key passed to modify!");
# Get the original info.
my $orig = $self->select(qw/ID FatherID Full_Name Name Number_of_Links/ => { ID => $id })->fetchrow_hashref
or return $self->warn(BADCATID => $id);
# Fix up the father ID.
$set->{FatherID} ||= 0;
if ($set->{FatherID} !~ /^\d+$/) {
my $new_id = $self->get_id_from_name($set->{FatherID});
if (! $new_id) {
my $names = $self->suggestions($set->{FatherID});
return $self->error(@$names
? ('BADCATSUG', 'WARN', "<ul>" . join('', map "<li>$_</li>", @$names) . "</ul>")
: ('BADCATNAME', 'WARN', $set->{FatherID})
);
}
$set->{FatherID} = $new_id;
}
$self->can_modify($set, $orig) or return;
if ($orig->{Name} eq $set->{Name} and $orig->{FatherID} == $set->{FatherID}) {
# Name and parent haven't changed, no special modify handling needed
return $self->SUPER::modify($set);
}
elsif ($orig->{FatherID} == $set->{FatherID}) {
# Name has changed, but parent is the same: update ancestors'
# timestamps, change the full name, and update subcategory names.
($set->{Full_Name} = $orig->{Full_Name}) =~ s/\Q$orig->{Name}\E$/$set->{Name}/i;
my $ret = $self->SUPER::modify($set);
if ($ret) {
# Update was successful, update the timestamp of old and new parents
# Clear the as the tree just changed
$self->_clear_cache;
if ($set->{FatherID}) {
my $parents = $self->parents($id);
$self->update({ Timestmp => \"NOW()" }, { ID => $parents }, { GT_SQL_SKIP_CHECK => 1 })
if @$parents;
}
$self->update_full_name($id, $orig->{Full_Name}, $set->{Full_Name});
}
return $ret;
}
else {
# The category has moved; get the new parent's full name and update
my $fn = $self->select(Full_Name => { ID => $set->{FatherID} })->fetchrow;
$set->{Full_Name} = ($fn ? "$fn/" : '') . $set->{Name};
my $ret = $self->SUPER::modify($set);
if ($ret) {
# Clear the cache as the tree has changed.
$self->_clear_cache;
$self->update_full_name($id, $orig->{Full_Name}, $set->{Full_Name});
# Now update counters on the above parents.
# Clear out the cache as otherwise we get our old parents.
if ($orig->{Number_of_Links} != 0) {
$self->link_count($orig->{FatherID}, -$orig->{Number_of_Links});
$self->link_count($set->{FatherID}, $orig->{Number_of_Links});
}
}
# Clear out the cache.
$self->_clear_cache;
return $ret;
}
}
sub update_full_name {
# -----------------------------------------------------------------------------
# Call this after changing a category's Full_Name to change all the category's
# children's full names. Call with the category ID, old full name, and new
# full name.
#
my ($self, $id, $old, $new) = @_;
my @children = @{$self->children($id)};
my $new_escaped = $self->quote($new . '/');
my $old_offset = length($old) + 2;
my $set;
if (lc $self->{connect}->{driver} eq 'mysql') {
$set = "CONCAT($new_escaped, SUBSTRING(Full_Name, $old_offset))";
}
elsif (lc $self->{connect}->{driver} eq 'pg') {
$set = "$new_escaped || SUBSTRING(Full_Name, $old_offset)";
}
elsif (lc $self->{connect}->{driver} eq 'odbc' or lc $self->{connect}->{driver} eq 'mssql') {
$set = "$new_escaped + SUBSTRING(Full_Name, $old_offset, 255)";
}
elsif (lc $self->{connect}->{driver} eq 'oracle') {
$set = "$new_escaped || SUBSTR(Full_Name, $old_offset)";
}
if ($set) {
$self->update(
{ Full_Name => \$set },
{ ID => \@children },
{ GT_SQL_SKIP_CHECK => 1 }
);
}
else {
my $sth = $self->select(qw/ID Full_Name/ => { ID => \@children });
while (my ($id, $full_name) = $sth->fetchrow) {
$full_name =~ s/^\Q$old/$new/ or next;
$self->update({ Full_Name => $full_name }, { ID => $id }, { GT_SQL_SKIP_CHECK => 1 });
}
}
}
sub can_modify {
# -------------------------------------------------------------------
# Returns 1 if a record can be modified, undef otherwise.
#
my ($self, $new, $orig) = @_;
# If the FatherID has changed, make sure the new father exists. If it's 0, then
# it's the root category and we don't worry about it.
if ($orig->{FatherID} != $new->{FatherID} or $orig->{Name} ne $new->{Name}) {
if ($orig->{FatherID} != $new->{FatherID} and $new->{FatherID}) {
$self->count({ ID => $new->{FatherID} }) or return $self->error('BADCATID', 'WARN', $new->{FatherID});
}
# Now make sure the new FatherID,Name doesn't exist as it must be unique.
$self->count({ FatherID => $new->{FatherID}, Name => $new->{Name} }, GT::SQL::Condition->new(ID => '!=' => $orig->{ID})) and return $self->error('CATEXISTS', 'WARN', $new->{Name});
}
return 1;
}
sub template_set {
# -------------------------------------------------------------------
# Return the value of template set to use for a given category.
#
my $self = shift;
my $id = shift or return $self->error('BADARGS', 'FATAL', "Must pass category id to template_set");
return '' unless (exists $self->{schema}->{cols}->{Category_Template});
return $self->{_template_cache}->{$id} if (exists $self->{_template_cache}->{$id});
# If this category has a template set, use it.
my $cat_info = $self->select(Category_Template => { ID => $id })->fetchrow;
# Otherwise look at its parents.
unless ($cat_info) {
my $parents = $self->parents($id);
for my $parent (@$parents) {
$cat_info = $self->select(Category_Template => { ID => $parent })->fetchrow
and last;
}
}
$self->{_template_cache}->{$id} = $cat_info || '';
return $self->{_template_cache}->{$id};
}
sub parents {
# -----------------------------------------------------------------------------
# Returns parent ID's given one or more category ID's. If called with a single
# category ID, the return value is an array reference of the ID's of the
# category's parents, from father => root. If called with an array reference
# of category ID's, the return value is a hash reference of
# (ID => [rootid ... parentid]) pairs, with one pair for each category.
#
my $self = shift;
my $id = shift or return $self->error('BADARGS', 'FATAL', "No category id passed to parents");
my (%ret, @lookup);
for (ref $id ? @$id : $id) {
unless ($ret{$_} = $self->{_parent_cache}->{$_}) {
push @lookup, $_;
}
}
if (@lookup) {
my $parents = $self->tree->parent_ids(id => \@lookup, include_dist => 1);
for (@lookup) {
$ret{$_} = $self->{_parent_cache}->{$_} = [sort { $parents->{$_}->{$b} <=> $parents->{$_}->{$a} } keys %{$parents->{$_}}];
}
}
return ref $id
? \%ret
: [reverse @{$ret{$id}}];
}
sub children {
# -----------------------------------------------------------------------------
# Exactly like parents(), except you get descendants rather than ancestors, and
# you get them in shallowest => deepest.
#
my $self = shift;
my $id = shift or return $self->error('BADARGS', 'FATAL', "No category id passed to children");
my (%ret, @lookup);
for (ref $id ? @$id : $id) {
unless ($ret{$_} = $self->{_child_cache}->{$_}) {
push @lookup, $_;
}
}
if (@lookup) {
my $children = $self->tree->child_ids(id => \@lookup, include_dist => 1);
for (@lookup) {
$ret{$_} = $self->{_child_cache}->{$_} = [sort { $children->{$_}->{$a} <=> $children->{$_}->{$b} } keys %{$children->{$_}}];
}
}
return ref $id
? \%ret
: $ret{$id};
}
sub suggestions {
# -----------------------------------------------------------------------------
# Returns a list of suggested category names. Takes a name and optional limit.
#
my $self = shift;
my $name = shift;
$name =~ y/\r\n//d;
$name =~ /\S/ or return [];
$self->select_options('LIMIT 10');
return [$self->select(Full_Name => GT::SQL::Condition->new(Full_Name => LIKE => "%$name%"))->fetchall_list];
}
sub link_count {
# -----------------------------------------------------------------------------
# Change the Number_of_Links count by n for specified id, and all parents. You
# can pass multiple ID's by passing an array reference for ID. You can pass
# both multiple change values by passing a hash reference of (CHANGE => [ID,
# ...]) pairs as the ID (the change value passed to the function will be
# ignored). Note that Direct_Links counts are NOT changed.
#
my ($self, $id, $change) = @_;
my %id;
if (!$id or ref $id eq 'ARRAY' and !@$id) {
return;
}
elsif (ref $id eq 'HASH') {
%id = %$id;
}
else {
%id = ($change => ref $id ? $id : [$id]);
}
my %final;
while (my ($change, $id) = each %id) {
for (@$id) {
$final{$_} = ($final{$_} || 0) + $change;
}
my $parents = $self->tree->parent_ids(id => $id);
for my $parent (keys %$parents) {
for (@{$parents->{$parent}}) {
$final{$_} += $change;
}
}
}
my %change;
for (keys %final) {
push @{$change{$final{$_}}}, $_;
}
for (keys %change) {
$self->update(
{ Number_of_Links => \('Number_of_Links' . ($_ > 0 ? ' + ' : ' - ') . abs) },
{ ID => $change{$_} },
{ GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
);
}
}
sub changed {
# -------------------------------------------------------------------
# Returns a statement handle that can be looped through to get a list
# of changed categories.
#
Links::init_date();
my $self = shift;
my $date = GT::Date::date_get(defined $_[0] ? $_[0] : time);
my $sth = $self->select(GT::SQL::Condition->new(Timestmp => '>=' => $date ));
return $sth;
}
sub get_id_from_name {
# -------------------------------------------------------------------
# Returns the category id based on the name.
#
my ($self, $name) = @_;
$name =~ y/\r\n//d;
$name =~ /\S/ or return;
return $self->{_id_cache}->{$name} if exists $self->{_id_cache}->{$name};
$self->{_id_cache}->{$name} = $self->select(ID => { Full_Name => $name })->fetchrow_array;
return $self->{_id_cache}->{$name};
}
sub get_name_from_id {
# -------------------------------------------------------------------
# Returns the category full name based on the id.
#
my ($self, $id) = @_;
return $self->{_name_cache}->{$id} if exists $self->{_name_cache}->{$id};
return $self->{_name_cache}->{$id} = $self->select(Full_Name => { ID => $id })->fetchrow;
}
sub as_url {
# -------------------------------------------------------------------
#
my ($self, $name, $format) = @_;
return $PLG->dispatch('category_as_url', sub { return $self->_as_url(@_) }, $name, $format);
}
sub _as_url {
# -------------------------------------------------------------------
# Return the passed-in category name as a formatted category path, usable for
# static templates.
#
my ($self, $name, $format) = @_;
my $cat = $self->select({ Full_Name => $name })->fetchrow_hashref
or return $name;
require Links::Tools;
$format ||= $IN->param('d') ? $CFG->{build_category_dynamic} ? "%$CFG->{build_category_dynamic}%" : '' : $CFG->{build_category_format};
$format ||= '%Full_Name%';
if ($format eq '%Full_Name%' and ($IN->param('d') or $CFG->{build_format_compat})) {
# Old Links SQL's (prior to configurable category naming) didn't
# coalesce multiple _'s into a single _, and dynamic mode still depends
# on that behaviour - so if the format is just Full_Name, mimic the old
# behaviour.
(my $ret = $cat->{Full_Name}) =~ y|a-zA-Z0-9_/-|_|c;
return $ret;
}
if ($format =~ /%Full_ID%/) {
$cat->{Full_ID} = join '/', (@{$self->tree->parent_ids(id => $cat->{ID})}, $cat->{ID});
}
return Links::Tools::parse_format(
$format,
%$cat,
clean => 1
);
}
sub set_new {
# -------------------------------------------------------------------
# Sets the new flag for a given category id (or list).
#
my $self = shift;
my @ids = ref $_[0] eq 'ARRAY' ? @{shift()} : shift;
my $rel = $DB->table('Links', 'CatLinks', 'Category');
for my $id (@ids) {
my $parents = $self->parents($id);
my @pids = reverse @$parents;
push @pids, $id;
for my $pid (@pids) {
my $children = $self->children($pid);
$rel->select_options('GROUP BY Add_Date');
my $sth = $rel->select(qw/MAX(Add_Date) isNew/ => GT::SQL::Condition->new(
CategoryID => '=' => [$pid, @$children],
VIEWABLE
));
my ($newest, $new) = $sth->fetchrow;
$self->update(
{ Has_New_Links => $new || 'No', Newest_Link => $newest },
{ ID => $pid },
{ GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
);
}
}
}
sub _clear_cache {
# -------------------------------------------------------------------
# Clear out cache results whenever a category is added/deleted/changed.
#
my $self = shift;
delete @$self{qw{_parent_cache _child_cache _name_cache _id_cache _template_cache}};
return 1;
}
1;

View File

@ -0,0 +1,41 @@
# ==================================================================
# 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: ClickTrack.pm,v 1.3 2009/05/08 19:56:50 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# ClickTrack is subclassed so that new() is wrapped to handle ClickTrack table
# cleanups - but only the first time a ClickTrack table object is created, and
# only once / day.
package Links::Table::ClickTrack;
use strict;
use Links qw/$CFG %STASH/;
use GT::SQL::Table ();
use vars qw/@ISA/;
@ISA = 'GT::SQL::Table';
sub new {
my $self = shift->SUPER::new(@_) or return;
return $self if $STASH{clicktrack_cleanup}++;
Links::init_date();
my $cleanup_date = GT::Date::date_get(time - 2*24*60*60, '%yyyy%-%mm%-%dd%');
return $self if $CFG->{last_clicktrack_cleanup} and $cleanup_date eq $CFG->{last_clicktrack_cleanup};
$self->delete(GT::SQL::Condition->new(Created => '<' => $cleanup_date));
$CFG->{last_clicktrack_cleanup} = $cleanup_date;
$CFG->save;
$self;
}
1;

View File

@ -0,0 +1,630 @@
# ==================================================================
# 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: Links.pm,v 1.33 2009/05/11 05:57:45 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::Table::Links;
# ==================================================================
use strict;
use Links qw/:payment :objects/;
use GT::SQL;
use GT::SQL::Table;
use vars qw /@ISA $DEBUG $ERRORS $ERROR_MESSAGE $CATLINK/;
@ISA = qw/GT::SQL::Table/;
$DEBUG = 0;
$ERROR_MESSAGE = 'GT::SQL';
$ERRORS = {
NOCATEGORY => "You did not specify a category for this link.",
BADCATSUG => "There is no category with that name. Perhaps you meant: %s",
BADCATEGORY => "Invalid Category '%s', it does not exist.",
};
sub _query {
# -------------------------------------------------------------------
# Overrides the default query to allow searching on category values.
#
my $self = shift;
my $opts = $self->common_param(@_) or return $self->fatal(BADARGS => '$obj->insert(HASH or HASH_REF or CGI) only.');
# Parse date/time
if ($opts->{ExpiryDate} and $opts->{ExpiryDate} !~ /^\s*-?\d+\s*$/) {
my $converted = Links::date_to_time($opts->{ExpiryDate});
$opts->{ExpiryDate} = $converted if defined $converted;
}
my $cat_id = $opts->{'CatLinks.CategoryID'} or return $self->SUPER::_query($opts);
$cat_id = $self->clean_category_ids($cat_id) or return;
# Strip out values that are empty or blank (as query is generally
# derived from cgi input).
my %input = map { $_ => $opts->{$_} } grep { defined $opts->{$_} and $opts->{$_} !~ /^\s*$/ } keys %$opts;
$opts = \%input;
# Create a CatLinks,Links table to do the search.
my $db = $DB->table('CatLinks','Links');
# Now start handling the search
my $cond = $self->build_query_cond($opts, $self->{schema}->{cols});
if ( (ref $cond) =~ /::sth/i ) {
return $cond;
}
# Set the limit clause, defaults to 25, set to -1 for none.
my $in = $self->_get_search_opts($opts);
my $offset = ($in->{nh} - 1) * $in->{mh};
$db->select_options("ORDER BY $in->{sb} $in->{so}") if $in->{sb};
$db->select_options("LIMIT $in->{mh} OFFSET $offset") unless $in->{mh} == -1;
# Add to the condition the category clause.
my $final = new GT::SQL::Condition;
$final->add($cond) if $cond;
$final->add('CatLinks.CategoryID', 'IN', $cat_id);
# Now do the select.
my @sel;
push @sel, $final if $final;
push @sel, $opts->{rs} if $opts->{rs} and $final;
my $sth = $db->select(@sel) or return;
$self->{last_hits} = $db->hits;
return $sth;
}
sub add {
# -------------------------------------------------------------------
# Adds a link, but passes through Plugins::Dispatch.
#
my $self = shift;
my $p = (ref $_[0] eq 'HASH') ? shift : {@_};
$PLG->dispatch('add_link', sub { $self->_plg_add(@_) }, $p);
}
sub _plg_add {
# -------------------------------------------------------------------
# Add a link.
#
my ($self, $p) = @_;
# Check to see if we can add a link, all errors get cascaded back.
$p->{'CatLinks.CategoryID'} or return $self->warn('NOCATEGORY');
$p->{'CatLinks.CategoryID'} = $self->clean_category_ids($p->{'CatLinks.CategoryID'}) or return;
$self->set_date_flags($p);
my $counted = ($p->{isValidated} eq 'Yes' and $p->{ExpiryDate} >= time);
if ($p->{ExpiryDate} >= time) {
$p->{ExpiryCounted} = 0;
}
else {
$p->{ExpiryCounted} = 1;
}
# Add the link, and return if there was an error, the error is propogated back.
my $id = $self->SUPER::add($p) or return;
# Now add all the categories that the link belongs too.
my $cat = $DB->table('Category');
my $cat_lnk = $DB->table('CatLinks');
my @cat_ids = ref $p->{'CatLinks.CategoryID'} ? @{$p->{'CatLinks.CategoryID'}} : $p->{'CatLinks.CategoryID'};
my %parents;
# Get a list of all the parents that this will affect.
foreach my $cat_id (@cat_ids) {
$cat_lnk->insert({ LinkID => $id, CategoryID => $cat_id }) or return;
if ($counted) {
for (@{$cat->parents($cat_id)}) { $parents{$_}++ }
$parents{$cat_id}++;
}
}
# Now update those categories.
if ($counted) {
$cat->update(
{ Newest_Link => $p->{Add_Date}, Has_New_Links => 'Yes', Number_of_Links => \"Number_of_Links + 1" },
{ ID => [keys %parents] },
{ GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
);
$cat->update({ Direct_Links => \"Direct_Links + 1" }, { ID => \@cat_ids });
}
return $id;
}
sub delete {
# -----------------------------------------------------------------------------
# Deletes one or more links; there is a 'delete_link' hook below that can be
# used by plugins.
#
my ($self, $where) = @_;
if (not ref $where or ref $where eq 'ARRAY') {
$where = { ID => $where };
}
return $self->fatal(BADARGS => 'Usage: $links->delete(condition)')
unless (ref $where eq 'HASH' and keys %$where) or (UNIVERSAL::isa($where, 'GT::SQL::Condition') and $where->sql);
my $CatLinks = $DB->table('CatLinks');
# Sometimes { ID => x, CatLinks.CategoryID => y } gets passed in; it is
# wrong - CatLinks->delete should be used instead, which will recall this
# subroutine if any links need to be deleted.
if (ref $where eq 'HASH' and $where->{ID} and not ref $where->{ID}
and $where->{'CatLinks.CategoryID'} and not ref $where->{'CatLinks.CategoryID'}) {
return $CatLinks->delete({ LinkID => $where->{ID}, CategoryID => $where->{'CatLinks.CategoryID'} });
}
# Delete called with a normal condition
my $links = $self->select(qw/ID isValidated Add_Date ExpiryDate ExpiryCounted/ => $where)->fetchall_hashref;
@$links or return '0 but true';
my $new_cutoff = GT::Date::timelocal(0, 0, 0, (localtime time - $CFG->{build_new_cutoff})[3 .. 5]);
my (@counts, @new);
for (@$links) {
my $add_time = GT::Date::timelocal(GT::Date::parse_format($_->{Add_Date}, GT::Date::FORMAT_DATE));
if ($_->{isValidated} eq 'Yes' and ($_->{ExpiryDate} >= time or not $_->{ExpiryCounted})) {
push @counts, $_->{ID};
push @new, $_->{ID} if $add_time >= $new_cutoff;
}
}
# Figure out how much each category needs to be decremented
$CatLinks->select_options("GROUP BY CategoryID");
my %cats = $CatLinks->select(CategoryID => 'COUNT(*)' => { LinkID => \@counts })->fetchall_list;
my %change;
while (my ($catid, $count) = each %cats) {
push @{$change{-$count}}, $catid;
}
my $ret;
{
# CatLinks, which has an fk to Links.ID, needs to know what we're
# deleting so that it doesn't try to recall Links->delete
local @Links::Table::CatLinks::DELETING;
if ($PLG->active_plugins('delete_link')) {
for (@$links) {
@Links::Table::CatLinks::DELETING = $_->{ID};
my $r = $PLG->dispatch('delete_link', sub { return $self->_plg_delete_link(@_) }, { ID => $_->{ID} });
$ret += $r if defined $r;
}
$ret = '0 but true' if defined $ret and $ret == 0;
}
else {
# delete_link plugin hook isn't being used, a single delete will do it
my @lids = map $_->{ID}, @$links;
@Links::Table::CatLinks::DELETING = @lids;
$ret = $self->SUPER::delete({ ID => \@lids });
}
}
my $Category = $DB->table('Category');
$Category->link_count(\%change);
while (my ($change, $ids) = each %change) {
$Category->update({ Direct_Links => \("Direct_Links - " . abs $change) }, { ID => $ids });
}
$CatLinks->select_options("GROUP BY CategoryID");
my @new_cats = $CatLinks->select(CategoryID => { LinkID => \@new })->fetchall_list;
# Now reset new flags on categories.
if ($ret and @new_cats) {
$Category->set_new(\@new_cats);
}
return $ret;
}
sub _plg_delete_link {
# -----------------------------------------------------------------------------
# Deletes a single link ID (plugin hook 'delete_link'. The second argument,
# $link, will, for historic reasons, always be a hash reference containing an
# 'ID' key, the value of which is the ID of the link to be deleted.
#
my ($self, $link) = @_;
my $link_id = $link->{ID};
return $self->SUPER::delete({ ID => $link_id });
}
sub modify {
# -------------------------------------------------------------------
# Modifies a link, but passes through the plugin system.
#
my ($self, $link) = @_;
$PLG->dispatch('modify_link', sub { return $self->_plg_modify(@_) }, $link);
}
sub _plg_modify {
# -------------------------------------------------------------------
# Modify a single link.
#
my $self = shift;
my $set = shift or return $self->fatal(BADARGS => "Usage: \$cat->modify( { col => value ... } ).");
my $id = $set->{ID} or return $self->fatal(BADARGS => "No primary key passed to modify!");
# Let's set the changed date to right now.
Links::init_date();
$set->{Mod_Date} = GT::Date::date_get();
# Force it to uncounted so that category counts will be properly updated
$set->{ExpiryCounted} = 0;
# Check to see if we can modify (makes sure valid category id's were set).
$set->{'CatLinks.CategoryID'} or return $self->warn('NOCATEGORY');
$set->{'CatLinks.CategoryID'} = $self->clean_category_ids($set->{'CatLinks.CategoryID'}) or return;
$self->set_date_flags($set);
# Check to see if we are changing from not validated => validated.
my ($old_validated, $old_expiry) = $self->select(qw/isValidated ExpiryDate/ => { ID => $set->{ID} })->fetchrow;
# Check that the ExpiryDate is valid for the categories the link is in.
require Links::Payment;
my $expiry = (exists $set->{ExpiryDate} and $set->{ExpiryDate}) ? $set->{ExpiryDate} : $old_expiry;
$expiry = Links::Payment::check_expiry_date({ ExpiryDate => $expiry }, $set->{'CatLinks.CategoryID'});
$set->{ExpiryDate} = $expiry if $expiry;
my $new_validated = exists $set->{isValidated} ? $set->{isValidated} : $old_validated;
my $new_expiry = exists $set->{ExpiryDate} ? $set->{ExpiryDate} : $old_expiry;
my $was_counted = $old_validated eq 'Yes' && $old_expiry >= time;
my $now_counted = $new_validated eq 'Yes' && $new_expiry >= time;
if (exists $set->{ExpiryDate}) {
$set->{ExpiryCounted} = $set->{ExpiryDate} >= time ? 0 : 1;
}
=for comment
Here are the various cases that the category count update code needs to handle and what to do in those cases:
add the link to a new category
was counted, now_counted increment new cat
!was counted, now counted increment new cat
was counted, !now counted nothing
!was counted, !now counted nothing
remove the link from a category
was counted, now_counted decrement old cat (CatLinks handles correctly)
!was counted, now counted nothing (CatLinks handles incorrectly and decrements in some cases, we fix and increment)
was counted, !now counted decrement old cat (CatLinks handles correctly)
!was counted, !now counted nothing (CatLinks handles correctly)
no category changes
was counted, now_counted nothing
!was counted, now counted increment cats
was counted, !now counted decrement cats
!was counted, !now counted nothing
the above combined (what the code needs to do)
was counted, now_counted increment new cats
!was counted, now counted increment curr cats, leave removed cats
was counted, !now counted decrement cats except removed and new cats (ie. decrement curr cats, except new cats)
!was counted, !now counted nothing
=cut
# Do the update.
my $ret = $self->SUPER::modify($set);
# Check to see if the link has been moved into another category.
if ($ret) {
my $cat_lnk = $DB->table('CatLinks');
my %orig_ids = map { $_ => 1 } $cat_lnk->select(CategoryID => { LinkID => $id })->fetchall_list;
my %cat_ids = map { $_ => 1 } ref $set->{'CatLinks.CategoryID'} ? @{$set->{'CatLinks.CategoryID'}} : $set->{'CatLinks.CategoryID'};
# Categories that the link has just been added to
my @new_cats = grep !$orig_ids{$_}, keys %cat_ids;
# Categories that the link has just been removed from
my @old_cats = grep !$cat_ids{$_}, keys %orig_ids;
my %link_adjustment;
my $Category = $DB->table('Category');
# CatLinks doesn't update category counts on insert, so it's done further down in the code
if (@new_cats) {
$cat_lnk->insert_multiple([qw/LinkID CategoryID/], map [$id, $_], @new_cats);
}
# However, deleting from CatLinks does result in updated category counts
if (@old_cats) {
$cat_lnk->delete({ LinkID => $id, CategoryID => \@old_cats });
# If the link has been modified from isValidated = No to Yes then the delete()
# from CatLinks will end up incorrectly decrementing the category count. If
# this is the case, then the count needs to increment to comphensate for this
# bug. This isn't !$was_counted && $now_counted because CatLinks delete
# currently does not take ExpiryDate into consideration.
push @{$link_adjustment{1}}, @old_cats if $old_validated eq 'No' and $new_validated eq 'Yes';
}
# The status hasn't changed: increment the new categories
if ($was_counted and $now_counted) {
push @{$link_adjustment{1}}, @new_cats if @new_cats;
}
# It wasn't viewable, but is now: increment all the current categories
elsif (not $was_counted and $now_counted) {
push @{$link_adjustment{1}}, keys %cat_ids;
}
# Was viewable, but now isn't: decrement all the current categories (except new ones)
elsif ($was_counted and not $now_counted) {
# Don't decrement counts on new categories, since the addition of the link
# never incremented the count in the first place
my %not_new = %cat_ids;
for (@new_cats) {
delete $not_new{$_};
}
push @{$link_adjustment{-1}}, keys %not_new;
}
# Otherwise, it wasn't visible and still isn't, or it was visible but now
# isn't. In both cases, the new categories don't need to be incremented.
# Actually adjust the link counts:
$Category->link_count(\%link_adjustment);
while (my ($change, $ids) = each %link_adjustment) {
$Category->update({ Direct_Links => \("Direct_Links" . ($change > 0 ? ' + ' : ' - ') . abs $change) }, { ID => $ids });
}
# If this link is now validated this link, let's update category counters and new flags.
# It also needs to be updated if a link has been added to new categories.
if ((not $was_counted and $now_counted) or @new_cats) {
foreach my $cat (keys %cat_ids) {
my @cats = ($cat, @{$Category->parents($cat)});
my $cond = GT::SQL::Condition->new(ID => '=', \@cats);
if ($set->{isNew} eq 'Yes') {
$Category->update({ Has_New_Links => 'Yes' }, $cond, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
}
$cond->add('Newest_Link', '<', $set->{Add_Date});
$Category->update({ Newest_Link => $set->{Add_Date} }, $cond, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
}
}
# Update the category timestamps to let people know that the page has changed.
$Category->update({ Timestmp => \"NOW()" }, { ID => [keys %cat_ids, @old_cats] }, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
}
return $ret;
}
sub update {
# -------------------------------------------------------------------
# Update a link.
#
my ($self, $set, $where) = @_;
my $ret = $self->SUPER::update($set, $where);
# Update the Category Timestmp of links which have certain columns updated
for (split(/\s*,\s*/, $CFG->{links_cols_update_category})) {
if (exists $set->{$_}) {
my @cats = $DB->table('Links', 'CatLinks')->select('CategoryID', $where)->fetchall_list;
$DB->table('Category')->update({ Timestmp => \'NOW()' }, { ID => \@cats });
last;
}
}
return $ret;
}
sub detailed_url {
# -----------------------------------------------------------------------------
# Takes one or more link ID's, returns one or more parsed detailed URL/paths in
# the same order and position the links were passed in, NOT prefixed with
# build_detail_url/build_detail_path. If the ID passed in is actually a
# hashref, it is assumed that this hash ref includes a full set of Links and
# Category values for the link.
#
my ($self, @ids) = @_;
my (@links, @sel_links, $need_select);
for (@ids) {
if (ref) {
push @links, $_;
push @sel_links, undef;
}
else {
push @links, undef;
push @sel_links, $_;
$need_select++;
}
}
if ($need_select) {
my %links_cols = %{$self->cols};
# Only select Category columns that don't conflict with Links columns.
my @cat_cols = grep !$links_cols{$_}, keys %{$DB->table('Category')->cols};
my $rel = $DB->table(qw/Links CatLinks Category/);
my %links = map { $_->{ID} => $_ } @{$rel->select(
'Links.*', @cat_cols, 'CategoryID', { LinkID => [grep $_, @sel_links] }
)->fetchall_hashref};
for my $i (0 .. $#sel_links) {
$links[$i] = $links{$sel_links[$i]} if $sel_links[$i];
}
}
require Links::Tools;
my $format;
$format = $CFG->{build_detail_format} unless $IN->param('d');
$format ||= '%ID%';
$format .= '_%ID%' unless $format =~ /%ID%/;
my @ret = $PLG->dispatch('detailed_url', sub {
my ($format, @links) = @_;
my @ret;
for (@links) {
my $parsed;
if ($_) {
# Make Full_Name act the same for both category and detailed urls. Set
# build_format_compat = 2 if you want the < 3.3 behaviour of coalesced _'s for
# Full_Name.
if ($CFG->{build_format_compat} == 1) {
(my $fn = $_->{Full_Name}) =~ y|a-zA-Z0-9_/-|_|c;
$format =~ s/%Full_Name%/$fn/g;
}
$parsed = Links::Tools::parse_format(
$format,
%$_,
clean => 1
);
$parsed =~ s{(^|[/\\])index$}{${1}_index};
$parsed .= $CFG->{build_extension};
}
push @ret, $parsed;
}
return @ret;
}, $format, @links);
return wantarray ? @ret : $ret[0];
}
sub category_detailed_url {
# -----------------------------------------------------------------------------
# A wrapper to detailed_url which will return url's which given a category id,
# will only return url's which take the category into consideration. The only
# use for this is when a link is in multiple categories.
#
my ($self, $cat_id, @ids) = @_;
my %links_cols = %{$self->cols};
# Only select Category columns that don't conflict with Links columns.
my @cat_cols = grep !$links_cols{$_}, keys %{$DB->table('Category')->cols};
my @links;
my $rel = $DB->table(qw/Links CatLinks Category/);
for (@ids) {
push @links, $rel->select('Links.*', @cat_cols, 'CategoryID', { LinkID => $_, CategoryID => $cat_id })->fetchrow_hashref;
}
my @ret = $self->detailed_url(@links);
return wantarray ? @ret : $ret[0];
}
sub clean_category_ids {
# -------------------------------------------------------------------
# Takes an argument which could be a list of category names or ids
# and returns an array ref of ids.
#
my ($self, $arg) = @_;
my $cat = $DB->table('Category');
# Fix up Category Names => Id numbers and offer suggestions
# if name was not found.
if (! ref $arg and $arg !~ /^\d*$/) {
my @cat_names = split /\n\r?/, $arg;
my @cat_ids = ();
foreach my $name (@cat_names) {
$name =~ s/[\r\n]//g; # Textareas have a nasty habit of putting \r's on the results.
my $id = ($name =~ /^\d+$/) ? $name : $cat->get_id_from_name($name);
if ($id) {
push(@cat_ids, $id);
}
else {
my $names = $cat->suggestions($name);
return $self->error(@$names
? ('BADCATSUG', 'WARN', "<ul>" . join('', map "<li>$_</li>", @$names) . "</ul>")
: ('BADCATEGORY', 'WARN', $name)
);
}
}
return \@cat_ids;
}
# We assume that if ID numbers are passed in, that they will
# be correct. This will get checked anyways by GT::SQL::Table,
# so no point doing it twice.
else {
my @ids = ref $arg ? @$arg : ($arg);
return \@ids;
}
}
sub get_categories {
# -------------------------------------------------------------------
# Takes a link id and returns a hash of category id => category name.
#
my $self = shift;
my $id = shift;
my $db = $DB->table('Category', 'CatLinks');
my $sth = $db->select( { 'CatLinks.LinkID' => $id }, [ 'Category.ID', 'Category.Full_Name' ] );
my %res = ();
while (my ($id, $name) = $sth->fetchrow_array) {
$res{$id} = $name;
}
return \%res;
}
sub set_date_flags {
# -------------------------------------------------------------------
# Takes a link hash ref and sets the date flags properly.
#
my ($self, $p) = @_;
Links::init_date();
my $today = GT::Date::date_get();
if (GT::Date::date_diff($today, $p->{Add_Date}) <= $CFG->{build_new_cutoff}) {
$p->{isNew} = 'Yes';
$p->{isChanged} = 'No';
}
elsif (GT::Date::date_diff($today, $p->{Mod_Date}) <= $CFG->{build_new_cutoff}) {
$p->{isChanged} = 'Yes';
$p->{isNew} = 'No';
}
else {
$p->{isNew} = 'No';
$p->{isChanged} = 'No';
}
# Possible ExpiryDate values that have to be handled here:
# -1 (unpaid link) - leave it as is, does not need to be converted
# \d (unixtime) - leave it as is, does not need to be converted
# >=\d (doesn't actually occur here, but in _query) - leave it as is, does not need to be converted
# YYYY-MM-DD
# YYYY/MM/DD
# YYYY/MM/DD HH::MM::SS
# The purpose of this bit of code is to convert any human readable dates into
# unixtime and leave everything else as is.
if ($p->{ExpiryDate} and $p->{ExpiryDate} !~ /^\s*-?\d+\s*$/) {
my $converted = Links::date_to_time($p->{ExpiryDate});
$p->{ExpiryDate} = $converted if defined $converted;
}
}
sub add_reviews {
# -------------------------------------------------------------------
# Adds review information, but passes through the plugin system.
#
my ($self, $link) = @_;
$PLG->dispatch('add_reviews', sub { return $self->_plg_add_reviews(@_) }, $link);
}
sub _plg_add_reviews {
# -------------------------------------------------------------------
# Adds review information to an array ref of hash refs of links passed in
# in one query.
#
my $self = shift;
my $links = shift;
if (ref $links eq 'HASH') {
$links = [ $links ];
}
my $review_db = $DB->table('Reviews');
my @ids = map { $_->{ID} } @$links;
return unless (@ids);
my $sth = $review_db->select({ Review_Validated => 'Yes' }, { Review_LinkID => \@ids });
my %reviews;
my %review_count;
while (my $review = $sth->fetchrow_hashref) {
push @{$reviews{$review->{Review_LinkID}}}, $review;
$review_count{$review->{Review_LinkID}}++;
}
for my $link (@$links) {
$link->{Review_Count} = $review_count{$link->{ID}};
$link->{Review_Loop} = $reviews{$link->{ID}};
}
return $links;
}
1;

View File

@ -0,0 +1,93 @@
# ==================================================================
# 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: Reviews.pm,v 1.1 2007/11/16 07:15:00 brewt Exp $
#
# Copyright (c) 2007 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::Table::Reviews;
# ==================================================================
use strict;
use Links qw/:objects/;
use GT::SQL;
use GT::SQL::Table;
use vars qw/@ISA $ERROR_MESSAGE/;
@ISA = qw/GT::SQL::Table/;
$ERROR_MESSAGE = 'GT::SQL';
sub add {
# -----------------------------------------------------------------------------
# Add a review.
#
my $self = shift;
my $rec = (ref $_[0] eq 'HASH') ? shift : { @_ };
my $id = $self->SUPER::add($rec) or return;
# Update the link/category timestamp if the review is validated.
_update_timestamp($rec->{Review_LinkID}) if $rec->{Review_Validated} eq 'Yes';
$id;
}
sub modify {
# -----------------------------------------------------------------------------
# Modify a review.
#
my $self = shift;
my $set = shift or return $self->fatal(BADARGS => 'Usage: $reviews->modify({ col => value ... }).');
my $id = $set->{ReviewID} or return $self->fatal(BADARGS => 'No primary key passed to modify!');
my ($old, $link_id) = $self->select('Review_Validated', 'Review_LinkID', { ReviewID => $id })->fetchrow;
my $ret = $self->SUPER::modify($set) or return;
# Only update the timestamp if it was unvalidated and still is - this is the
# only case where the pages shouldn't be rebuilt.
my $new = $set->{Review_Validated} || $old;
_update_timestamp($link_id) unless $old eq 'No' and $new eq 'No';
$ret;
}
sub delete {
# -----------------------------------------------------------------------------
# Delete one or more reviews.
#
my ($self, $cond) = @_;
ref $cond or return $self->fatal(BADARGS => '$reviews->delete(condition)');
# Get the link ids of the reviews that are about to be deleted and are
# validated (as only those pages need to be rebuilt).
my @link_ids = $self->select('Review_LinkID', $cond, { Review_Validated => 'Yes' })->fetchall_list;
my $ret = $self->SUPER::delete($cond) or return;
_update_timestamp(\@link_ids) if @link_ids;
$ret;
}
sub _update_timestamp {
# -----------------------------------------------------------------------------
# Given a link ID (or an array ref if you want to update more than one link),
# update the Timestmp columns of the link as well as all the categories that
# the link is in. This ensures that these pages will be rebuilt on "Build
# Changed".
#
my $link_id = shift;
return unless $link_id;
my @cats = $DB->table('Links', 'CatLinks')->select('CategoryID', { LinkID => $link_id })->fetchall_list;
$DB->table('Category')->update({ Timestmp => \'NOW()' }, { ID => \@cats }) if @cats;
$DB->table('Links')->update({ Timestmp => \'NOW()' }, { ID => $link_id });
}
1;

View File

@ -0,0 +1,162 @@
# ==================================================================
# 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: Users.pm,v 1.5 2005/05/12 20:51:24 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::Table::Users;
# ==================================================================
use strict;
use GT::SQL;
use GT::SQL::Table;
use Links qw/$CFG $PLG/;
use vars qw/@ISA $ERRORS $ERROR_MESSAGE $AUTH/;
@ISA = qw/GT::SQL::Table/;
$ERROR_MESSAGE = 'GT::SQL';
$ERRORS = {
AUTHERROR => "Authentication Error: %s",
INVALIDFORMAT => "Invalid format for username: %s"
};
sub init {
# -------------------------------------------------------------------
# Load the authentication module.
#
require Links::Authenticate;
Links::Authenticate::auth('init', {});
return 1;
}
sub add {
# -------------------------------------------------------------------
my ($self, @args) = @_;
return $PLG->dispatch('add_user', sub { return $self->_plg_add(@_); }, @args );
}
sub _plg_add {
# -------------------------------------------------------------------
init();
my $self = shift;
my $p = ref $_[0] eq 'HASH' ? shift : {@_};
if (! Links::Authenticate::auth('valid_format', { Username => $p->{Username} })) {
$ERRORS->{INVALIDFORMAT} = Links::language('USER_INVALIDUSERNAME');
return $self->error('INVALIDFORMAT', 'WARN', $p->{Username});
}
my $h = Links::Authenticate::auth('add_user', { Username => $p->{Username}, Password => $p->{Password} });
unless ($h) {
$ERRORS->{AUTHERROR} = Links::language('USER_AUTHERROR');
return $self->error('AUTHERROR', 'WARN', $Links::Authenticate::error);
}
$p->{Username} = $h->{Username};
$p->{Password} = $h->{Password};
return $self->SUPER::add($p);
}
sub delete {
# -------------------------------------------------------------------
my ($self, @args) = @_;
return $PLG->dispatch('delete_user', sub { return $self->_plg_delete(@_); }, @args );
}
sub _plg_delete {
# -------------------------------------------------------------------
init();
my ($self, $cond) = @_;
if (! ref $cond) {
$cond = { Username => $cond };
}
my $count = 0;
my $link_db = $Links::DB->table('Links');
my $sth = $self->select('Username', $cond);
while (my ($user) = $sth->fetchrow_array) {
my @links = $link_db->select('ID', { LinkOwner => $user })->fetchall_list;
for my $link_id (@links) {
$link_db->delete($link_id);
}
if (Links::Authenticate::auth('del_user', { Username => $user })) {
my $ret = $self->SUPER::delete($user);
$count++ if $ret;
}
}
return $count;
}
sub modify {
# -------------------------------------------------------------------
my ($self, @args) = @_;
return $PLG->dispatch('modify_user', sub { return $self->_plg_modify(@_); }, @args );
}
sub _plg_modify {
# -------------------------------------------------------------------
init();
my $self = shift;
my $input = $self->common_param(@_) or return $self->error('BADARGS', 'FATAL', '$obj->insert(HASH or HASH_REF or CGI) only.');
my $id = $input->{Username} or return $self->error("BADARGS", "FATAL", "No primary key passed to modify!");
my $sth = $self->select('Username', 'Password', { Username => $id });
my $rec = $sth->fetchrow_hashref;
if ($rec) {
if ($input->{Password} ne $rec->{Password}) {
Links::Authenticate::auth('change_pass', { Username => $rec->{Username}, Password => $rec->{Password}, New_Password => $input->{Password} })
or return $self->error('AUTHERROR', 'WARN', $Links::Authenticate::error);
}
}
# Connect to the database if we are not already connected
$self->connect;
# Copy the data and remove anything that doesn't make sense here.
my $c = $self->{schema}->{cols};
my $set = {};
for (keys %$c) {
$set->{$_} = $input->{$_} if exists $input->{$_};
}
# Remove primary keys from update clause.
my $where;
if ($input->{orig_username}) {
$where->{Username} = $input->{orig_username};
}
else {
foreach my $key (@{$self->{schema}->{pk}}) {
$where->{$key} = delete $set->{$key} if exists $set->{$key};
}
}
return $self->error("NOPKTOMOD", "WARN") unless keys %$where == @{$self->{schema}->{pk}};
# Remove timestamps - no sense updating.
$self->_check_timestamp($where, $set) or return;
foreach my $col (keys %$c) {
delete $set->{$col} if $c->{$col}->{type} eq 'TIMESTAMP';
}
# Execute the update
$self->update($set, $where) or return;
return 1;
}
sub random_pass {
# -------------------------------------------------------------------
# Returns a random password.
#
my $self = shift;
my $pass = '';
for (1 .. 8) { $pass .= chr(65 + int rand 57); }
return $pass;
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,287 @@
# ==================================================================
# 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: Update.pm,v 1.11 2009/05/08 19:56:50 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
package Links::Update;
use strict;
use Links qw/$CFG $IN %STASH/;
use GT::Update qw/:severity/;
use GT::File::Tools qw/basename/;
use GT::Config;
use constant CACHE_TIMEOUT => 5*60; # Only check the server at most once every 5 minutes
sub _updater {
$STASH{updates} ||= GT::Config->load("$CFG->{admin_root_path}/Links/Config/Updates.pm", { debug => $CFG->{debug_level} });
return $STASH{updater} if $STASH{updater};
(my $cgi_path = $CFG->{admin_root_path}) =~ s{[\\/]+admin[\\/]*$}//;
$STASH{updater} = GT::Update->new(
product => 'Links',
version => $CFG->{version},
reg_number => $CFG->{reg_number},
init_path => $CFG->{admin_root_path},
perl_path => $CFG->{path_to_perl},
backup_path => "$CFG->{admin_root_path}/updates",
paths => {
script => {
cgi => $cgi_path,
admin => $CFG->{admin_root_path}
},
library => $CFG->{admin_root_path},
template => $CFG->{admin_root_path} . '/templates',
static => {
static => $CFG->{build_static_path},
},
fixed => {
static => $CFG->{build_static_path},
cool => $CFG->{build_cool_path},
detail => $CFG->{build_detail_path},
new => $CFG->{build_new_path},
ratings => $CFG->{build_ratings_path},
build => $CFG->{build_root_path},
},
version => $CFG->{admin_root_path}
},
replacements => {
library => {
'' => {
'Links.pm' => {
'<%VERSION%>' => $CFG->{version}
}
}
}
},
installed => ($STASH{updates}->{installed} ||= {}),
testing => $STASH{updates}->{testing}
);
}
sub check {
my $updater = _updater;
my ($cached, @updates);
if (my $cache = $STASH{updates}->{cache} and !$STASH{updates}->{testing}) {
if ($cache->{version} == $GT::Update::VERSION and $cache->{time} > time - CACHE_TIMEOUT) { # Only check at most once every 5 minutes
@updates = @{$cache->{updates}};
$cached = 1;
}
}
unless ($cached) {
@updates = $updater->check;
if (@updates == 1 and not defined $updates[0]) {
my $error = $updater->error;
my ($error_code, $error_message) = $error =~ /error code: (\d{3})\s*(.*)/;
return { error => $error, update_error_code => $error_code, update_error_message => $error_message };
}
$STASH{updates}->{cache} = { time => time, version => $GT::Update::VERSION, updates => \@updates };
$STASH{updates}->save;
}
my %ret;
my %available = map { $_->id => $_ } @updates;
for my $update (@updates) {
my $id = $update->id;
my $severity = $update->severity;
my $update_type = $severity == CRITICAL ? 'critical' : $severity == RECOMMENDED ? 'recommended' : $severity == VERSION ? 'version' : 'optional';
my $info = {
id => $id,
title => $update->title,
description => \($update->description),
severity => $severity,
files => [$update->files],
reversible => $update->reversible,
unique => $update->unique,
deps => [$update->deps],
revdeps => [$update->revdeps],
requires => [$update->requires],
deps_first => $update->deps_first,
update_type => $update_type,
installed => $update->installed
};
push @{$ret{$update_type}}, $info;
$ret{update}->{$id} = $info;
}
for (sort { $a <=> $b } keys %{$STASH{updates}->{installed}->{$CFG->{version}}}) {
next if $available{$_};
my %info = %{$STASH{updates}->{installed}->{$CFG->{version}}->{$_}};
$info{id} = $_;
my $severity = $info{severity};
my $update_type = $severity == CRITICAL ? 'critical' : $severity == RECOMMENDED ? 'recommended' : $severity == VERSION ? 'version' : 'optional';
push @{$ret{$update_type}}, \%info;
}
for (qw/critical recommended optional version/) {
$ret{"${_}_total"} = @{$ret{$_} ||= []};
$ret{"${_}_installed"} = $ret{"${_}_installable"} = 0;
for my $update (@{$ret{$_}}) {
next unless $available{$update->{id}};
if ($available{$update->{id}}->{installed}) {
$ret{"${_}_installed"}++;
}
elsif (!$available{$update->{id}}->{impossible}) {
$ret{"${_}_installable"}++;
}
}
push @{$ret{update_types}}, { update_type => $_, updates => $ret{$_} };
}
my @historic = sort { _numeric_version($a) <=> _numeric_version($b) } keys %{$STASH{updates}->{installed}};
$ret{historic} = \@historic;
\%ret;
}
sub check_historic {
my $updater = _updater;
my $version = shift || $CFG->{version};
my @updates = $updater->check($version);
my %ret = (historic_version => $version, current_version => $CFG->{version});
for (@updates) {
my @files = $_->files;
my $severity = $_->severity;
my $update_type = $severity == CRITICAL ? 'critical' : $severity == RECOMMENDED ? 'recommended' : $severity == VERSION ? 'version' : 'optional';
my $id = $_->id;
my %info = (
id => $id,
title => $_->title,
description => \($_->description),
severity => $severity,
files => \@files,
reversible => ($version eq $CFG->{version} ? $_->reversible : 0),
unique => $_->unique,
deps => [$_->deps],
revdeps => [$_->revdeps],
requires => [$_->requires],
revdeps_first => $_->revdeps_first,
update_type => $update_type,
installed => $_->installed
);
push @{$ret{$update_type}}, \%info;
$ret{update}->{$id} = \%info;
}
for (qw/critical recommended optional version/) {
push @{$ret{update_types}}, { update_type => $_, updates => $ret{$_} };
}
my @historic = sort { _numeric_version($a) <=> _numeric_version($b) } grep keys %{$STASH{updates}->{installed}->{$_}}, keys %{$STASH{updates}->{installed}};
$ret{historic} = \@historic;
\%ret;
}
# Takes a version such as 1.3.7 and converts it to 1.0307.
sub _numeric_version {
my @v = split /\./, (shift =~ /^(\d+(?:\.\d+)*)/)[0];
my $numeric = 0;
for (0 .. $#v) { $numeric += $v[$_] * 100**-$_ }
$numeric;
}
sub browser_install {
my @updates = $IN->param('install');
my ($status, $errors) = install(@updates);
if (!$status) {
$errors->{updates_selected} = \@updates;
return $errors;
}
my %ret = (update_success => 1, update_status => $status, updates_selected => []);
if ($status == 2) {
my $id = $errors;
my $path;
for (@{$STASH{updates}->{installed}->{$CFG->{version}}->{$updates[0]}->{files}}) {
if (basename($_->{file}) eq 'install.cgi') {
$path = $_->{file} . "?upgrade_choice=Yes;install_dir=" . $IN->escape($CFG->{admin_root_path});
last;
}
}
$ret{continue_url} = $path;
}
return \%ret;
}
# Installs updates passed in. Returns (0, \%error_hash) on failure, 1 on
# success of normal updates, (2, $id) on the success of version upgrade files.
sub install {
my @updates = @_;
my $updater = _updater;
my $v = $updater->verify(@updates);
return 0, { %$v, update_failed => 1, verify_failed => 1 } if ref $v eq 'HASH';
@updates = @$v;
my $success = $updater->install_verified(@updates);
if (!$success) {
my $error = $updater->error;
return 0, { update_failed => 1, error => "Update failed: $error" };
}
$STASH{updates}->{installed} = { $updater->installed };
delete $STASH{updates}->{cache};
$STASH{updates}->save;
if (@updates == 1 and $STASH{updates}->{installed}->{$CFG->{version}}->{$updates[0]}->{severity} == VERSION) {
# We just installed a version upgrade
return (2, $updates[0]);
}
return 1;
}
sub browser_uninstall {
my @updates = $IN->param('uninstall');
my ($status, $errors) = uninstall(@updates);
if (!$status) {
$errors->{updates_selected} = \@updates;
return $errors;
}
return { uninstall_success => 1, updates_selected => [] };
}
sub uninstall {
my @updates = @_;
my $updater = _updater;
my $v = $updater->verify_uninstall(@updates);
return { %$v, update_failed => 1, verify_failed => 1 } if ref $v eq 'HASH';
@updates = @$v;
my $success = $updater->uninstall_verified(@updates);
if (!$success) {
my $error = $updater->error;
return 0, { uninstall_failed => 1, error => "Update uninstall failed: $error" };
}
$STASH{updates}->{installed} = { $updater->installed };
delete $STASH{updates}->{cache};
$STASH{updates}->save;
return 1;
}
# Takes a string, such as '/foo/bar/blah/sdffffffddddddddddddddddddddddddddddd'
# and replaces a part of it with ...
# The arguments are:
# - string
# - number of characters before the ...
# - number of characters after the ...
sub shorten {
my ($string, $leading, $trailing) = @_;
if (length($string) <= ($leading + $trailing + 3)) {
return $string;
}
else {
return substr($string, 0, $leading) . ' ... ' . substr($string, -$trailing);
}
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,303 @@
# ==================================================================
# 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: Add.pm,v 1.59 2007/12/20 20:31:35 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::User::Add;
# ==================================================================
use strict;
use Links qw/:objects :payment/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# -------------------------------------------------------------------
# Display either an add form or process an add request.
#
if ($CFG->{user_required} and !$USER) {
print $IN->redirect(Links::redirect_login_url('add'));
return;
}
my $custom;
if (exists $CFG->{payment}->{remote}->{used}->{PayPal} and $custom = $IN->param('custom') and substr($custom, 0, 3) eq 'do;') {
substr($custom, 0, 3) = '';
my @pairs = split /;/, $custom;
for (@pairs) {
my ($key, $val) = split /=/, $_;
next unless $key and $val;
$IN->param($key => $val) unless $IN->param($key);
}
}
print $IN->header;
# We are processing an add request.
if ($IN->param('add')) {
my $results = $PLG->dispatch('user_add_link', \&add_link);
if (defined $results->{error}) {
print Links::SiteHTML::display('add', $results);
}
else {
$results = Links::SiteHTML::tags('link', $results);
$results->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_ADD_SUCCESS'), "$CFG->{db_cgi_url}/add.cgi");
if ($CFG->{payment}->{enabled}) {
require Links::Payment;
my @cats = $IN->param('CatLinks.CategoryID');
my $opt = Links::Payment::load_cat_price(\@cats);
if (exists $opt->{error}) {
print Links::SiteHTML::display('error', $opt);
}
elsif ($opt->{payment_mode} == NOT_ACCEPTED) {
if ($CFG->{admin_email_add}) {
Links::send_email('link_added.eml', $results, { admin_email => 1 }) or die "Unable to send mail: $GT::Mail::error";
}
print Links::SiteHTML::display('add_success', $results);
}
else {# payment option for this category is required or optional
$results->{link_id} = $results->{ID}; # we need a different tag since both Category and Link have ID
$opt->{CategoryID} = delete $opt->{ID}; # remove category id
$opt->{CategoryDescription} = delete $opt->{Description};
$results->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_PAYMENT'), "$CFG->{db_cgi_url}/modify.cgi?do=payment_linked;process_payment=1;modify=1;ID=$results->{link_id}");
print Links::SiteHTML::display('payment', { %$results, %$opt });
}
}
else {
if ($CFG->{admin_email_add}) {
Links::send_email('link_added.eml', $results, { admin_email => 1 }) or die "Unable to send mail: $GT::Mail::error";
}
print Links::SiteHTML::display('add_success', $results);
}
}
}
# We are processing a payment request.
elsif ($IN->param('process_payment') and $CFG->{payment}->{enabled}) {
my $payment_term = $IN->param('payment_term') || '';
my $do = $IN->param('do');
if ($payment_term eq 'free') {
my $link = $DB->table('Links')->get($IN->param('link_id'));
if (not $link or ($CFG->{user_required} and $link->{LinkOwner} ne $USER->{Username})) {
print Links::SiteHTML::display('error', { error => !$link ? $GT::SQL::ERRORS : Links::language('PAYMENTERR_NOTOWNER') });
return;
};
$link = Links::SiteHTML::tags('link', $link);
# Set ExpiryDate to free
$link->{'CatLinks.CategoryID'} = $IN->param('cat_id');
$link->{ExpiryDate} = FREE;
$link->{ExpiryNotify}= 0;
# Update the link
$DB->table('Links')->update({ ExpiryDate => FREE, ExpiryNotify => 0 }, { ID => $link->{ID} });
# Update the Timestmp for link's categories so they get rebuilt with build changed
my @cats = $DB->table('Links', 'CatLinks')->select('CategoryID', { LinkID => $link->{ID} })->fetchall_list;
$DB->table('Category')->update({ Timestmp => \'NOW()' }, { ID => \@cats });
# Add some special tags for formatting.
@cats = $DB->table('Category', 'CatLinks')->select('Category.Full_Name', { 'CatLinks.LinkID' => $link->{ID} })->fetchall_list;
$link->{Category} = join "\n", sort @cats;
$link->{Category_loop} = [sort @cats];
$link->{Host} = $ENV{REMOTE_HOST} ? "$ENV{REMOTE_HOST} ($ENV{REMOTE_ADDR})" : $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : 'none';
$link->{Referer} = $ENV{HTTP_REFERER} ? $ENV{HTTP_REFERER} : 'none';
$link->{AutoValidate} = $CFG->{build_auto_validate};
if ($CFG->{admin_email_add}) {
Links::send_email('link_added.eml', $link, { admin_email => 1 }) or die "Unable to send mail: $GT::Mail::error";
}
$link->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_ADD_SUCCESS'), "$CFG->{db_cgi_url}/add.cgi");
print Links::SiteHTML::display('add_success', $link);
}
elsif ($IN->param('payment_success')) {
print Links::SiteHTML::display('payment_success', { main_title_loop => Links::Build::build('title', Links::language('LINKS_PAYMENT_SUCCESS'), $CFG->{build_root_url} . "/" . ($CFG->{build_home} || ($CFG->{build_index_include} ? $CFG->{build_index} : ''))) });
}
elsif ($do =~ /^payment_(method|form|direct)$/) {
require Links::Payment;
my $vars = Links::Payment->$1();
my $page = $IN->param('page') || $IN->param('do');
my $opt = Links::Payment::load_cat_price($IN->param('cat_id'));
if ($opt->{payment_mode} == NOT_ACCEPTED) {
print Links::SiteHTML::display('error', { error => Links::language('PAYMENTERR_NOTACCEPTED') });
return;
}
my $link = $DB->table('Links')->get($IN->param('link_id'));
if (not $link or ($CFG->{user_required} and $link->{LinkOwner} ne $USER->{Username})) {
print Links::SiteHTML::display('error', { error => !$link ? $GT::SQL::ERRORS : Links::language('PAYMENTERR_NOTOWNER') });
return;
}
$link = Links::SiteHTML::tags('link', $link);
$link->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_PAYMENT'), "$CFG->{db_cgi_url}/modify.cgi?do=payment_linked;process_payment=1;modify=1;ID=$link->{ID}");
print Links::SiteHTML::display($page, { %$vars, %$opt, %$link });
}
else {
print Links::SiteHTML::display('error', { error => "Invalid action" });
}
}
# We are displaying an add form.
else {
my @id = grep { /^\d+$/ } $IN->param('ID');
# If we don't have an id, and can't generate a list, let's send the user a message.
if (!@id and !$CFG->{db_gen_category_list}) {
print Links::SiteHTML::display('error', { error => Links::language('ADD_SELCAT') });
}
else {
# Otherwise display the add form.
if ($USER) {
$IN->param('Contact_Name') or ($IN->param('Contact_Name', $USER->{Name} || $USER->{Username}));
$IN->param('Contact_Email') or ($IN->param('Contact_Email', $USER->{Email}));
}
if ($DB->table('Category')->count == 0) {
print Links::SiteHTML::display('error', { error => Links::language('ADD_NOCATEGORIES') });
}
# If we're not generating a category list, the add form can't be shown without a valid category ID.
elsif (!$CFG->{db_gen_category_list} and $DB->table('Category')->count({ ID => \@id }) == 0) {
print Links::SiteHTML::display('error', { error => Links::language('ADD_INVALIDCAT', join(', ', @id)) });
}
else {
my $category = {};
if ($CFG->{db_gen_category_list} < 2) {
require Links::Tools;
$category = Links::Tools::category_list();
$category->{Category} = sub { Links::Tools::category_list_html() };
}
print Links::SiteHTML::display('add', {
main_title_loop => Links::Build::build('title', Links::language('LINKS_ADD'), "$CFG->{db_cgi_url}/add.cgi" . (@id ? "?ID=" . join(';ID=', @id) : '')),
%$category
});
}
}
}
}
sub add_link {
# --------------------------------------------------------
# Add the link to the database.
#
my $class = shift;
my @id = $IN->param('CatLinks.CategoryID');
my %ret;
if ($CFG->{db_gen_category_list} < 2) {
require Links::Tools;
%ret = %{Links::Tools::category_list()};
$ret{Category} = sub { Links::Tools::category_list_html() };
}
$ret{main_title_loop} = Links::Build::build('title', Links::language('LINKS_ADD'), "$CFG->{db_cgi_url}/add.cgi" . (@id ? "?ID=" . join(';ID=', @id) : ''));
# Check the referer.
if (@{$CFG->{db_referers}}) {
my $found = 0;
if ($ENV{'HTTP_REFERER'}) {
foreach (@{$CFG->{db_referers}}) { $ENV{'HTTP_REFERER'} =~ /\Q$_\E/i and $found++ and last; }
}
unless ($found) {
return { error => Links::language('ADD_BADREFER', $ENV{'HTTP_REFERER'}), %ret };
}
}
# Get our form data.
my $input = $IN->get_hash;
# Check if the link is valid
if ($CFG->{user_link_validation}) {
require Links::Tools;
my $status = Links::Tools::link_status($input->{URL});
if ($status and $Links::Tools::STATUS_BAD{$status}) {
return { error => Links::language('ADD_BADSTATUS', $Links::Tools::STATUS_BAD{$status}), %ret };
}
}
my $db = $DB->table('Links');
my $cdb = $DB->table('Category');
# Columns the user should not be passing in
for my $key (qw/ID LinkOwner Add_Date Mod_Date Timestmp Date_Checked ExpiryDate ExpiryCounted ExpiryNotify LinkExpired/) {
delete $input->{$key};
}
for my $key (keys %{$CFG->{add_system_fields}}) {
$input->{$key} = $CFG->{add_system_fields}->{$key};
}
# Set the LinkOwner
$input->{LinkOwner} = $USER ? $USER->{Username} : 'admin';
# Set date variable to today's date.
Links::init_date();
my $today = GT::Date::date_get();
$input->{Add_Date} = $today;
$input->{Mod_Date} = $today;
# Backward compatibility
$input->{Contact_Name} = $input->{'Contact_Name'} || $input->{'Contact Name'} || ($USER ? $USER->{Name} : '');
$input->{Contact_Email} = $input->{'Contact_Email'} || $input->{'Contact Email'} || ($USER ? $USER->{Email} : '');
$input->{isValidated} = ($CFG->{build_auto_validate} == 1 and $USER or $CFG->{build_auto_validate} == 2) ? 'Yes' : 'No';
# Check the category
my @cids = $IN->param('CatLinks.CategoryID');
my @name;
if (@cids) {
foreach my $cid (@cids) {
next if (! $cid);
my $sth = $cdb->select('Full_Name', { ID => $cid });
$sth->rows or return { error => Links::language('ADD_INVALIDCAT', $cid), %ret };
push @name, $sth->fetchrow;
}
if (@name) {
$input->{'CatLinks.CategoryID'} = \@cids;
}
}
my $take_payments = (
$CFG->{payment}->{enabled}
and
(
$cdb->count(GT::SQL::Condition->new(Payment_Mode => '>=' => OPTIONAL, ID => '=' => \@cids))
or
(
$CFG->{payment}->{mode} >= OPTIONAL and
$cdb->count(GT::SQL::Condition->new(Payment_Mode => '=' => GLOBAL, ID => '=' => \@cids))
)
)
);
# Set the payment expiry
# Set this to unlimited when payment is turned off so that if payment is turned on
# at a later date, those users aren't forced to pay.
$input->{ExpiryDate} = $CFG->{payment}->{enabled} && $take_payments ? UNPAID : FREE;
# Setup the language for GT::SQL.
local $GT::SQL::ERRORS->{ILLEGALVAL} = Links::language('ADD_ILLEGALVAL');
local $GT::SQL::ERRORS->{UNIQUE} = Links::language('ADD_UNIQUE');
local $GT::SQL::ERRORS->{NOTNULL} = Links::language('ADD_NOTNULL');
local $Links::Table::Links::ERRORS->{NOCATEGORY} = Links::language('ADD_NOCATEGORY');
$Links::Table::Links::ERRORS if 0; # silence -w
# Add the record.
my $id = $db->add($input);
$input->{ID} = $id;
if (! $id) {
my $error = "<ul>" . join('', map "<li>$_</li>", $db->error) . "</ul>";
return { error => $error, %ret };
}
# Add some special tags for formatting.
$input->{Category} = join "\n", sort @name;
$input->{Category_loop} = [sort @name];
$input->{Host} = $ENV{REMOTE_HOST} ? "$ENV{REMOTE_HOST} ($ENV{REMOTE_ADDR})" : $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : 'none';
$input->{Referer} = $ENV{HTTP_REFERER} ? $ENV{HTTP_REFERER} : 'none';
$input->{AutoValidate} = $CFG->{build_auto_validate};
# Send the visitor to the success page.
return $input;
}
1;

View File

@ -0,0 +1,126 @@
# ==================================================================
# 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: Editor.pm,v 1.15 2009/05/09 06:40:54 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::User::Editor;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::Browser::Controller;
use Links::Browser;
use Links::SiteHTML;
sub handle {
# ------------------------------------------------------------------
# This script is only available to users who have logged on.
#
unless ($USER) {
my $url = $IN->url(absolute => 1, query_string => 1);
$url = $IN->escape($url);
$url = $CFG->{db_cgi_url} . "/user.cgi?url=$url;from=browser";
print $IN->redirect($url);
return;
}
my $editors = $DB->table('Editors');
my @nodes;
my $perms = {};
# Get a controller to manage access.
my $ctrl = Links::Browser::Controller->new(user => $USER);
if ($USER->{Status} eq 'Administrator') {
$ctrl->{admin} = 1;
}
else {
my $sth = $editors->select({ Username => $USER->{Username} });
if ($sth->rows) {
while (my $ed = $sth->fetchrow_hashref) {
push @nodes, $ed->{CategoryID};
$perms->{$ed->{CategoryID}} = $ed;
}
}
unless (@nodes) {
print $IN->header;
print Links::SiteHTML::display('error', { error => Links::language('BROWSER_NOTEDITOR') });
return;
}
}
# Handle the special condition which related to viewing
# and downloading files. Must remap the passed column
# values so Jump functions properly.
my $method = $IN->param('do');
if ($method and $method =~ m/^(?:(v)iew|(download))_file$/) {
$IN->param($+, $IN->param('cn'));
$IN->param('ID', $IN->param('link_id') || $IN->param('id'));
$IN->param('DB', $IN->param('db'));
require Links::User::Jump;
return Links::User::Jump::handle();
}
elsif ($method and $method =~ m/^(?:(v)iew|(download))_tmp_file$/) {
my $download = $2;
# view_tmp_file doesn't go through Jump because only editors are
# allowed to see them - the tmp files are used for pending Changes.
my $col = $IN->param('cn');
my $id = $IN->param('link_id');
my $changes = $DB->table('Changes')->select({ LinkID => $id })->fetchrow_hashref;
my ($linkinfo, $fh);
if ($changes) {
$linkinfo = eval $changes->{ChgRequest};
if ($linkinfo and -f $linkinfo->{$col}) {
my $colfh = \do { local *FH; *FH };
if (open $colfh, "<$linkinfo->{$col}") {
$fh = $colfh;
binmode $fh;
}
}
}
if (!$fh) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('FILE_UNKNOWN', $id) });
return;
}
(my $filename = $linkinfo->{"${col}_filename"} || $linkinfo->{$col}) =~ s{.*[/\\]}{};
print $IN->header($IN->file_headers(
filename => $filename,
inline => $download ? 0 : 1,
size => -s $linkinfo->{$col}
));
while (read $fh, my $buffer, 64*1024) {
print $buffer;
}
return 1;
}
# Load the tree if it is under 200 categories.
$ctrl->{load_tree} = 1;
$ctrl->{user_base_node} = \@nodes;
$ctrl->{perms} = $perms;
$ctrl->{admin_templates} = 0;
# Begin the script.
print $IN->header(-charset => $CFG->{header_charset});
$method = $ctrl->can_run;
if ($method) {
my $browser = Links::Browser->new(ctrl => $ctrl);
$PLG->dispatch("browser_$method", sub { $browser->$method(); }, $browser);
}
else {
print Links::SiteHTML::display('error', { error => Links::language('BROWSER_UNAUTHORIZED') });
}
}
1;

View File

@ -0,0 +1,186 @@
# ==================================================================
# 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: Jump.pm,v 1.26 2006/02/20 22:38:31 jagerman 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::User::Jump;
# ==================================================================
use strict;
use Links qw/:objects :payment/;
use Links::SiteHTML;
sub handle {
# --------------------------------------------------------------
# Jump to a given ID.
#
$PLG->dispatch('jump_link', \&_plg_jump, {});
}
sub _plg_jump {
# --------------------------------------------------------------
# Jump to a given link.
#
my $links = $DB->table('Links');
my $id = $IN->param('ID') || $IN->param('Detailed');
my $action = $IN->param('action') || '';
my $goto = '';
my $rec = {};
if ($CFG->{framed_jump} and $id and $action eq 'jump_frame') {
my $error;
if ($id !~ /^\d+$/) {
$error = Links::language('JUMP_INVALIDID', $id);
}
else {
$rec = $links->select({ ID => $id }, VIEWABLE)->fetchrow_hashref;
unless ($rec) {
$error = Links::language('JUMP_INVALIDID', $id);
$rec = {};
}
elsif ($CFG->{build_detailed}) {
$rec->{detailed_url} = "$CFG->{build_detail_url}/" . $links->detailed_url($id);
}
}
print $IN->header();
print Links::SiteHTML::display('jump_frame', { error => $error, %$rec });
return;
}
# If we are chosing a random link, then get the total and go to one at random.
if (lc $id eq "random") {
my $offset = int rand $links->count(VIEWABLE);
$links->select_options("LIMIT 1 OFFSET $offset");
my $sth = $links->select(qw/ID URL/ => VIEWABLE);
($id, $goto) = $sth->fetchrow_array;
}
elsif (defined $id) {
if ($id !~ /^\d+$/) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('JUMP_INVALIDID', $id) });
return;
}
# Find out if we're going to be displaying a file
my $col = $IN->param('v') || $IN->param('dl') || $IN->param('view') || $IN->param('download');
if ($col) {
# in this case, we need to know from what table we want to load our data from.
# It will by default pull information from the Links table, however if the
# DB=tablename option is used, it will apply the request to that table instead
my $table_name = $IN->param('DB') || 'Links';
unless ($table_name =~ m/^\w+$/) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('FILE_TABLEFORMAT' ) });
return;
};
if ($table_name ne 'Links') {
eval { $links = $DB->table($table_name) };
if ($@) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('FILE_TABLE', $table_name, $GT::SQL::error) });
return;
}
}
my $fh;
eval { $fh = $links->file_info($col, $id); };
if ($fh) {
if ($IN->param('v') or $IN->param('view')) { # Viewing
print $IN->header($IN->file_headers(
filename => $fh->File_Name,
mimetype => $fh->File_MimeType,
inline => 1,
size => $fh->File_Size
));
}
else { # Downloading
print $IN->header($IN->file_headers(
filename => $fh->File_Name,
mimetype => $fh->File_MimeType,
inline => 0,
size => $fh->File_Size
));
}
binmode $fh;
while (read($fh, my $buffer, 65536)) {
print $buffer;
}
return 1;
}
else {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('FILE_UNKNOWN', $id) });
return;
}
}
# Jump to a URL, bump the hit counter.
else {
$rec = $links->select({ ID => $id }, VIEWABLE)->fetchrow_hashref;
unless ($rec) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('JUMP_INVALIDID', $id) });
return;
}
$goto = $rec->{URL};
my $clicktrack = $DB->table('ClickTrack');
my $rows = $clicktrack->count({ LinkID => $id, IP => $ENV{REMOTE_ADDR}, ClickType => 'Hits' });
unless ($rows) {
eval {
$clicktrack->insert({ LinkID => $id, IP => $ENV{REMOTE_ADDR}, ClickType => 'Hits', Created => \"NOW()" });
$links->update({ Hits => \"Hits + 1" }, { ID => $id }, { GT_SQL_SKIP_INDEX => 1 });
};
}
}
}
# Oops, no link.
else {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('JUMP_INVALIDID', $id) });
return;
}
unless (defined $goto) {
my $error = ($IN->param('ID') eq 'random') ? Links::language('RANDOM_NOLINKS') : Links::language('JUMP_INVALIDID', $id);
print $IN->header();
print Links::SiteHTML::display('error', { error => $error });
return;
}
# Redirect to a detailed page if requested.
if ($CFG->{build_detailed} and $IN->param('Detailed')) {
$goto = Links::transform_url("$CFG->{build_detail_url}/" . $links->detailed_url($id));
}
($goto =~ m,^\w+://,) or ($goto = "http://$goto");
if ($goto) {
if ($CFG->{framed_jump} and not ($CFG->{build_detailed} and $IN->param('Detailed'))) {
unless (keys %$rec) {
$rec = $links->select({ ID => $id }, VIEWABLE)->fetchrow_hashref;
}
$rec->{detailed_url} = "$CFG->{build_detail_url}/" . $links->detailed_url($id) if $CFG->{build_detailed};
print $IN->header();
print Links::SiteHTML::display('jump', { destination => $goto, %$rec });
return;
}
else {
print $IN->redirect($goto);
}
}
else {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('JUMP_INVALIDID', $id) });
return;
}
}
1;

View File

@ -0,0 +1,263 @@
# ==================================================================
# 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: Login.pm,v 1.19 2005/05/08 09:56:44 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::User::Login;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# -------------------------------------------------------------------
# Determine what to do.
#
my $input = $IN->get_hash;
my $mtl = Links::Build::build('title', Links::language('LINKS_LOGIN'), "$CFG->{db_cgi_url}/user.cgi");
if ($input->{login}) {
$PLG->dispatch('user_login', \&login_user);
}
elsif ($input->{signup_user}) {
$PLG->dispatch('user_signup', \&signup_user);
}
elsif ($input->{validate_user}) {
$PLG->dispatch('user_validate', \&validate_user);
}
elsif ($input->{send_validate}) {
$PLG->dispatch('user_validate_email', \&send_validate);
}
elsif ($input->{send_pass} and $CFG->{user_allow_pass}) {
$PLG->dispatch('user_pass_email', \&send_pass);
}
elsif ($input->{signup_form}) {
print $IN->header();
print Links::SiteHTML::display('signup_form', { Username => $IN->param('Username') || '', Password => '', Email => $IN->param('Email') || '', main_title_loop => Links::Build::build('title', Links::language('LINKS_SIGNUP'), "$CFG->{db_cgi_url}/user.cgi?signup_form=1") });
}
elsif ($input->{validate}) {
print $IN->header();
print Links::SiteHTML::display('validate_form', { main_title_loop => Links::Build::build('title', Links::language('LINKS_VALIDATE'), "$CFG->{db_cgi_url}/user.cgi?validate=1") });
}
elsif ($input->{logout}) {
Links::Authenticate::auth('delete_session');
$USER = undef;
print $IN->header();
print Links::SiteHTML::display('login', { Username => '', Password => '', Email => '', error => Links::language('USER_LOGOUT'), main_title_loop => $mtl });
}
elsif ($input->{email_pass} and $CFG->{user_allow_pass}) {
print $IN->header();
print Links::SiteHTML::display('login_email', { main_title_loop => Links::Build::build('title', Links::language('LINKS_EMAILPASS'), "$CFG->{db_cgi_url}/user.cgi?email_pass=1") });
}
else {
print $IN->header();
print Links::SiteHTML::display('login', { Username => $IN->param('Username') || '', main_title_loop => $mtl });
}
}
# ==============================================================
sub login_user {
# --------------------------------------------------------
# Logs a user in, and creates a session ID.
#
my $username = $IN->param('Username') || shift;
my $password = $IN->param('Password') || shift;
my $goto = shift || 'login_success';
my $mtl = Links::Build::build('title', Links::language('LINKS_LOGIN'), "$CFG->{db_cgi_url}/user.cgi");
# Make sure we have both a username and password.
if (!$username or !$password) {
print $IN->header();
print Links::SiteHTML::display('login', { error => Links::language('USER_BADLOGIN'), Username => $username, main_title_loop => $mtl });
return;
}
# Check that the user exists, and that the password is valid.
my $user = Links::init_user($username, $password);
if (!$user) {
print $IN->header();
require Links::Authenticate;
if (Links::Authenticate::auth_valid_user({ Username => $username, Password => $password })) {
print Links::SiteHTML::display('login', { error => Links::language('USER_NOTVAL', $user->{Email}), Username => $user->{Username}, main_title_loop => $mtl });
}
else {
print Links::SiteHTML::display('login', { error => Links::language('USER_BADLOGIN'), main_title_loop => $mtl });
}
return;
}
# Store the session in either a cookie or url based.
my $results = Links::Authenticate::auth('create_session', { Username => $user->{Username} });
return if $results->{redirect};
# Get the $USER information.
$USER = Links::Authenticate::auth('get_user', { Username => $username, Password => $password, auto_create => 1 });
print $IN->header(); # In case the session didn't print it.
print Links::SiteHTML::display($goto, { %$user, main_title_loop => $mtl });
}
sub signup_user {
# --------------------------------------------------------
# Signs a new user up.
#
my $username = $IN->param('Username');
my $password = $IN->param('Password');
my $email = $IN->param('Email');
my $mtl = Links::Build::build('title', Links::language('LINKS_SIGNUP'), "$CFG->{db_cgi_url}/user.cgi?signup_form=1");
if (!$username or !$password or !$email) {
print $IN->header();
print Links::SiteHTML::display('signup_form', { error => Links::language('USER_INVALIDSIGNUP'), main_title_loop => $mtl });
return;
}
unless ($email =~ /.\@.+\../) {
print $IN->header();
print Links::SiteHTML::display('signup_form', { error => Links::language('USER_INVALIDEMAIL', $email), main_title_loop => $mtl });
return;
}
# Check that the username doesn't already exist.
my $db = $DB->table('Users');
my $user = $db->get($username);
if ($user) {
print $IN->header();
print Links::SiteHTML::display( 'signup_form', { error => Links::language('USER_NAMETAKEN', $username), main_title_loop => $mtl });
return;
}
# Check that the email address doesn't already exist.
my $hits = $db->count({ Email => $email });
if ($hits) {
print $IN->header();
print Links::SiteHTML::display('signup_form', { error => Links::language('USER_EMAILTAKEN', $email), main_title_loop => $mtl });
return;
}
my ($code, $msg);
# Add the user in, set defaults for fields not specified.
$user = $IN->get_hash();
my $def = $db->default || {};
foreach (keys %$def) {
$user->{$_} = $def->{$_} unless (exists $user->{$_});
}
# Send validation email if needed.
if ($CFG->{user_validation}) {
my $code = time . $$ . int rand 1000;
$user->{Status} = "Not Validated";
$user->{Validation} = $code;
my $ret = $db->add($user);
if (!$ret) {
print $IN->header();
print Links::SiteHTML::display('signup_form', { error => $db->error, main_title_loop => $mtl });
return;
}
}
else {
$user->{Status} = "Registered";
$user->{Validation} = 0;
my $ret = $db->add($user);
if (!$ret) {
print $IN->header();
print Links::SiteHTML::display('signup_form', { error => $db->error, main_title_loop => $mtl });
return;
}
}
# Print the welcome screen.
if ($CFG->{user_validation}) {
print $IN->header();
print Links::SiteHTML::display('signup_success', { %$user, main_title_loop => $mtl });
Links::send_email('validate.eml', $user) or die "Unable to send message: $GT::Mail::error";
}
else {
my $results = Links::Authenticate::auth('create_session', { Username => $user->{Username} });
$USER = Links::Authenticate::auth('get_user', { Username => $user->{Username}, Password => $user->{Password}, auto_create => 1 });
print $IN->header();
print Links::SiteHTML::display('signup_success', { %$user, main_title_loop => $mtl });
}
}
sub validate_user {
# --------------------------------------------------------
# Validates a user.
#
my $code = $IN->param('code');
$code =~ s/^\s*|\s*$//g;
my $mtl = Links::Build::build('title', Links::language('LINKS_VALIDATE'), "$CFG->{db_cgi_url}/user.cgi?validate=1");
if (!$code) {
print $IN->header;
print Links::SiteHTML::display('validate_form', { error => Links::language('USER_INVALIDVAL'), main_title_loop => $mtl });
return;
}
my $db = $DB->table('Users');
my $sth = $db->select({ Validation => $code });
my $user = $sth->fetchrow_hashref;
if (! $user) {
print $IN->header;
print Links::SiteHTML::display('validate_form', { error => Links::language('USER_INVALIDVAL'), main_title_loop => $mtl });
return;
}
$db->update({ Status => 'Registered' }, { Username => $user->{Username} });
login_user($user->{Username}, $user->{Password}, 'validate_success');
}
sub send_pass {
# -------------------------------------------------------------------
# Sends the user a password reminder email.
#
my $email = $IN->param('Email');
my $user_db = $DB->table('Users');
my $sth = $user_db->select( { Email => $email } );
print $IN->header();
my $user = $sth->fetchrow_hashref;
if ($user and $email =~ /.+\@.+\..+/) {
Links::send_email('password.eml', { %$user, %ENV }) or die "Unable to send message: $GT::Mail::error";
print Links::SiteHTML::display('login', { error => Links::language('USER_PASSSENT'), Username => '', Password => '', main_title_loop => Links::Build::build('title', Links::language('LINKS_LOGIN'), "$CFG->{db_cgi_url}/user.cgi") });
}
else {
print Links::SiteHTML::display('login_email', { error => Links::language('USER_NOEMAIL'), main_title_loop => Links::Build::build('title', Links::language('LINKS_EMAILPASS'), "$CFG->{db_cgi_url}/user.cgi?email_pass=1") });
}
}
sub send_validate {
# -------------------------------------------------------------------
# Sends the validation email if the user needs another one.
#
my $email = $IN->param('Email');
my $user_db = $DB->table('Users');
my $sth = $user_db->select( { Email => $email } );
print $IN->header();
if ($sth->rows) {
# Prepare the message.
my $user = $sth->fetchrow_hashref;
# Make sure there is a validation code.
if (! $user->{Validation}) {
$user->{Validation} = (time) . ($$) . (int rand(1000));
$user_db->modify($user);
}
Links::send_email('validate.eml', $user) or die "Unable to send message: $GT::Mail::error";
print Links::SiteHTML::display('login', { error => Links::language('USER_VALSENT'), Username => '', Password => '', main_title_loop => Links::Build::build('title', Links::language('LINKS_LOGIN'), "$CFG->{db_cgi_url}/user.cgi") });
}
else {
print Links::SiteHTML::display('login_email', { error => Links::language('USER_NOEMAIL'), main_title_loop => Links::Build::build('title', Links::language('LINKS_EMAILPASS'), "$CFG->{db_cgi_url}/user.cgi?email_pass=1") });
}
}
1;

View File

@ -0,0 +1,571 @@
# ==================================================================
# 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: Modify.pm,v 1.82 2013/02/01 04:43:56 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::User::Modify;
# ==================================================================
use strict;
use Links qw/:objects :payment/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# ---------------------------------------------------
# Determine what to do.
#
my $link_id = $IN->param('LinkID');
if ($CFG->{user_required} and !$USER) {
print $IN->redirect(Links::redirect_login_url('modify'));
return;
}
# Perform the link modification
if ($IN->param('modify')) {
_modify();
}
elsif ($USER) {
# Display the link modify form (for a specific link)
if ($IN->param('LinkID')) {
_modify_passed_in();
}
else {
_list_owned_links();
}
}
# Display the link modify form (used when user_required is off)
else {
_modify_form();
}
}
# ==============================================================
sub _modify {
# --------------------------------------------------------
# Modifies a link.
#
# If payment is enabled and we are processing a payment
if ($CFG->{payment}->{enabled} and $IN->param('process_payment')) {
my $payment_term = $IN->param('payment_term') || '';
my $do = $IN->param('do') || '';
if ($payment_term eq 'free') {
print $IN->header();
my $link = $DB->table('Links')->get(scalar $IN->param('link_id'));
my $mtl = Links::Build::build('title', Links::language('LINKS_MODIFY_SUCCESS'), "$CFG->{db_cgi_url}/modify.cgi");
if (not $link or ($CFG->{user_required} and $link->{LinkOwner} ne $USER->{Username})) {
print Links::SiteHTML::display('error', { error => !$link ? $GT::SQL::ERRORS : Links::language('PAYMENTERR_NOTOWNER'), main_title_loop => $mtl });
return;
}
$link = Links::SiteHTML::tags('link', $link);
# Add some special tags for formatting.
$link->{Category} = $DB->table('Category', 'CatLinks')->select('Category.Full_Name', { 'CatLinks.LinkID' => $link->{ID} })->fetchrow;
# Set ExpiryDate to free
$link->{'CatLinks.CategoryID'} = $IN->param('cat_id');
$link->{ExpiryDate} = FREE;
$link->{ExpiryNotify}= 0;
# Update the link
$DB->table('Links')->update({ ExpiryDate => FREE, ExpiryNotify => 0 }, { ID => $link->{ID} });
# Update the Timestmp for link's categories so they get rebuilt with build changed
my @cats = $DB->table('Links', 'CatLinks')->select('CategoryID', { LinkID => $link->{ID} })->fetchall_list;
$DB->table('Category')->update({ Timestmp => \'NOW()' }, { ID => \@cats });
print Links::SiteHTML::display('modify_success', { %$link, main_title_loop => $mtl });
}
elsif ($do eq 'payment_linked') {
print $IN->header;
my $link = $DB->table('Links', 'CatLinks')->select({ ID => scalar $IN->param('ID') })->fetchrow_hashref;
if (!$link) {
print Links::SiteHTML::display('error', { error => Links::language('MODIFY_NOLINKS') });
return;
}
elsif ($CFG->{user_required} and $link->{LinkOwner} ne $USER->{Username}) {
print Links::SiteHTML::display('error', { error => Links::language('PAYMENTERR_NOTOWNER') });
return;
}
$link = Links::SiteHTML::tags('link', $link);
require Links::Payment;
my @cid = $DB->table('CatLinks')->select('CategoryID', { LinkID => $link->{ID} })->fetchall_list;
my $opt = Links::Payment::load_cat_price(\@cid);
if ($opt->{payment_mode} == NOT_ACCEPTED) {
print Links::SiteHTML::display('error', { error => Links::language('PAYMENTERR_NOTACCEPTED') });
return;
}
$link->{link_id} = $link->{ID}; # we need a different tag since both Category and Link have ID
$opt->{CategoryID} = delete $opt->{ID}; # remove category id
$opt->{CategoryDescription} = delete $opt->{Description};
$link->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_PAYMENT'), "$CFG->{db_cgi_url}/modify.cgi?do=payment_linked;process_payment=1;modify=1;ID=$link->{link_id}");
print Links::SiteHTML::display('payment', { %$link, %$opt });
}
elsif ($do =~ /^payment_(method|form|direct)$/) {
require Links::Payment;
my $vars = Links::Payment->$1();
my $page = $IN->param('page') || $IN->param('do');
my $opt = Links::Payment::load_cat_price($IN->param('cat_id'));
if ($opt->{payment_mode} == NOT_ACCEPTED) {
print Links::SiteHTML::display('error', { error => Links::language('PAYMENTERR_NOTACCEPTED') });
return;
}
my $link = $DB->table('Links')->get($IN->param('link_id'));
print $IN->header();
if (not $link or $link->{LinkOwner} ne $USER->{Username}) {
print Links::SiteHTML::display('error', { error => !$link ? $GT::SQL::ERRORS : Links::language('PAYMENTERR_NOTOWNER') });
return;
}
$link = Links::SiteHTML::tags('link', $link);
$link->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_PAYMENT'), "$CFG->{db_cgi_url}/modify.cgi?do=payment_linked;process_payment=1;modify=1;ID=$link->{ID}");
print Links::SiteHTML::display($page, { %$vars, %$opt, %$link });
}
else {
print $IN->header;
print Links::SiteHTML::display('error', { error => "Invalid action" });
}
}
# Otherwise, modify the link
else {
my $results = $PLG->dispatch('user_modify_link', \&modify_link, {});
if (defined $results->{error}) {
print $IN->header();
print Links::SiteHTML::display('modify', $results);
}
else {
$results->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_MODIFY_SUCCESS'), "$CFG->{db_cgi_url}/modify.cgi");
if ($CFG->{payment}->{enabled}) {
require Links::Payment;
my @cid = $IN->param('CatLinks.CategoryID');
my $opt = Links::Payment::load_cat_price(\@cid);
print $IN->header();
if (exists $opt->{error}) {
print Links::SiteHTML::display('error', $opt);
}
elsif ($opt->{payment_mode} == NOT_ACCEPTED or ($results->{ExpiryDate} >= time)) {
print Links::SiteHTML::display('modify_success', $results);
}
else {# display payment form if the link is expired or payment mode for this category is required or optional
$results->{link_id} = $results->{ID}; # we need a different tag since both Category and Link have ID
$opt->{CategoryID} = delete $opt->{ID}; # remove category id
$opt->{CategoryDescription} = delete $opt->{Description};
$results->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_PAYMENT'), "$CFG->{db_cgi_url}/modify.cgi?do=payment_linked;process_payment=1;modify=1;ID=$results->{link_id}");
print Links::SiteHTML::display('payment', {%$results,%$opt});
}
}
else {
print $IN->header();
print Links::SiteHTML::display('modify_success', $results);
}
}
}
}
sub _modify_passed_in {
# --------------------------------------------------------
# Display link that was passed in.
#
my $lid = $IN->param('LinkID');
my $link_db = $DB->table('Links');
my $mtl = Links::Build::build('title', Links::language('LINKS_MODIFY'), "$CFG->{db_cgi_url}/modify.cgi?LinkID=$lid");
my $sth = $link_db->select({ ID => $lid, LinkOwner => $USER->{Username} }, VIEWABLE);
if ($sth->rows) {
my $link = $sth->fetchrow_hashref;
my @ids = $DB->table('CatLinks')->select('CategoryID', { LinkID => $link->{ID} })->fetchall_list;
$IN->param('CatLinks.CategoryID', \@ids);
$link->{Contact_Name} ||= $USER->{Name} || $USER->{Username};
$link->{Contact_Email} ||= $USER->{Email};
my $category = {};
if ($CFG->{db_gen_category_list} < 2) {
require Links::Tools;
$category = Links::Tools::category_list();
$category->{Category} = sub { Links::Tools::category_list_html() };
}
print $IN->header();
print Links::SiteHTML::display('modify', {
main_title_loop => $mtl,
%$link,
%$category
});
}
elsif (!$CFG->{user_required}) {
_modify_form();
}
else {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('MODIFY_NOTOWNER'), LinkID => $lid, main_title_loop => $mtl });
}
}
sub _list_owned_links {
# --------------------------------------------------------
# Display a list of links the user owns.
#
my $link_db = $DB->table('Links');
my ($limit, $offset, $nh) = Links::limit_offset();
my $mtl = Links::Build::build('title', Links::language('LINKS_MODIFY'), "$CFG->{db_cgi_url}/modify.cgi");
$link_db->select_options("ORDER BY Title ASC", "LIMIT $limit OFFSET $offset");
my $sth = $link_db->select({
LinkOwner => $USER->{Username},
# If payment is enabled, we want to show non-validated links to allow
# payment to occur, otherwise only show validated ones
($CFG->{payment}->{enabled} ? () : (isValidated => 'Yes'))
});
my $total = $link_db->hits;
if (! $sth->rows) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('MODIFY_NOLINKS'), main_title_loop => $mtl });
return;
}
my ($toolbar, %paging);
my @links;
while (my $hash = $sth->fetchrow_hashref) {
push @links, Links::SiteHTML::tags('link', $hash);
}
if ($total > $limit) {
my $url = $CFG->{db_cgi_url} . "/" . $IN->url;
$toolbar = $DB->html(['Links'], $IN)->toolbar($nh, $limit, $total, $url);
%paging = (
url => $url,
num_hits => $total,
max_hits => $limit,
current_page => $nh
);
}
print $IN->header();
print Links::SiteHTML::display('modify_select', {
link_results_loop => \@links,
main_title_loop => $mtl,
total => $total,
next => $toolbar,
paging => \%paging
});
}
sub _modify_form {
# --------------------------------------------------------
# Just display the regular form.
#
my @id = $IN->param('ID'); # Category ID.
my $link = {};
print $IN->header();
if ($IN->param('LinkID')) {
my $lid = $IN->param('LinkID');
$link = $DB->table('Links')->select({ ID => $lid }, VIEWABLE)->fetchrow_hashref;
if (!$link) {
print Links::SiteHTML::display('error', { error => Links::language('MODIFY_INVALIDLINKID', $lid) });
return;
}
if (!@id) {
@id = $DB->table('CatLinks')->select('CategoryID', { LinkID => $lid })->fetchall_list;
# Set ID to the categories that the link is in so Links::Tools::category_list
# pre-selects them
$IN->param(ID => \@id);
}
}
if (!@id and !$CFG->{db_gen_category_list}) {
print Links::SiteHTML::display('error', { error => Links::language('MODIFY_SELCAT') });
}
else {
my $category = {};
if ($CFG->{db_gen_category_list} < 2) {
require Links::Tools;
$category = Links::Tools::category_list();
$category->{Category} = sub { Links::Tools::category_list_html() };
}
print Links::SiteHTML::display('modify', {
main_title_loop => Links::Build::build('title', Links::language('LINKS_MODIFY'), "$CFG->{db_cgi_url}/modify.cgi" . (@id ? "?ID=" . join(';ID=', @id) : '')),
%$category,
%$link
});
}
}
sub modify_link {
# --------------------------------------------------------
# Change the requested link.
#
my $args = $IN->get_hash();
my $db = $DB->table('Links');
my %cols = $db->cols;
# Make it possible to use any column to find the link we're modifying.
# Normally, we use the LinkID to find the link, but in some conditions the URL
# is used. Using this isn't recommended as you're not guaranteed to get the
# same or unique results.
my ($column, $value);
foreach my $col (keys %cols) {
if (exists $args->{'Current_' . $col} and $args->{'Current_' . $col}) {
$column = $col;
$value = $args->{'Current_' . $col};
last;
}
}
my $lid = $args->{LinkID};
my %ret;
if ($CFG->{db_gen_category_list} < 2) {
require Links::Tools;
%ret = %{Links::Tools::category_list()};
$ret{Category} = sub { Links::Tools::category_list_html() };
}
$ret{main_title_loop} = Links::Build::build('title', Links::language('LINKS_MODIFY'), "$CFG->{db_cgi_url}/modify.cgi" . ($lid ? "?LinkID=$lid" : ''));
$ret{LinkID} = $lid;
unless ($value or ($lid and $USER)) {
return { error => Links::language('MODIFY_NOURL'), %ret };
}
# Find the requested link
my ($link, $sth);
if ($USER and $lid) {
#if ($CFG->{user_required}) {
# Mod added back on April 10 by Virginia
if ($CFG->{user_required} and $USER->{Status} ne 'Administrator') { # mod by Virginia Lo on Oct 29, 2007
$sth = $db->select({ ID => $lid, LinkOwner => $USER->{Username} });
}
else {
$sth = $db->select({ ID => $lid });
}
$sth->rows or return { error => Links::language('MODIFY_INVALIDLINKID', $lid), %ret };
}
else {
$sth = $db->select({ $column => $value });
$sth->rows or return { error => Links::language('MODIFY_BADURL', $value), %ret };
}
$link = $sth->fetchrow_hashref;
# Make sure to only allow modifications to validated links. We currently allow
# the user to modify expired links.
unless ($link->{isValidated} eq 'Yes') {
return { error => Links::language('MODIFY_NOLINKS'), %ret };
}
my $new = {%$args};
# Forced system fields (these aren't in the add_system_fields option)
my @system = qw/ID LinkOwner Add_Date Mod_Date Timestmp Date_Checked ExpiryDate ExpiryCounted ExpiryNotify LinkExpired/;
my %system = map { $_ => 1 } @system;
for my $key (keys %cols) {
# Users can't modify system fields, so remove them so the columns don't get
# modified
if (exists $system{$key} or exists $CFG->{add_system_fields}->{$key}) {
delete $new->{$key};
next;
}
# Use the original link value if it hasn't been passed in from cgi. This is
# done to make sure all Links columns pass the column checks (not null, regex,
# etc checks). It has to be done for all columns, since column definitions may
# have changed since the record was originally inserted.
$new->{$key} = $link->{$key} unless defined $args->{$key};
}
# Check that the ExpiryDate is valid for the categories the link is in
if ($CFG->{payment}->{enabled}) {
require Links::Payment;
my $expiry = Links::Payment::check_expiry_date($link);
$new->{ExpiryDate} = $expiry if $expiry;
}
# modify() needs the primary key to perform the update
$new->{ID} = $link->{ID};
Links::init_date();
$new->{Mod_Date} = GT::Date::date_get();
# Backwards compatibility
$new->{Contact_Name} = $args->{Contact_Name} || $args->{'Contact Name'} || ($USER ? $USER->{Name} : '');
$new->{Contact_Email} = $args->{Contact_Email} || $args->{'Contact Email'} || ($USER ? $USER->{Email} : '');
# Setup the language for GT::SQL
local $GT::SQL::ERRORS->{ILLEGALVAL} = Links::language('ADD_ILLEGALVAL');
local $GT::SQL::ERRORS->{UNIQUE} = Links::language('ADD_UNIQUE');
local $GT::SQL::ERRORS->{NOTNULL} = Links::language('ADD_NOTNULL');
local $Links::Table::Links::ERRORS->{NOCATEGORY} = Links::language('MODIFY_NOCATEGORY');
$Links::Table::Links::ERRORS if 0; # silence -w
# On error, file column values need to be restored (since they need to get
# re-uploaded). This is done so that the templates show the correct fields on
# an error.
my %fcols = $db->_file_cols();
for (keys %fcols) {
$ret{$_} = $link->{$_};
}
# Because we store the change request in the Changes table and do not perform
# the modify directly, all the column checks that modify() would normally do
# need to be done now.
my $fset;
unless ($USER and $CFG->{user_direct_mod}) {
if (keys %fcols) {
require GT::SQL::File;
my $file = GT::SQL::File->new({ parent_table => $DB->table('Links'), connect => $DB->{connect} });
$fset = $file->pre_file_actions(\%fcols, $new, $args, $new->{ID}) or return { error => $GT::SQL::error, %ret };
}
# The following block of code modifies $new (so that _check_update() works
# properly), but we don't want that later on, so make a shallow copy of it.
my $new_copy = { %$new };
# This block of code is pulled from GT::SQL::Table::modify (minus the comments)
my $cols = $db->{schema}->{cols};
for my $col (keys %$cols) {
next unless exists $new_copy->{$col};
if ($cols->{$col}->{type} eq 'TIMESTAMP') {
delete $new_copy->{$col};
}
elsif ($cols->{$col}->{type} =~ /^(?:.*INT|INTEGER|FLOAT|REAL|DOUBLE|DECIMAL|DATE|TIME|DATETIME)$/ and defined $new_copy->{$col} and $new_copy->{$col} eq '') {
$new_copy->{$col} = undef;
}
elsif ($cols->{$col}->{not_null} and not (defined $new_copy->{$col} and length $new_copy->{$col})) {
$new_copy->{$col} = undef;
}
}
$db->_check_update($new_copy, { ID => $new_copy->{ID} }) or return { error => $GT::SQL::error, %ret };
}
# Make sure the category id's are valid
$IN->param('CatLinks.CategoryID')
or return { error => Links::language('MODIFY_NOCATEGORY'), %ret };
# Set the Category ID's
my @c_ids = $IN->param('CatLinks.CategoryID');
$new->{'CatLinks.CategoryID'} = $db->clean_category_ids(\@c_ids)
or return { error => $GT::SQL::error, %ret };
# Check if the link is valid
if ($CFG->{user_link_validation}) {
require Links::Tools;
my $status = Links::Tools::link_status($new->{URL});
if ($status and $Links::Tools::STATUS_BAD{$status}) {
return { error => Links::language('MODIFY_BADSTATUS', $Links::Tools::STATUS_BAD{$status}), %ret };
}
}
my $orig_cats = $db->get_categories($new->{ID});
my $new_cats;
# Add the link either directly in, or into the change request table.
if ($USER and $CFG->{user_direct_mod}) {
if ($USER->{Status} ne 'Administrator' and $link->{LinkOwner} ne $USER->{Username}) {
return { error => Links::language('MODIFY_NOTOWNER'), %ret };
}
my $res = $db->modify($new) or return { error => $GT::SQL::error, %ret };
$new_cats = $db->get_categories($new->{ID});
}
else {
require GT::Dumper;
my $chg_db = $DB->table('Changes');
# Remove any columns which haven't changed
for my $key (keys %cols) {
next if not exists $new->{$key} or $key eq 'ID';
delete $new->{$key} if $new->{$key} eq (defined $link->{$key} ? $link->{$key} : '');
}
# Handle updating the expiry date later on (when the admin does the change
# validation). It can't be done here because payments can be made to the link
# before the change validation occurs, losing the user's updated expiry date.
delete $new->{ExpiryDate};
# pre_file_actions() pulls the file columns out of the $new hash; put them back
# in and save the uploaded file(s) in a temporary location for processing upon
# change validation.
foreach my $col (keys %fcols) {
if (exists $fset->{$col}) {
my $fh = $fset->{$col};
my $fname = GT::CGI->escape(get_filename($fh));
my $fpath = "$CFG->{admin_root_path}/tmp/$new->{ID}-$fname";
open F, ">$fpath";
binmode F; binmode $fh;
my $buf;
while (read $fh, $buf, 4096) { print F $buf; };
close F;
$new->{$col} = $fpath;
$new->{"${col}_filename"} = $fset->{"${col}_filename"} || get_filename($fh);
}
elsif (exists $fset->{"${col}_del"}) {
$new->{"${col}_del"} = $fset->{"${col}_del"};
}
}
my $count = $chg_db->count({ LinkID => $new->{ID} });
if ($count) {
my $href = $chg_db->select('ChgRequest', { LinkID => $new->{ID} })->fetchrow;
$href = eval $href;
foreach (keys %fcols) {
my $fpath = $href->{$_} or next;
$fpath ne $new->{$_} or next;
$fpath !~ /\.\./ or next;
$fpath =~ /^[\w\\\/\-\.%]+$/ or next;
-e $fpath or next;
$fpath =~ m,^\Q$CFG->{admin_root_path}\E/tmp/, or next;
unlink $fpath;
}
$chg_db->update({ LinkID => $new->{ID}, Username => $link->{LinkOwner}, ChgRequest => GT::Dumper->dump({ data => $new, var => '' }) }, { LinkID => $new->{ID} })
or return { error => $GT::SQL::error, %ret };
}
else {
$chg_db->insert({ LinkID => $new->{ID}, Username => $link->{LinkOwner}, ChgRequest => GT::Dumper->dump({ data => $new, var => '' }) })
or return { error => $GT::SQL::error, %ret };
}
my $cdb = $DB->table('Category');
foreach my $id (@c_ids) {
my $cat = $cdb->get($id, 'HASH', ['Full_Name']);
$new_cats->{$id} = $cat->{Full_Name};
}
}
# Now email the site admin.
if ($CFG->{admin_email_mod}) {
my %tags;
for my $key (keys %$link) {
$tags{"Original_" . $key} = $link->{$key};
$tags{"New_" . $key} = exists $new->{$key} ? $new->{$key} : $link->{$key};
}
# Pull in the extra fields that might be in $new (eg. extra file data)
for my $key (keys %$new) {
next if exists $tags{"New_" . $key};
$tags{"New_" . $key} = $new->{$key};
}
$tags{Original_Category} = join "\n", sort values %$orig_cats;
$tags{Original_Category_loop} = [sort values %$orig_cats];
$tags{New_Category} = join "\n", sort values %$new_cats;
$tags{New_Category_loop} = [sort values %$new_cats];
$GT::Mail::error ||= '';
Links::send_email('link_modified.eml', \%tags, { admin_email => 1 }) or die "Unable to send message: $GT::Mail::error";
}
$new->{Category} = join("\n", sort values %$new_cats);
$new->{Category_loop} = [sort values %$new_cats];
# All done!
return { %$args, %$link, %$new };
}
sub get_filename {
# -------------------------------------------------------------------
my $fpath = shift;
my @path = split /[\\\/]/, $fpath;
return pop @path;
}
1;

View File

@ -0,0 +1,250 @@
# ==================================================================
# 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: Page.pm,v 1.33 2007/12/19 06:59:12 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::User::Page;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# --------------------------------------------------------------
# Wrap in a subroutine to prevent possible mod_perl probs.
#
$ENV{PATH_INFO} and ($ENV{PATH_INFO} =~ s/.*page\.cgi//);
my $page = $IN->param('g') || $ENV{PATH_INFO} || '';
# We can display a custom template by passing in p=template (the p is for
# page).
my $custom = $IN->param('p') || '';
return generate_custom_page($custom) if $custom;
# Clean up page a little.
$page =~ s|^/+||;
$page =~ s|/+$||;
# Reset the grand total.
$Links::Build::GRAND_TOTAL = 0;
# Figure out what to look for.
my ($new_match) = $CFG->{build_new_url} =~ m{^\Q$CFG->{build_root_url}\E/(.+)};
my ($cool_match) = $CFG->{build_cool_url} =~ m{^\Q$CFG->{build_root_url}\E/(.+)};
my ($rate_match) = $CFG->{build_ratings_url} =~ m{^\Q$CFG->{build_root_url}\E/(.+)};
# Strip out the index.html/more*.html if it is there.
$page =~ s{/*(?:\Q$CFG->{build_home}\E|\Q$CFG->{build_index}\E|\Q$CFG->{build_more}\E\d+\Q$CFG->{build_extension}\E)$}{};
if ($new_match and $page =~ m{^\Q$new_match\E(?:/|$)}) {
$PLG->dispatch('generate_new', \&generate_new_page);
}
elsif ($cool_match and $page =~ m{^\Q$cool_match\E(?:/|$)}) {
$PLG->dispatch('generate_cool', \&generate_cool_page);
}
elsif ($rate_match and $page =~ m{^\Q$rate_match\E/?$}) {
$PLG->dispatch('generate_rate', \&generate_rate_page);
}
# By default the detailed page format in dynamic mode will be
# "<%config.build_detailed_url%>/<%ID%>.<%build_extension%>", but other certain
# formats can be used without breaking other URLs.
elsif ($page =~ /\d+\Q$CFG->{build_extension}\E$/) {
$PLG->dispatch('generate_detailed', \&generate_detailed_page);
}
elsif ($page !~ /\S/) {
$PLG->dispatch('generate_home', \&generate_home_page);
}
elsif ($page =~ /(\w+\.cgi)/) {
print $IN->redirect("$CFG->{db_cgi_url}/$1");
}
else {
$PLG->dispatch('generate_category', \&generate_category_page);
}
}
sub generate_custom_page {
# --------------------------------------------------------
# Displays a custom template.
#
my $page = shift;
if ($CFG->{dynamic_404_status}) {
my $template_set = Links::template_set();
if (! Links::template_exists($template_set, "$page.html")) {
print "Status: 404" . $GT::CGI::EOL;
}
}
print $IN->header();
print Links::SiteHTML::display($page, {});
}
sub generate_home_page {
# --------------------------------------------------------
# Display the home page.
#
print $IN->header();
print Links::Build::build(home => {});
}
sub generate_category_page {
# --------------------------------------------------------
# This routine will display a category, first thing we need
# to do is figure out what category we've been asked for.
#
my $page_num = 1;
my $page = $IN->param('g') || $ENV{PATH_INFO} || '';
$page_num = $1 if $page =~ s{/\Q$CFG->{build_more}\E(\d+)\Q$CFG->{build_extension}\E$}{};
$page =~ s/\Q$CFG->{build_index}\E$//;
$page =~ s|^/+||;
$page =~ s|/+$||;
my $like = $page;
$page =~ y/_/ /;
# Now we get the ID number of the category based on the URL.
my $cat_db = $DB->table('Category');
my $id;
if ($CFG->{build_category_dynamic} eq 'ID' or $page =~ /^\d+$/) {
($id) = $page =~ /(\d+)$/;
# Make sure the ID is valid
$id = $cat_db->select(ID => { ID => $id })->fetchrow;
}
else {
$id = $cat_db->select(ID => { ($CFG->{build_category_dynamic} || 'Full_Name') => $page })->fetchrow;
}
if (!$id) {
# Oops, we may have had a escaped character '_' that wasn't a space. We need
# to look it up manually.
$like =~ y/'"//d;
$id = $cat_db->select(ID => GT::SQL::Condition->new(($CFG->{build_category_dynamic} || 'Full_Name') => LIKE => $like))->fetchrow;
}
# Check for valid sort order.
my %opts;
$opts{id} = $id;
$opts{nh} = $page_num;
$opts{sb} = $IN->param('sb');
$opts{so} = $IN->param('so');
$opts{cat_sb} = $IN->param('cat_sb');
$opts{cat_so} = $IN->param('cat_so');
unless ($opts{sb} and exists $DB->table('Links')->cols->{$opts{sb}} and (not $opts{so} or $opts{so} =~ /^(?:desc|asc)$/i)) {
delete $opts{sb};
delete $opts{so};
}
unless ($opts{cat_sb} and exists $DB->table('Category')->cols->{$opts{cat_sb}} and (not $opts{cat_so} or $opts{cat_so} =~ /^(?:desc|asc)$/i)) {
delete $opts{cat_sb};
delete $opts{cat_so};
}
if ($id) {
print $IN->header();
print Links::Build::build('category', \%opts);
}
else {
print "Status: 404" . $GT::CGI::EOL if $CFG->{dynamic_404_status};
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('PAGE_INVALIDCAT', $page) });
}
}
sub generate_new_page {
# --------------------------------------------------------
# Creates a "What's New" page. Set build_span_pages to 1 in setup
# and it will create a seperate page for each date.
#
my ($page, $date);
$page = $IN->param('g') || $ENV{PATH_INFO} || '';
if ($page =~ /\Q$CFG->{build_index}\E$/) {
$date = '';
}
else {
($date) = $page =~ m{/([^/]+)\Q$CFG->{build_extension}\E$};
}
if ($date) {
my $nh = 1;
my $lpp = $CFG->{build_links_per_page} || 25;
if ($date =~ s/_(\d+)//) {
$nh = $1;
}
print $IN->header();
print Links::Build::build('new_subpage', { date => $date, mh => $lpp, nh => $nh });
}
elsif ($CFG->{build_new_date_span_pages}) {
print $IN->header();
print Links::Build::build('new_index', {});
}
else {
print $IN->header();
print Links::Build::build('new', {});
}
}
sub generate_cool_page {
# --------------------------------------------------------
# Creates a "What's Cool" page.
#
my $page = $IN->param('g') || $ENV{PATH_INFO} || '';
my $nh = 1;
my $mh = $CFG->{build_span_pages} ? $CFG->{build_links_per_page} : 1000;
if ($page =~ /\Q$CFG->{build_more}\E(\d+)\Q$CFG->{build_extension}\E$/) {
$nh = $1;
}
print $IN->header();
print Links::Build::build('cool', { mh => $mh, nh => $nh });
}
sub generate_rate_page {
# --------------------------------------------------------
# Creates a Top 10 ratings page.
#
print $IN->header();
print Links::Build::build('rating', {});
}
sub generate_detailed_page {
# --------------------------------------------------------
# This routine build a single page for every link.
#
my ($page, $id, $link, $detail_match);
$page = $IN->param('g') || $ENV{PATH_INFO} || '';
($id) = $page =~ /(\d+)\Q$CFG->{build_extension}\E$/;
# Fetch the category info if the link is in multiple categories and the category
# the detailed page was accessed from was passed in. This is done so the next
# and previous links are correct.
# Note that due to the URL transformation (Links::clean_output), it isn't
# possible to pass in the CategoryID unless the detailed url is self generated
# (ie. <%detailed_url%> isn't used).
if ($id) {
my $cat_id = $IN->param('CategoryID');
if ($cat_id and $DB->table('CatLinks')->count({ LinkID => $id, CategoryID => $cat_id })) {
$link = $DB->table(qw/Links CatLinks Category/)->select({ LinkID => $id, CategoryID => $cat_id })->fetchrow_hashref;
}
else {
$link = $DB->table('Links')->get($id, 'HASH');
}
}
if (!$link) {
print "Status: 404" . $GT::CGI::EOL if $CFG->{dynamic_404_status};
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('PAGE_INVALIDDETAIL', $page) });
return;
}
print $IN->header();
print Links::Build::build('detailed', $link);
}
1;

View File

@ -0,0 +1,96 @@
# ==================================================================
# 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: Rate.pm,v 1.20 2007/12/19 06:59:12 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::User::Rate;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# ---------------------------------------------------
# Determine what to do.
#
my $id = $IN->param('ID');
# Make sure we are allowed to rate it.
if ($CFG->{user_rate_required} and not $USER) {
print $IN->redirect(Links::redirect_login_url('rate'));
return;
}
# Now figure out what to do.
my $mtl = Links::Build::build('title', Links::language('LINKS_RATE'), "$CFG->{db_cgi_url}/rate.cgi");
if ($IN->param('rate')) {
my $results = $PLG->dispatch('rate_link', \&rate_it, {});
$results->{main_title_loop} = $mtl;
if (defined $results->{error}) {
print $IN->header();
print Links::SiteHTML::display('rate', $results);
}
else {
print $IN->header();
print Links::SiteHTML::display('rate_success', $results);
}
}
elsif (defined $id and ($id =~ /^\d+$/)) {
print $IN->header();
my $rec = $DB->table('Links')->get($id);
unless ($rec) {
print Links::SiteHTML::display('error', { error => Links::language('RATE_INVALIDID', $id), main_title_loop => $mtl });
return;
}
$rec->{detailed_url} = $CFG->{build_detail_url} . '/' . $DB->table('Links')->detailed_url($rec->{ID}) if $CFG->{build_detailed};
print Links::SiteHTML::display('rate', { %$rec, main_title_loop => $mtl });
}
else {
print $IN->redirect($IN->param('d') ? "$CFG->{db_cgi_url}/page.cgi?d=1" : $CFG->{build_root_url} . "/" . ($CFG->{build_home} || ($CFG->{build_index_include} ? $CFG->{build_index} : '')));
}
}
sub rate_it {
# --------------------------------------------------------
# Give this link a rating.
#
my $id = $IN->param('ID');
my $rating = $IN->param('rate');
# Let's get the link information.
my $links = $DB->table('Links');
my $rec = $links->get($id);
$rec or return { error => Links::language('RATE_INVALIDID', $id) };
# Make sure we have a valid rating.
unless ($rating =~ /^\d\d?$/ and $rating >= 1 and $rating <= 10) {
return { error => Links::language('RATE_INVALIDRATE', $rating), %$rec };
}
# Update the rating unless they have already voted.
my $clicktrack = $DB->table('ClickTrack');
my $rows = $clicktrack->count({ LinkID => $id, IP => $ENV{REMOTE_ADDR}, ClickType => 'Rate' });
if ($rows) {
return { error => Links::language('RATE_VOTED', $id), %$rec };
}
else {
eval {
$clicktrack->insert({ LinkID => $id, IP => $ENV{REMOTE_ADDR}, ClickType => 'Rate', Created => \'NOW()' });
$rec->{Rating} = ($rec->{Rating} * $rec->{Votes} + $rating) / ++$rec->{Votes};
$links->update({ Rating => $rec->{Rating}, Votes => $rec->{Votes} }, { ID => $rec->{ID} });
};
return $rec;
}
}
1;

View File

@ -0,0 +1,605 @@
# ==================================================================
# 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: Review.pm,v 1.78 2007/11/16 07:12:57 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::User::Review;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::Build;
use Links::SiteHTML;
sub handle {
# ------------------------------------------------------------------
# Determine what to do.
#
my $input = $IN->get_hash;
if ($input->{add_review}) { $PLG->dispatch('review_add', \&add_review) }
elsif ($input->{edit_review}) { $PLG->dispatch('review_edit', \&edit_review) }
elsif ($input->{helpful}) { $PLG->dispatch('review_helpful', \&helpful_review) }
else { $PLG->dispatch('review_search', \&review_search_results) }
return;
}
# ==================================================================
sub review_search_results {
# ------------------------------------------------------------------
# Display a list of validated reviews for a link
#
my $id = shift;
my $mtl = Links::Build::build('title', Links::language('LINKS_REVIEW'), "$CFG->{db_cgi_url}/review.cgi");
# Get our form data and prepare some default data.
my $args = $IN->get_hash;
$id ||= $args->{ID};
$args->{username} = '\*' if $args->{username} eq '*';
# Return error if no action
unless ($args->{keyword} or $args->{ReviewID} or $id) {
if ($USER) {
$args->{username} ||= $USER->{Username};
$IN->param(username => $args->{username});
}
elsif (!$args->{username} and !$args->{helpful}) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_INVALID_ACTION'), main_title_loop => $mtl });
return;
}
}
# Reset ReviewID to null
my $from_helpful = ($args->{helpful}) ? $args->{ReviewID} : '';
$args->{ReviewID} = '';
# Review must be validated to list
$args->{Review_Validated} = 'Yes';
$args->{nh} = (defined $args->{nh} and $args->{nh} =~ /^(\d+)$/) ? $1 : 1;
$args->{mh} = (defined $args->{mh} and $args->{mh} =~ /^(\d+)$/) ? $1 : $CFG->{reviews_per_page};
$args->{so} = (defined $args->{so} and $args->{so} =~ /^(asc|desc)$/i) ? $1 : $CFG->{review_sort_order};
($args->{sb} and ($args->{sb} =~ /^[\w\s,]+$/) or ($args->{sb} = $CFG->{review_sort_by}));
delete $args->{ma};
my $rec = { noLink => 1 };
# If we are listing reviews of a link
if ($id) {
$id and $args->{ID} = $id;
# Check if ID is valid
$rec = $DB->table('Links')->get($args->{ID});
$rec or do {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_INVALIDID', $args->{ID}), main_title_loop => $mtl });
return;
};
$rec = Links::SiteHTML::tags('link', $rec);
$args->{Review_LinkID} = $args->{ID};
$args->{ww} = 1;
}
# If we have a user to list
elsif ($args->{username}) {
$args->{Review_LinkID} = '';
$args->{Review_Owner} = $args->{username};
$args->{'Review_Owner-opt'} = '=';
}
elsif ($IN->param('ReviewID')) {
$args->{ReviewID} = $IN->param('ReviewID');
$args->{'ReviewID-opt'} = '=';
}
my $reviews = $DB->table('Reviews');
my $review_sth = $reviews->query_sth($args);
my $review_hits = $reviews->hits;
# Return if no results.
unless ($review_hits) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_NORESULTS', $args->{ID} || $args->{username}), main_title_loop => $mtl });
return;
}
my @review_results_loop;
Links::init_date();
my $today = GT::Date::date_get(time, GT::Date::FORMAT_DATETIME);
my %review_cache;
my $last_review = 0;
while (my $review = $review_sth->fetchrow_hashref) {
$review->{Review_Count} = $reviews->count({ Review_LinkID => $review->{Review_LinkID}, Review_Validated => 'Yes' });
$review->{Review_IsNew} = (GT::Date::date_diff($today, $review->{Review_Date}) < $CFG->{review_days_old});
if ($CFG->{review_allow_modify} and $USER->{Username} eq $review->{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 = $review->{Review_ModifyDate} =~ /^0000-00-00 00:00:00/ ? $review->{Review_Date} : $review->{Review_ModifyDate};
if (GT::Date::date_is_greater($date, $timeout)) {
$review->{Review_CanModify} = 1;
}
GT::Date::date_set_format($oldfmt);
}
else {
$review->{Review_CanModify} = 1;
}
}
if ($review->{Review_ModifyDate} ne $review->{Review_Date} and $review->{Review_ModifyDate} !~ /^0000-00-00 00:00:00/) {
$review->{Review_ModifyDate} = GT::Date::date_transform($review->{Review_ModifyDate}, GT::Date::FORMAT_DATETIME, $CFG->{date_review_format});
}
else {
delete $review->{Review_ModifyDate};
}
$review->{Review_Date} = GT::Date::date_transform($review->{Review_Date}, GT::Date::FORMAT_DATETIME, $CFG->{date_review_format});
$review->{Num} = $review->{Review_WasHelpful} + $review->{Review_WasNotHelpful};
($from_helpful eq $review->{ReviewID}) and $review->{last_helpful} = 1;
$CFG->{review_convert_br_tags} and $review->{Review_Contents} = _translate_html($review->{Review_Contents});
# Add the link info to the review
if ($args->{username} or $args->{ReviewID} or $args->{keyword}) {
my $catlink = $DB->table('CatLinks', 'Category', 'Links');
unless (exists $review_cache{$review->{Review_LinkID}}) {
$review_cache{$review->{Review_LinkID}} = $catlink->get({ LinkID => $review->{Review_LinkID} });
}
if ($last_review != $review->{Review_LinkID}) {
my $names = $review_cache{$review->{Review_LinkID}};
$review->{LinkID} = $names->{ID};
$review->{cat_linked} = sub { Links::Build::build('title_linked', { name => $names->{Full_Name}, complete => 1 }) };
$review->{cat_loop} = Links::Build::build('title', $names->{Full_Name});
foreach my $key (keys %$names) {
next if ($key eq 'ID');
exists $review->{$key} or ($review->{$key} = $names->{$key});
}
}
$last_review = $review->{Review_LinkID};
}
push @review_results_loop, $review;
}
my ($toolbar, %paging);
if ($review_hits > $args->{mh}) {
my $url = $CFG->{db_cgi_url} . "/" . $IN->url;
$url =~ s/([;&?]?)nh=(\d+)/($1 and $1 eq '?') ? '?' : ''/eg;
$url =~ s/[;&]helpful=1//eg;
$toolbar = $DB->html($reviews, $args)->toolbar($args->{nh} || 1, $args->{mh} || 25, $review_hits, $url);
%paging = (
url => $url,
num_hits => $review_hits,
max_hits => $args->{mh} || 25,
current_page => $args->{nh} || 1
);
}
else {
$toolbar = '';
}
# Some statistics for review list
my ($review_stats,$review_count);
if (!defined $args->{keyword}) {
if ($args->{username}) {
%$review_stats = map { $_ => $reviews->count({ Review_Owner => $args->{username}, Review_Rating => $_, Review_Validated => 'Yes' }) } (1 .. 5);
$review_count = $reviews->count({ Review_Owner => $args->{username}, Review_Validated => 'Yes'} );
}
else {
%$review_stats = map { $_ => $reviews->count({ Review_LinkID => $args->{ID}, Review_Rating => $_, Review_Validated => 'Yes' }) } (1 .. 5);
$review_count = $reviews->count({ Review_LinkID => $args->{ID}, Review_Validated => 'Yes'});
}
if ($review_count) {
for (1 .. 5) {
$review_stats->{'p' . $_} = $review_stats->{$_} * 150 / $review_count;
}
}
}
$review_stats ||= { noStats => 1 };
print $IN->header();
print Links::SiteHTML::display('review_search_results', {
%$review_stats,
%$rec,
show_link_info => ($args->{username} or $args->{ReviewID} or $args->{keyword}),
main_title_loop => $mtl,
Review_Count => $review_hits,
Review_Loop => \@review_results_loop,
Review_SpeedBar => $toolbar,
paging => \%paging
});
return;
}
sub add_review {
# ------------------------------------------------------------------
# Add a review (only logged in users can add reviews if required)
#
my $id = $IN->param('ID') || '';
my $mtl = Links::Build::build('title', Links::language('LINKS_REVIEW_ADD'), "$CFG->{db_cgi_url}/review.cgi");
# Check if we have a valid ID
my $db = $DB->table('Links');
my $rec = $db->get($id);
unless ($id =~ /^\d+$/ and $rec) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_INVALIDID', $id), main_title_loop => $mtl });
return;
}
$rec = Links::SiteHTML::tags('link', $rec);
$rec->{anonymous} = !$CFG->{user_review_required};
# Only logged in users can add reviews (if required) or must redirect to the login page
if ($CFG->{user_review_required} and !$USER) {
print $IN->redirect(Links::redirect_login_url('review'));
return;
}
my ($cat_id, $cat_name) = each %{$db->get_categories($id)};
my %title = (
title_loop => Links::Build::build('title', "$cat_name/$rec->{Title}"),
title => sub { Links::Build::build('title_unlinked', "$cat_name/$rec->{Title}") },
title_linked => sub { Links::Build::build('title_linked', "$cat_name/$rec->{Title}") }
);
print $IN->header();
# If we have a review to add from a form
if ($IN->param('add_this_review')) {
my $results = $PLG->dispatch('add_this_review', \&_add_this_review, $rec);
# If we have error
if (defined $results->{error}) {
print Links::SiteHTML::display('review_add', { %$results, %$rec, %title, main_title_loop => $mtl });
}
# Return to add success page
else {
print Links::SiteHTML::display('review_add_success', { %$results, %$rec, %title, main_title_loop => $mtl });
}
}
else {
if ($USER) {
my $reviews = $DB->table('Reviews');
my $rc = $reviews->count({ Review_LinkID => $id, Review_Owner => $USER->{Username} });
# Keep pre 3.2.0 behaviour of allowing the user to edit their existing review
if ($rc == 1 and $CFG->{review_max_reviews} == 1) {
my $review = $reviews->select({ Review_LinkID => $id, Review_Owner => $USER->{Username} })->fetchrow_hashref;
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 = $review->{Review_ModifyDate} =~ /^0000-00-00 00:00:00/ ? $review->{Review_Date} : $review->{Review_ModifyDate};
if (not $CFG->{review_allow_modify} or $review->{Review_Validated} eq 'No' or ($CFG->{review_modify_timeout} and GT::Date::date_is_smaller($date, $timeout))) {
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_MAX_REVIEWS', $CFG->{review_max_reviews}), main_title_loop => $mtl });
}
else {
print Links::SiteHTML::display('review_edit', {
%$rec, %title, confirm => 1,
main_title_loop => Links::Build::build('title', Links::language('LINKS_REVIEW_EDIT'), "$CFG->{db_cgi_url}/review.cgi")
});
}
GT::Date::date_set_format($oldfmt);
return;
}
elsif ($CFG->{review_max_reviews} and $rc + 1 > $CFG->{review_max_reviews}) {
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_MAX_REVIEWS', $CFG->{review_max_reviews}), main_title_loop => $mtl });
return;
}
}
# We are displaying an add review form
print Links::SiteHTML::display('review_add', { %$rec, %title, main_title_loop => $mtl });
}
}
sub _add_this_review {
# ------------------------------------------------------------------
# Add this review
#
# Get our form data and some default data.
my $rec = shift;
my $reviews = $DB->table('Reviews');
my $id = $IN->param('ID');
my $input = $IN->get_hash;
$input->{Review_LinkID} = $id;
$input->{Review_Validated} = ($CFG->{review_auto_validate} == 1 and $USER or $CFG->{review_auto_validate} == 2) ? 'Yes' : 'No';
$input->{Review_WasHelpful} = 0 ;
$input->{Review_WasNotHelpful} = 0 ;
$input->{Host} = $ENV{REMOTE_HOST} ? "$ENV{REMOTE_HOST} ($ENV{REMOTE_ADDR})" : $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : 'none';
$input->{Referer} = $ENV{HTTP_REFERER} ? $ENV{HTTP_REFERER} : 'none';
# Get the review owner
$input->{Review_Owner} = $USER ? $USER->{Username} : 'admin';
if (not $CFG->{user_review_required} and not $USER) {
$input->{Review_GuestName} or return { error => Links::language('REVIEW_GUEST_NAME_REQUIRED') };
$input->{Review_GuestEmail} or return { error => Links::language('REVIEW_GUEST_EMAIL_REQUIRED') };
}
# Make sure we have a valid rating.
my $cols = $reviews->cols;
if (exists $cols->{Review_Rating} and $cols->{Review_Rating}->{not_null} and ($input->{Review_Rating} !~ /^\d$/ or $input->{Review_Rating} < 1 or $input->{Review_Rating} > 5)) {
return { error => Links::language('REVIEW_RATING', $input->{Review_Rating}) };
}
# Set date review to today's date.
Links::init_date();
$input->{Review_Date} = GT::Date::date_get(time, GT::Date::FORMAT_DATETIME);
$input->{Review_ModifyDate} = $input->{Review_Date};
# Check that the number of reviews the user owns is under the limit.
if ($USER and $CFG->{review_max_reviews} and
$CFG->{review_max_reviews} < $reviews->count({ Review_LinkID => $id, Review_Owner => $USER->{Username} }) + 1) {
return { error => Links::language('REVIEW_MAX_REVIEWS', $CFG->{review_max_reviews}) };
}
# Change the language.
local $GT::SQL::ERRORS->{ILLEGALVAL} = Links::language('ADD_ILLEGALVAL');
local $GT::SQL::ERRORS->{UNIQUE} = Links::language('ADD_UNIQUE');
local $GT::SQL::ERRORS->{NOTNULL} = Links::language('ADD_NOTNULL');
# Add the review.
# The review will be added only if Review_email_2 is blank
my $added_id = $input->{Review_email_2} ? 1 : $reviews->add($input);
$input->{ReviewID} = $added_id;
unless ($added_id) {
my $error = "<ul><li>" . join("</li><li>", $reviews->error) . "</li></ul>";
return { error => $error };
}
# Format the date for sending email
$input->{Review_Date} = GT::Date::date_transform($input->{Review_Date}, GT::Date::FORMAT_DATETIME, $CFG->{date_review_format});
# Mail the email.
if ($CFG->{admin_email_review_add}) {
Links::send_email('review_added.eml', { %{$USER || {}}, %$input, %$rec }, { admin_email => 1 }) or die "Unable to send mail: $GT::Mail::error";
}
# Review added successfully, return to review_add_success page
$CFG->{review_convert_br_tags} and $input->{Review_Contents} = _translate_html($input->{Review_Contents});
return $input;
}
sub edit_review {
# ------------------------------------------------------------------
# Edit a review (only logged in users can edit their reviews)
#
my $id = $IN->param('ID') || '';
my $rid = $IN->param('ReviewID');
my $mtl = Links::Build::build('title', Links::language('LINKS_REVIEW_EDIT'), "$CFG->{db_cgi_url}/review.cgi");
if (!$CFG->{review_allow_modify}) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_MODIFY_DENIED'), main_title_loop => $mtl });
return;
}
# Only logged in users can update their reviews or must redirect to the login page
if (!$USER) {
print $IN->redirect(Links::redirect_login_url('review'));
return;
}
# Check if we have a valid ID
my $db = $DB->table('Links');
my $rec = $db->get($id);
unless (($id =~ /^\d+$/) and $rec) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_INVALIDID', $id), main_title_loop => $mtl });
return;
}
$rec = Links::SiteHTML::tags('link', $rec);
# If a ReviewID isn't passed in and they have more than one review, then just edit the first review
my $review = $DB->table('Reviews')->select({ Review_LinkID => $id, Review_Owner => $USER->{Username}, $rid ? (ReviewID => $rid) : () })->fetchrow_hashref;
if (!$review) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_NOT_EXISTS', $id), main_title_loop => $mtl });
return;
}
elsif ($review->{Review_Validated} eq 'No') {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_ADD_WAIT', $id), main_title_loop => $mtl });
return;
}
# Has the review modify period passed?
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 = $review->{Review_ModifyDate} =~ /^0000-00-00 00:00:00/ ? $review->{Review_Date} : $review->{Review_ModifyDate};
my $smaller = GT::Date::date_is_smaller($date, $timeout);
GT::Date::date_set_format($oldfmt);
if ($smaller) {
print $IN->header();
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_MODIFY_TIMEOUT', $CFG->{review_modify_timeout}), main_title_loop => $mtl });
return;
}
}
my ($cat_id, $cat_name) = each %{$db->get_categories($id)};
my %title = (
title_loop => Links::Build::build('title', "$cat_name/$rec->{Title}"),
title => sub { Links::Build::build('title_unlinked', "$cat_name/$rec->{Title}") },
title_linked => sub { Links::Build::build('title_linked', "$cat_name/$rec->{Title}") }
);
# If we have a review to update from a form
if ($IN->param('update_this_review')) {
my $results = $PLG->dispatch('update_this_review', \&_update_this_review, $rec);
# If we have error
if (defined $results->{error}) {
print $IN->header();
print Links::SiteHTML::display('review_edit', { %$results, %$rec, %title, main_title_loop => $mtl });
}
# Return to edit success page
else {
print $IN->header();
print Links::SiteHTML::display('review_edit_success', { %$results, %$rec, %title, main_title_loop => $mtl });
}
}
# We are displaying an edit review form
elsif ($IN->param('confirmed')) {
print $IN->header();
print Links::SiteHTML::display('review_edit', { %$rec, %$review, %title, main_title_loop => $mtl });
}
# Else invalid action
else {
return review_search_results();
}
}
sub _update_this_review {
# ------------------------------------------------------------------
# Edit this review
#
# Get our link record.
my $rec = shift;
# Get our form data and some default data.
my $input = $IN->get_hash;
my $reviews = $DB->table('Reviews');
my $id = $IN->param('ID');
$input->{Review_LinkID} = $id;
$input->{Review_Validated} = ($CFG->{review_auto_validate} == 1 and $USER or $CFG->{review_auto_validate} == 2) ? 'Yes' : 'No';
$input->{Review_WasHelpful} = 0 ;
$input->{Review_WasNotHelpful} = 0 ;
$input->{Host} = $ENV{REMOTE_HOST} ? "$ENV{REMOTE_HOST} ($ENV{REMOTE_ADDR})" : $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : 'none';
$input->{Referer} = $ENV{HTTP_REFERER} ? $ENV{HTTP_REFERER} : 'none';
# Get the review owner
$input->{Review_Owner} = $USER->{Username};
# Check if this review is valid for this user
my $rows = $reviews->get({ Review_LinkID => $id, Review_Owner => $USER->{Username}, Review_Validated => 'Yes' });
return { error => Links::language('REVIEW_INVALID_UPDATE') } unless $rows;
# Make sure we have a valid rating.
my $cols = $reviews->cols;
if (exists $cols->{Review_Rating} and $cols->{Review_Rating}->{not_null} and ($input->{Review_Rating} !~ /^\d$/ or $input->{Review_Rating} < 1 or $input->{Review_Rating} > 5)) {
return { error => Links::language('REVIEW_RATING', $input->{Review_Rating}) };
}
# Has the review modify period passed?
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 = $rows->{Review_ModifyDate} =~ /^0000-00-00 00:00:00/ ? $rows->{Review_Date} : $rows->{Review_ModifyDate};
my $smaller = GT::Date::date_is_smaller($date, $timeout);
GT::Date::date_set_format($oldfmt);
if ($smaller) {
return { error => Links::language('REVIEW_MODIFY_TIMEOUT', $CFG->{review_modify_timeout}) };
}
}
# Set date review to today's date.
Links::init_date();
delete $input->{Review_Date};
$input->{Review_ModifyDate} = GT::Date::date_get(time, GT::Date::FORMAT_DATETIME);
# Change the language.
local $GT::SQL::ERRORS->{ILLEGALVAL} = Links::language('ADD_ILLEGALVAL');
local $GT::SQL::ERRORS->{UNIQUE} = Links::language('ADD_UNIQUE');
local $GT::SQL::ERRORS->{NOTNULL} = Links::language('ADD_NOTNULL');
# Update the record.
$reviews->modify($input, { ReviewID => $input->{ReviewID} }) or return { error => $GT::SQL::error };
# Delete the review track from this ReviewID
$DB->table('ClickTrack')->delete({ ReviewID => $input->{ReviewID}, ClickType => 'Review' }) or return { error => $GT::SQL::error };
# Format the date for sending email
$input->{Review_Date} = GT::Date::date_transform($input->{Review_ModifyDate}, GT::Date::FORMAT_DATETIME, $CFG->{date_review_format});
# Mail the email.
if ($CFG->{admin_email_review_mod}) {
my %tags;
foreach my $key (keys %$rows) {
$tags{"Original_$key"} = $rows->{$key};
}
foreach my $key (keys %$input) {
$tags{"New_$key"} = $input->{$key};
}
Links::send_email('review_modified.eml', { %$USER, %tags, %$rec }, { admin_email => 1 }) or die "Unable to send mail: $GT::Mail::error";
}
# Review added successfully, return to review_add_success page
$CFG->{review_convert_br_tags} and $input->{Review_Contents} = _translate_html($input->{Review_Contents});
return $input;
}
sub helpful_review {
# ------------------------------------------------------------------
# Review was helpful or not
#
my $reviewID = $IN->param('ReviewID');
my $mtl = Links::Build::build('title', Links::language('LINKS_REVIEW'), "$CFG->{db_cgi_url}/review.cgi");
# Get our Reviews db object
my $db = $DB->table('Reviews');
my $rec = $db->get($reviewID);
if (!$rec) {
print $IN->header;
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_INVALIDID', $rec->{Review_Subject}), main_title_loop => $mtl });
return;
}
# Update the rating unless they have already voted.
my $click_db = $DB->table('ClickTrack');
my $rows = $click_db->count({ ReviewID => $rec->{ReviewID}, IP => $ENV{REMOTE_ADDR}, ClickType => 'Review' });
if ($rows) {
print $IN->header;
print Links::SiteHTML::display('error', { error => Links::language('REVIEW_VOTED', $rec->{Review_Subject}), main_title_loop => $mtl });
return;
}
else {
eval {
$click_db->insert({ LinkID => $rec->{Review_LinkID}, ReviewID => $rec->{ReviewID}, IP => $ENV{REMOTE_ADDR}, ClickType => 'Review', Created => \"NOW()" });
# Update the Timestmp for the link so that the detailed page gets rebuilt with build changed
$DB->table('Links')->update({ Timestmp => \'NOW()' }, { ID => $rec->{Review_LinkID} });
};
}
# Change the language.
local $GT::SQL::ERRORS->{ILLEGALVAL} = Links::language('ADD_ILLEGALVAL');
local $GT::SQL::ERRORS->{UNIQUE} = Links::language('ADD_UNIQUE');
local $GT::SQL::ERRORS->{NOTNULL} = Links::language('ADD_NOTNULL');
# If this review was helpful
if ($IN->param('yes')) {
if (!$db->update({ Review_WasHelpful => $rec->{Review_WasHelpful} + 1 }, { ReviewID => $reviewID })) {
print $IN->header;
print Links::SiteHTML::display('error', { error => $db->error, main_title_loop => $mtl });
return;
}
}
else {
if (!$db->update({ Review_WasNotHelpful => $rec->{Review_WasNotHelpful} + 1 }, { ReviewID => $reviewID })) {
print $IN->header;
print Links::SiteHTML::display('error', { error => $db->error, main_title_loop => $mtl });
return;
}
}
return review_search_results();
}
sub _translate_html {
# -------------------------------------------------------------------
# Translate contents to html format
#
my $html = shift;
$html = GT::CGI::html_escape($html);
$html =~ s,\r?\n,<br />,g;
return $html;
}
1;

View File

@ -0,0 +1,359 @@
# ==================================================================
# 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: Search.pm,v 1.48 2006/08/08 23:30:09 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::User::Search;
# ==================================================================
use strict;
use Links qw/:objects/;
use Links::SiteHTML;
use Links::Build;
my $time_hires;
sub handle {
#--------------------------------------------------------------------------------
# Determine whether we are displaying the search form, or doing a
# search.
#
my $db = $DB->table('Links');
my $results = {};
my $args = $IN->get_hash;
# Remove search fields we aren't allowed to search on.
my @bad = (@{$CFG->{search_blocked}}, qw/isValidated ExpiryDate/);
for my $col (@bad) {
$col =~ s/^\s*|\s*$//g;
if ($args->{$col}) {
delete $args->{$col};
$IN->delete($col);
}
for (qw(lt gt opt le ge ne)) {
delete $args->{"$col-$_"};
$IN->delete("$col-$_");
}
}
# If query is set we know we are searching.
return search() if defined $args->{query} and $args->{query} =~ /\S/;
# Otherwise, if we pass in a field name, we can search on that too.
foreach (keys %{$db->cols}) {
for my $opt ('', qw/-lt -gt -le -ge -ne/) {
return search() if defined $args->{"$_$opt"} and length $args->{"$_$opt"};
}
}
print $IN->header();
print Links::SiteHTML::display('search', { main_title_loop => Links::Build::build('title', Links::language('LINKS_SEARCH'), "$CFG->{db_cgi_url}/search.cgi") });
}
sub search {
# ------------------------------------------------------------------
# Do the search and print out the results.
#
my $results = $PLG->dispatch('search_results', \&query, {});
if (defined $results->{error}) {
print $IN->header();
$results->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_SEARCH'), "$CFG->{db_cgi_url}/search.cgi");
print Links::SiteHTML::display('search', $results);
}
else {
print $IN->header();
$results->{main_title_loop} = Links::Build::build('title', Links::language('LINKS_SEARCH_RESULTS'), "$CFG->{db_cgi_url}/search.cgi");
print Links::SiteHTML::display('search_results', $results);
}
if ($CFG->{debug_level} > 1) {
print "<blockquote><pre>", GT::SQL->query_stack_disp , "</pre></blockquote>";
}
}
sub query {
# ------------------------------------------------------------------
# Query the database.
#
# First get our search options.
my $args = $IN->get_hash;
if ($args->{query}) {
$args->{query} =~ s/^\s+//;
$args->{query} =~ s/\s+$//;
}
$args->{bool} = (defined $args->{bool} and $args->{bool} =~ /^(and|or)$/i) ? uc $1 : $CFG->{search_bool};
$args->{nh} = (defined $args->{nh} and $args->{nh} =~ /^(\d+)$/) ? $1 : 1;
$args->{mh} = (defined $args->{mh} and $args->{mh} =~ /^\d+$/) ? $args->{mh} : $CFG->{search_maxhits};
$args->{mh} = 200 if $args->{mh} > 200; # Safety limit
$args->{substring} = defined $args->{substring} ? $args->{substring} : $CFG->{search_substring};
$args->{so} = (defined $args->{so} and $args->{so} =~ /^(asc|desc)$/i) ? $1 : '';
$args->{sb} and ($args->{sb} =~ /^[\w\s,]+$/ or ($args->{sb} = ''));
delete $args->{ma};
# Make sure we only search on validated links.
$args->{isValidated} = 'Yes';
$args->{ExpiryDate} = '>=' . time if $CFG->{payment}->{enabled};
my $query = $args->{query} || '';
my $term = $IN->escape($query);
my $links = $DB->table('Links');
my $categories = $DB->table('Category');
# We don't do a category search if we only have a filters.
my $filter = 0;
if (!defined $query or $query eq '') {
$filter = 1;
}
$args->{filter} = $filter;
# Note: if you use this or the search_set_link_callback, remember to $PLG->action(STOP) or your callback won't be used
$args->{callback} = $PLG->dispatch('search_set_cat_callback', sub { return \&_cat_search_subcat if shift }, $args->{catid});
my $orig_sb = $args->{sb};
my $orig_so = $args->{so};
$args->{sb} = $CFG->{build_sort_order_search_cat};
$args->{so} = '';
$filter and $args->{sb} =~ s/\s*,?\s*score//;
my $started;
if (length $query and $CFG->{search_logging} and $args->{nh} == 1) {
if (!defined $time_hires) {
$time_hires = eval { require Time::HiRes } || 0;
}
$started = $time_hires ? Time::HiRes::time() : time;
}
my $cat_sth;
$cat_sth = $categories->query_sth($args) unless $filter;
my $cat_count = $filter ? 0 : $categories->hits();
$args->{callback} = $PLG->dispatch('search_set_link_callback', sub { return \&_search_subcat if shift }, $args->{catid});
$args->{sb} = $orig_sb ? $orig_sb : $CFG->{build_sort_order_search} || '';
$args->{so} = (defined $orig_so and $orig_so =~ /^(asc|desc)$/i) ? $1 : 'ASC';
$filter and $args->{sb} =~ s/\s*,?\s*score//;
# Don't force sorting by whether or not a link is paid, as that would make
# searching almost useless w.r.t. unpaid links since a 1% paid match would be
# higher than a 99% unpaid match.
my $link_sth = $links->query_sth($args);
my $link_count = $links->hits;
# Log the search if it's a new query
if (length $query and $CFG->{search_logging} and $args->{nh} == 1) {
my $elapsed = ($time_hires ? Time::HiRes::time() : time) - $started;
my $results = $link_count || 0;
my $sl = $DB->table('SearchLogs');
my $q = lc $query;
substr($q, 255) = '' if length $q > 255;
if (my $row = $sl->select({ slog_query => $q })->fetchrow_hashref) {
my $slog_time = defined $row->{slog_time}
? ($row->{slog_time} * $row->{slog_count} + $elapsed) / ($row->{slog_count} + 1)
: $elapsed;
$sl->update({
slog_count => $row->{slog_count} + 1,
slog_time => sprintf('%.6f', $slog_time),
slog_last => time,
slog_hits => $results
}, {
slog_query => $q
});
}
else {
$sl->insert({
slog_query => $q,
slog_count => 1,
slog_time => sprintf('%.6f', $elapsed),
slog_last => time,
slog_hits => $results
}) or die "$GT::SQL::error";
}
}
# Return if no results.
unless ($link_count or $cat_count) {
return { error => Links::language('SEARCH_NOLINKS', $term), term => $term };
}
# Now format the category results.
my $count = 0;
my ($category_results, @category_results_loop);
if (!$filter and $cat_count) {
while (my $cat = $cat_sth->fetchrow_hashref) {
last if ($count++ > $args->{mh});
my $title = Links::Build::build('title_linked', { name => $cat->{Full_Name}, complete => 1, home => 0 });
$category_results .= "<li>$title\n";
$cat->{title_linked} = $title;
$cat->{title_loop} = Links::Build::build('title', $cat->{Full_Name});
push @category_results_loop, $cat;
}
}
# And format the link results.
my (@link_results_loop, $link_results, %link_output);
if ($link_count) {
my $results = $link_sth->fetchall_hashref;
$links->add_reviews($results);
@link_results_loop = map Links::SiteHTML::tags('link', $_) => @$results unless $CFG->{build_search_gb};
if ($CFG->{build_search_gb}) {
my @ids = map { $_->{ID} } @$results;
my $catlink = $DB->table('CatLinks','Category');
my %names = $catlink->select('LinkID', 'Full_Name', { LinkID => \@ids })->fetchall_list;
foreach my $link (@$results) {
push @{$link_output{$names{$link->{ID}}}}, Links::SiteHTML::tags('link', $link);
}
}
}
# Join the link results by category if we are grouping.
if ($CFG->{build_search_gb}) {
foreach my $cat (sort keys %link_output) {
$link_output{$cat}->[0]->{title_linked} = sub { Links::Build::build('title_linked', { name => $cat, complete => 1, home => 0 }) };
$link_output{$cat}->[0]->{title_loop} = Links::Build::build('title', $cat);
push @link_results_loop, @{$link_output{$cat}};
}
}
$link_results = sub {
my $links;
$CFG->{build_search_gb} or return join("", map { Links::SiteHTML::display('link', $_) } @link_results_loop);
foreach my $cat (sort keys %link_output) {
my $title = Links::Build::build('title_linked', { name => $cat, complete => 1, home => 0 });
$links .= "<p>$title" . join("", map { Links::SiteHTML::display('link', $_) } @{$link_output{$cat}});
}
return $links;
};
# Generate a toolbar if requested.
my ($toolbar, %paging);
if ($link_count > $args->{mh} or $cat_count > $args->{mh}) {
my $url = $CFG->{db_cgi_url} . "/" . $IN->url;
$url =~ s/([;&?]?)nh=(\d+)/($1 and $1 eq '?') ? '?' : ''/eg;
$toolbar = Links::Build::build(search_toolbar => {
url => $url,
numlinks => $link_count > $cat_count ? $link_count : $cat_count,
nh => $args->{nh},
mh => $args->{mh}
});
%paging = (
url => $url,
num_hits => $link_count > $cat_count ? $link_count : $cat_count,
max_hits => $args->{mh},
current_page => $args->{nh}
);
}
else {
$toolbar = '';
}
# Print the output.
my $results = {
link_results => $link_results,
link_results_loop => \@link_results_loop,
category_results => $category_results,
category_results_loop => \@category_results_loop,
link_hits => $link_count,
cat_hits => $cat_count,
next => $toolbar,
paging => \%paging,
term => $term,
highlight => $CFG->{search_highlighting}
};
return $results;
}
sub _search_subcat {
# -------------------------------------------------------------------
# First argument is the query/table object, second argument is the current
# result set (note: can be quite large). Must return a new result set.
#
my ($query, $results) = @_;
return $results unless (keys %$results); # No matches.
my $cat_db = $DB->table('Category');
my $catlink_db = $DB->table('CatLinks', 'Category');
# We need the full name of the category.
my @cat_ids = $IN->param('catid') or return $results;
my (@children, %seen);
foreach my $id (@cat_ids) {
next if ($id !~ /^\d+$/);
my $child = $cat_db->children($id) or next;
push @children, @$child, $id;
}
@children or return $results;
@children = grep !$seen{$_}++, @children;
# Now do the joined query.
my %filtered = map { $_ => $results->{$_} }
$catlink_db->select(LinkID => { CategoryID => \@children, LinkID => [keys %$results] })->fetchall_list;
return \%filtered;
}
sub _search_subcat_and {
# -------------------------------------------------------------------
# Search subcategories using AND.
#
my ($query, $results) = @_;
return $results unless (keys %$results); # No matches
my $cat_db = $DB->table('Category');
my $catlink_db = $DB->table('CatLinks', 'Category');
# We need the full name of the category.
my @cat_ids = $IN->param('catid') or return $results;
my %final = %$results;
foreach my $id (@cat_ids) {
next unless ($id =~ /^\d+$/);
my @children;
my $childs = $cat_db->children($id);
push @children, @$childs, $id;
my $cond = GT::SQL::Condition->new(
CategoryID => 'IN' => \@children,
LinkID => 'IN' => [ keys %final ]
);
%final = ();
my $sth = $catlink_db->select($cond, ['LinkID']);
while (my $link_id = $sth->fetchrow_array) {
$final{$link_id} = $results->{$link_id};
}
last unless keys %final;
}
return \%final;
}
sub _cat_search_subcat {
# -------------------------------------------------------------------
# First argument is the query/table object, second argument is the current
# result set (note: can be quite large). Must return a new result set.
#
my ($query, $results) = @_;
return $results unless (keys %$results); # No matches.
my $cat_db = $DB->table('Category');
my @cat_ids = $IN->param('catid') or return $results;
my (@children, %seen);
foreach my $id (@cat_ids) {
next if ($id !~ /^\d+$/);
my $child = $cat_db->children($id) or next;
push @children, @$child, $id;
}
@children or return $results;
@children = grep { ! $seen{$_}++ } @children;
my %subcats = map { $_ => 1 } @children;
my $filtered = {};
while (my ($k, $s) = each %$results) {
$filtered->{$k} = $s if (exists $subcats{$k});
}
return $filtered;
}
1;

View File

@ -0,0 +1,119 @@
# ==================================================================
# 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: Treecats.pm,v 1.3 2006/09/12 06:07:12 brewt Exp $
#
# Copyright (c) 2006 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
package Links::User::Treecats;
# ==================================================================
use strict;
use Links qw/:objects/;
sub handle {
# Fetch these categories (and select them)
my @cid = $IN->param('cid');
# Fetch these links (and select them)
my @lid = $IN->param('lid');
# Fetch these categories
my @id = $IN->param('id');
# Fetch links as well as Categories
my $fetchlinks = $IN->param('links');
my $category = $DB->table('Category');
my $catlinks = $DB->table('CatLinks', 'Links');
# Fetching selected categories
if (@cid) {
@lid = ();
@id = @cid;
$fetchlinks = 0;
}
# Fetching selected links
elsif (@lid) {
# Get all the categories that the links are in
@id = $catlinks->select('CategoryID', { LinkID => \@lid }, VIEWABLE)->fetchall_list;
$fetchlinks = 1;
}
# Fetching categories/links
else {
@cid = ();
@lid = ();
@id = (0) unless @id;
}
my %vars;
# Only allow the use of treecats.cgi if db_gen_category_list == 2 or if
# treecats_enabled (hidden config option) is true
if ($CFG->{db_gen_category_list} != 2 and not $CFG->{treecats_enabled}) {
$vars{error} = 'Permission denied - treecats is currently disabled.';
}
else {
my @fetchlinks;
my $cond;
if (@cid or @lid) {
my $parents = $category->parents(\@id);
my @ids;
my @fids = (0);
for (keys %$parents) {
# Fetch all the parents and their children
push @ids, @{$parents->{$_}};
push @fids, @{$parents->{$_}};
# Fetch the category itself
push @ids, $_;
# When pre-selecting links, @id contains the category the link(s) are in. To
# completely draw the tree, the children of those categories need to be
# retreived as well.
if (@lid) {
push @fids, $_;
push @fetchlinks, $_;
}
push @fetchlinks, @{$parents->{$_}};
}
$cond = GT::SQL::Condition->new(ID => IN => \@ids, FatherID => IN => \@fids);
$cond->bool('OR');
}
else {
push @fetchlinks, @id;
$cond = GT::SQL::Condition->new(FatherID => IN => \@id);
}
$category->select_options("ORDER BY Full_Name");
$vars{categories} = $category->select($cond)->fetchall_hashref;
# Find the children counts of all the categories and check if they should be selected or not
my @cats;
for (@{$vars{categories}}) {
push @cats, $_->{ID};
}
$category->select_options("GROUP BY FatherID");
my %children = $category->select('FatherID', 'COUNT(*)', { FatherID => \@cats })->fetchall_list;
my %selected = map { $_ => 1 } @cid;
for (@{$vars{categories}}) {
$_->{children} = $children{$_->{ID}} || 0;
$_->{selected} = $selected{$_->{ID}} || 0;
}
if ($fetchlinks and @fetchlinks) {
# Remove CategoryID = 0 (shouldn't normally happen)
@fetchlinks = grep $_, @fetchlinks;
$catlinks->select_options("ORDER BY CategoryID, Title");
$vars{links} = $catlinks->select({ CategoryID => \@fetchlinks }, VIEWABLE)->fetchall_hashref;
%selected = map { $_ => 1 } @lid;
for (@{$vars{links}}) {
$_->{selected} = $selected{$_->{ID}} || 0;
}
}
}
print $IN->header('text/xml');
print Links::user_page('treecats.xml', \%vars);
}
1;

View File

@ -0,0 +1,585 @@
# ==================================================================
# 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;

View File

@ -0,0 +1,113 @@
# ==================================================================
# 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: mod_perl.pm,v 1.34 2005/03/28 22:58:07 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::mod_perl;
# ==================================================================
use strict();
# If under mod_perl, we use Apache::DBI to cache connections.
use GT::Base qw/MOD_PERL/;
BEGIN {
require Apache::DBI if MOD_PERL;
print STDERR "\nPreloading Gossamer Links modules into mod_perl:\n\t";
}
use Links();
BEGIN { print STDERR " ." }
use Links::Config();
# Preload commonly used GT libs.
use constants();
use GT::Cache();
use GT::CGI();
use GT::Date();
use GT::Lock;
BEGIN { print STDERR " ." }
use GT::Dumper();
use GT::FileMan();
use GT::Mail();
use GT::Mail::BulkMail();
use GT::MD5();
use GT::MD5::Crypt();
use GT::MIMETypes();
BEGIN { print STDERR " ." }
use GT::SQL();
use GT::SQL::Admin();
use GT::SQL::File();
use GT::SQL::Relation();
use GT::SQL::Search();
use GT::SQL::Display::HTML::Table();
use GT::SQL::Display::HTML::Relation();
use GT::SQL::Search::Base::Common();
use GT::SQL::Search::Base::Indexer();
use GT::SQL::Search::Base::STH();
use GT::SQL::Search::Base::Search();
BEGIN { print STDERR " ." }
use GT::Socket::Client();
use GT::TempFile();
use GT::Plugins();
use GT::Plugins::Author();
use GT::Plugins::Installer();
use GT::Plugins::Manager();
use GT::Template();
use GT::Template::Editor();
use GT::Template::Parser();
use GT::WWW();
BEGIN { print STDERR " ." }
# Preload Gossamer Links modules.
use Links::Admin();
use Links::Authenticate();
use Links::Bookmark();
use Links::Browser();
use Links::Build();
use Links::Bookmark();
use Links::Config();
use Links::Newsletter();
use Links::Parallel();
use Links::Payment();
use Links::Plugins();
BEGIN { print STDERR " ." }
use Links::SQL();
use Links::SiteHTML();
use Links::Tools();
use Links::Utils();
use Links::Browser::Controller();
use Links::Browser::JFunction();
use Links::Table::Category();
use Links::Table::Links();
use Links::Table::Users();
use Links::HTML::Category();
use Links::HTML::Links();
use Links::HTML::Users();
BEGIN { print STDERR " ." }
use Links::User::Add();
use Links::User::Editor();
use Links::User::Jump();
use Links::User::Login();
use Links::User::Modify();
use Links::User::Page();
use Links::User::Rate();
use Links::User::Review();
use Links::User::Search();
BEGIN { print STDERR " .\nAll modules loaded ok!\n" }
print STDERR "Compiling all functions ...";
GT::AutoLoader::compile_all();
print STDERR " All modules compiled and loaded okay!\n\n";
1;

View File

@ -0,0 +1,23 @@
sub {
my $related = shift || return;
my @ids = split ("\n",$related);
my @loop;
my $db = $DB->table('Links');
my $tags = GT::Template->tags;
my $id = $tags->{ID};
my $cond = GT::SQL::Condition->new('RelatedArticles','like',$id . "\n%");
$cond->add('RelatedArticles','like', "%\n" . $id . "\n%");
$cond->add('RelatedArticles','like', "\n" . $id);
use Data::Dumper;
print Dumper($cond);
#my $sth = $db->select($cond);
require Links::SiteHTML;
foreach my $id (@ids) {
my $link = $db->get($id);
$link = Links::SiteHTML::tags('link',$link);
push @loop, $link;
}
return { related_articles_loop => \@loop };
}