discourse-legacysite-perl/site/slowtwitch.com/cgi-bin/articles/admin/Links/Upgrade.pm
2024-06-17 21:49:12 +10:00

1035 lines
42 KiB
Perl

# ==================================================================
# 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: Upgrade.pm,v 1.50 2009/05/11 05:57:45 brewt Exp $
#
# Copyright (c) 2005 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
package Links::Upgrade;
use strict;
use vars qw/%VERSION_TREE @VERSION_HIDDEN/;
use Carp;
BEGIN {
# 1.01 below should be updated if this file depends on fixes/additions to GT::SQL::Upgrade
if (exists $INC{'GT/SQL/Upgrade.pm'} and $GT::SQL::Upgrade::VERSION < 1.01) {
delete $INC{'GT/SQL/Upgrade.pm'};
}
}
use GT::SQL::Upgrade;
# This has to be updated every release so that an upgrade can "walk" the tree
# to find any upgrade code.
%VERSION_TREE = (
'2.0.0' => '2.0.1',
'2.0.1' => '2.0.2',
'2.0.2' => '2.0.3',
'2.0.3' => '2.0.4',
'2.0.4' => '2.0.5',
'2.0.5' => '2.1.0',
'2.1.0' => '2.1.1',
'2.1.1' => '2.1.2',
'2.1.2' => '2.2.0',
'2.2.0' => '2.2.1',
'2.2.1' => '2.99.0',
'2.99.0' => '2.99.1',
'2.99.1' => '3.0.0',
'3.0.0' => '3.0.1',
'3.0.1' => '3.0.2',
'3.0.2' => '3.0.3',
'3.0.3' => '3.0.4',
'3.0.4' => '3.1.0',
'3.1.0' => '3.2.0',
'3.2.0' => '3.3.0',
);
# These versions won't show up in the available upgrade list returned by
# upgrades_available().
@VERSION_HIDDEN = ('2.99.0', '2.99.1');
sub PERFORM ($$) { "\nPerforming " . (substr($_[0], 0, 1) == 2 ? "Links SQL" : "Gossamer Links") . " $_[0] -> $_[1] upgrades...\n" }
sub DONE ($$) { (substr($_[0], 0, 1) == 2 ? "Links SQL" : "Gossamer Links") . " $_[0] -> $_[1] upgrades performed.\n\n" }
# In list context, returns a list of versions that are available to be upgraded
# from. In scalar context, returns a hash reference containing a
# upgrades_available key with a value of an array reference containing these
# versions (i.e. for use in a template). Only versions from %VERSION_TREE are
# included that actually have some upgrade code - in other words, 2.0.1 won't
# be includeded because there is no actual 2.0.1 upgrade code.
sub upgrades_available {
my %skip = map { $_ => 1 } @VERSION_HIDDEN;
my @avail =
map $_->[0],
sort { for my $i (1 .. (@$a > @$b ? @$a : @$b)) { my $c = $a->[$i] <=> $b->[$i]; return $c if $c } 0 }
map { ($skip{$_} or !__PACKAGE__->can_upgrade($_)) ? () : [$_, split /\./] }
keys %VERSION_TREE;
return wantarray ? @avail : { upgrades_available => \@avail };
}
# Usage:
# Links::Upgrade->upgrade(
# from => $version, # e.g. 2.2.1
# output => $coderef, # code reference will be called with any output
# config => $config, # config object or hash reference (may be changed by upgrade code)
# );
sub upgrade {
my $class = shift;
my %opts = @_;
for (qw/from config/) {
$opts{$_} or croak "Links::Upgrade->upgrade requires a '$_' option";
}
my ($version) = $opts{from} =~ /(\d+\.\d+\.\d+)/;
$version or croak "Invalid version passed to Links::Upgrade->upgrade: '$opts{from}'";
ref $opts{config} eq 'HASH' or UNIVERSAL::isa($opts{config}, 'GT::Config') or croak "Invalid 'config' value passed to Links::Upgrade->upgrade: '$opts{config}' not a hash reference or GT::Config object.";
not $opts{output} or ref $opts{output} eq 'CODE' or croak "Invalid 'output' value passed to Links::Upgrade->upgrade: '$opts{output}' not a code reference.";
$opts{output} ||= sub { print @_ };
my $safety;
while ($version) {
if (my $sub = $class->can_upgrade($version)) {
$sub->($opts{output}, $opts{config});
}
$safety++ < 100 or croak "Internal upgrade error: $version => $VERSION_TREE{$version} appears to be recursing.";
}
continue { $version = $VERSION_TREE{$version} }
# Walk the version upgrade tree
1;
}
# Takes a version, returns a code reference if the version -> next version
# upgrade code exists, undef otherwise.
sub can_upgrade {
my ($class, $from) = @_;
my $to = $VERSION_TREE{$from};
for ($from, $to) { y/./_/ }
$class->can("upgrade__${from}__$to");
}
# Called from GT::Template with the version to upgrade from. Returns either an
# error tag or a message tag.
sub browser_upgrade {
my ($from, $stream) = @_;
$from and $from =~ /\d\.\d+\.\d/ or return { error => 'Invalid upgrade version entered.' };
my $ret = '';
__PACKAGE__->upgrade(
from => $from,
config => $Links::CFG,
output => sub { if ($stream) { print @_ } else { $ret .= join '', @_ } }
);
return {
upgrade_successful => 1,
upgrade_result => $ret
};
}
# Although not strictly upgrade-specific (you can force a tree rebuild) it is
# here as it is primarily an upgrade feature.
# Takes 2-3 arguments - an output subroutine, a GT::SQL object, and an optional
# force value - if specified and true, a rebuild will be forced.
sub create_cat_tree {
my ($out, $DB, $force) = @_;
$out ||= sub { print @_ };
require GT::SQL::Tree;
require GT::SQL::Tree::Rebuild;
my $t = $DB->table('Category');
my %roots;
my $rebuild = GT::SQL::Tree::Rebuild->new(
table => $t,
order_by => 'Full_Name', # Ensure that parents come before children
cols => [qw/ID FatherID Full_Name/],
missing_root => sub {
my ($row, $table) = @_;
my ($id, $father) = @$row{qw/ID FatherID/};
if (!$father) {
return $roots{$id} = 0;
}
my $root;
if (exists $roots{$father}) {
return $roots{$id} = $roots{$father} || $father;
}
else {
die "No parent category found for $row->{Full_Name}! Your Category table is corrupted.";
}
},
missing_depth => sub {
my ($row, $table) = @_;
my $full_name = $row->{Full_Name};
return $row->{Full_Name} =~ y|/||;
}
);
my $e = $DB->editor('Category');
$out->("Adding Category tree...\n");
my $ret = $e->add_tree(father => "FatherID", root => "CatRoot", depth => "CatDepth", force => $force ? 'force' : 'check', rebuild => $rebuild);
$out->($ret ? "\tOkay!\n" : "\tAn error occured: $GT::SQL::error\n");
$ret;
}
sub browser_cat_tree {
my $stream = shift;
my $message;
my $okay = create_cat_tree(sub { if ($stream) { print @_ } else { for (@_) { $message .= $_ } } }, $Links::DB, 1);
return { browser_cat_tree_success => $okay, browser_cat_tree_message => $message };
}
sub upgrade__3_2_0__3_3_0 {
# ---------------------------------------------------------------
# Upgrade from 3.2.0 to 3.3.0
#
my ($out, $cfg) = @_;
$out->(PERFORM '3.2.0' => '3.3.0');
import lib $cfg->{admin_root_path};
require GT::SQL;
require GT::SQL::Table;
$Links::DB = my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
require GT::Config;
my $lang = GT::Config->load("$cfg->{admin_root_path}/templates/admin/language.txt");
# Gossamer Links German fix
if ($cfg->{date_review_format} eq '%dd.%mm%.%yyyy% %HH%:%MM%') {
$cfg->{date_review_format} = '%dd%.%mm%.%yyyy% %HH%:%MM%';
}
# These options were split into two, so they should retain the same value as the original option
if ($cfg->{build_new_date_span_pages} ne $cfg->{build_span_pages}) {
$cfg->{build_new_date_span_pages} = $cfg->{build_span_pages};
}
if ($cfg->{email_review_add} ne $cfg->{email_add}) {
$cfg->{email_review_add} = $cfg->{email_add};
}
$out->("Turning on build_format_compat...\n\tOkay!\n");
$cfg->{build_format_compat} = 2;
# Add new Reviews subclass
$out->("Adding Reviews subclass...\n");
my $t = $DB->table('Reviews');
$t->subclass(
table => { Reviews => "Links::Table::Reviews" }
);
$t->save_state();
$out->("\tOkay!\n");
$out->(DONE '3.2.0' => '3.3.0');
}
sub upgrade__3_1_0__3_2_0 {
# ---------------------------------------------------------------
# Upgrade from 3.1.0 to 3.2.0
#
my ($out, $cfg) = @_;
$out->(PERFORM '3.1.0' => '3.2.0');
import lib $cfg->{admin_root_path};
require GT::SQL;
require GT::SQL::Table;
$Links::DB = my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
require GT::Config;
my $lang = GT::Config->load("$cfg->{admin_root_path}/templates/admin/language.txt");
$out->("Updating PayPal postback check...\n");
for my $postback (@{$cfg->{payment}->{postback}}) {
next unless $postback->{method} eq 'PayPal';
$postback->{var} = 'txn_type';
last;
}
$out->("\tOkay!\n");
$out->("Updating review e-mail settings...\n");
if ($cfg->{admin_email_review_add}) {
$cfg->{admin_email_review_add} = $cfg->{admin_email_add};
}
if ($cfg->{admin_email_review_mod}) {
$cfg->{admin_email_review_mod} = $cfg->{admin_email_mod};
}
$out->("\tOkay!\n");
$out->("Removing old browser templates from admin template set...\n");
my $dir = "$cfg->{admin_root_path}/templates/admin";
opendir TPL, $dir or die "Could not open '$dir': $!";
while (defined(my $file = readdir TPL)) {
next unless -f "$dir/$file" and $file =~ /^browser.*\.html$/;
unlink "$dir/$file";
}
$out->("\tOkay!\n");
add_column($out, $DB, Sessions => session_expires => { type => 'TINYINT', default => 1 });
add_column($out, $DB, Reviews => Review_ModifyDate => { type => 'DATETIME', not_null => 1, default => '0000-00-00 00:00:00', form_display => $lang->{prompt_Review_ModifyDate} });
alter_column($out, $DB, Reviews => Review_Date => { type => 'DATETIME', not_null => 1, form_display => $lang->{prompt_Review_Date} });
$out->(DONE '3.1.0' => '3.2.0');
}
sub upgrade__3_0_4__3_1_0 {
# -----------------------------------------------------------------------------
# Upgrade from 3.0.4 to 3.0.5
my ($out, $cfg) = @_;
$out->(PERFORM '3.0.4' => '3.1.0');
import lib $cfg->{admin_root_path};
require GT::SQL;
require GT::SQL::Table;
$Links::DB = my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
require GT::Config;
my $lang = GT::Config->load("$cfg->{admin_root_path}/templates/admin/language.txt");
drop_index($out, $DB, CatLinks => 'catlnndx');
$out->("Scanning for and removing duplicate entries from CatLinks table...\n");
# Do some hackery to get a non-subclassed CatLinks table
#my $catlinks = $DB->table('CatLinks');
my $catlinks = GT::SQL::Table->new(
name => $DB->prefix . 'CatLinks',
connect => $DB->{connect},
debug => $DB->{_debug},
_err_pkg => 'GT::SQL::Table'
);
$catlinks->select_options('GROUP BY LinkID, CategoryID', 'HAVING COUNT(*) > 1');
my $sth = $catlinks->select(qw[LinkID CategoryID COUNT(*)]);
my $count;
while (my ($linkid, $catid) = $sth->fetchrow) {
my $deleted = $catlinks->delete({ LinkID => $linkid, CategoryID => $catid });
$count += $deleted - 1;
$catlinks->insert({ LinkID => $linkid, CategoryID => $catid });
}
$out->("\tOkay! " . ($count ? "$count duplicate entries found and removed.\n" : "No duplicate entries found.\n"));
add_unique($out, $DB, CatLinks => { cl_cl_q => [qw/CategoryID LinkID/] });
if ($cfg->{updates}) {
$out->("Moving update data from Links/Config/Data.pm to Links/Config/Updates.pm\n");
my $cfg_updates = delete $cfg->{updates};
require GT::Config;
my $updates = GT::Config->load("$cfg->{admin_root_path}/Links/Config/Updates.pm", { create_ok => 1 });
for (keys %$cfg_updates) {
$updates->{$_} ||= $cfg_updates->{$_};
}
$updates->save;
$out->("\tOkay!\n");
}
add_column($out, $DB, Links => LinkExpired => { type => 'INT', form_display => $lang->{prompt_LinkExpired}, form_type => 'hidden' });
add_index($out, $DB, Category => { c_p => ['Payment_Mode'] });
$out->(DONE '3.0.4' => '3.1.0');
}
sub upgrade__3_0_3__3_0_4 {
# ---------------------------------------------------------------
# Upgrade from 3.0.3 to 3.0.4
#
my ($out, $cfg) = @_;
$out->(PERFORM '3.0.3' => '3.0.4');
$out->(DONE '3.0.3' => '3.0.4');
}
sub upgrade__3_0_2__3_0_3 {
# ---------------------------------------------------------------
# Upgrade from 3.0.2 to 3.0.3
#
my ($out, $cfg) = @_;
$out->(PERFORM '3.0.2' => '3.0.3');
$out->(DONE '3.0.2' => '3.0.3');
}
sub upgrade__3_0_1__3_0_2 {
# ---------------------------------------------------------------
# Upgrade from 3.0.1 to 3.0.2
#
my ($out, $cfg) = @_;
$out->(PERFORM '3.0.1' => '3.0.2');
$out->(DONE '3.0.1' => '3.0.2');
}
sub upgrade__3_0_0__3_0_1 {
# ---------------------------------------------------------------
# Upgrade from 3.0.0 to 3.0.1
#
my ($out, $cfg) = @_;
$out->(PERFORM '3.0.0' => '3.0.1');
import lib $cfg->{admin_root_path};
require GT::SQL;
$Links::DB = my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
recreate_table($out, $DB, 'MailingListIndex', sub { my $table = shift; my $cols = $table->cols; $cols->{Name}->{type} eq 'TEXT' },
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'
);
if (-e(my $oldconfig = "$cfg->{admin_root_path}/Links/ConfigData.pm")) {
$out->("Removing old Links/ConfigData.pm file (has been replaced with Links/Config/Data.pm)...\n");
require GT::File::Tools;
my $ret = GT::File::Tools::move($oldconfig, "$oldconfig.old");
$out->($ret ? "\tOkay!\n" : "\tAn error occured: $!\n");
}
$out->(DONE '3.0.0' => '3.0.1');
}
sub upgrade__2_99_1__3_0_0 {
# -----------------------------------------------------------------------------
# Placeholders that currently just prints a 2.2.1 -> 3.0.0 success message. If
# a 2.99.1 is released, this will become 2_99_1__3_0_0 and so on. This allows
# transparent handling of the beta versions without duplicating any code and
# without needing to mention the beta in the upgrade output.
#
my ($out, $cfg) = @_;
$out->("Updating build_static_path, _url...\n");
$cfg->{build_static_path} ||= "$cfg->{build_root_path}/static";
$cfg->{build_static_url} ||= "$cfg->{build_root_url}/static";
$out->("\tOkay!\n");
$out->(DONE '2.2.1' => '3.0.0');
}
sub upgrade__2_99_0__2_99_1 {
# ---------------------------------------------------------------
# Upgrade from 2.99.0 to 2.99.1
#
my ($out, $cfg) = @_;
import lib $cfg->{admin_root_path};
require GT::Config;
my $lang = GT::Config->load("$cfg->{admin_root_path}/templates/admin/language.txt");
require GT::SQL;
$Links::DB = my $DB = GT::SQL->new("$cfg->{admin_root_path}/defs");
# Drop unnecessary Bookmark columns added to the Users table in 2.99.0
my $usercols = $DB->table('Users')->cols;
for (qw/FolderSortField FolderSortOrd/) {
drop_column($out, $DB, 'Users', $_) if exists $usercols->{$_};
}
delete $cfg->{bookmark_folder_sort};
delete $cfg->{bookmark_folder_sort_order};
delete $cfg->{bookmark_user_sort};
delete $cfg->{bookmark_user_sort_order};
# Don't print here - the final 2.99.x -> 3.0.0 code prints the final message.
1;
}
sub upgrade__2_2_1__2_99_0 {
# ---------------------------------------------------------------
# Upgrade from 2.2.1 to 2.99.0
#
my ($out, $cfg) = @_;
$out->(PERFORM '2.2.1' => '3.0.0');
import lib $cfg->{admin_root_path};
require GT::Config;
my $lang = GT::Config->load("$cfg->{admin_root_path}/templates/admin/language.txt");
require GT::SQL;
$Links::DB = my $DB = GT::SQL->new("$cfg->{admin_root_path}/defs");
$Links::STASH{clicktrack_cleanup} = 1;
$Links::STASH{expired_links} = 1;
require Links::Plugins;
require GT::Plugins::Manager;
my $plugin = GT::Config->load("$cfg->{admin_root_path}/Plugins/plugin.cfg", { create_ok => 1 });
{
package FakeCGI;
sub param { $_[0]->{$_[1]} }
}
add_column($out, $DB, Category => CatRoot => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_type => 'hidden' });
add_column($out, $DB, Category => CatDepth => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_type => 'hidden' });
add_column($out, $DB, Category => Direct_Links => { type => 'INT', not_null => 1, default => 0, form_display => $lang->{prompt_Direct_Links} });
create_cat_tree($out, $DB);
my $t = $DB->table('Category');
$out->("Updating Category fk to reference itself...\n");
my $ret = $t->fk(Category => { FatherID => 'ID' });
$out->($ret ? "\tOkay!\n" : "\tAn error occured: $GT::SQL::error\n");
$out->("Updating CatLinks subclass...\n");
$t = $DB->table('CatLinks');
$t->subclass(
table => { CatLinks => "Links::Table::CatLinks" }
);
$t->save_state();
$out->("\tOkay!\n");
$out->("Updating ClickTrack subclass...\n");
$t = $DB->table('ClickTrack');
$t->subclass(
table => { ClickTrack => "Links::Table::ClickTrack" }
);
$t->save_state();
$out->("\tOkay!\n");
$out->("Updating Direct_Links values...\n");
my $rel = $DB->table(qw/CatLinks Links/);
$rel->select_options("GROUP BY CategoryID");
my $where = GT::SQL::Condition->new(isValidated => '=' => 'Yes');
$where->add(ExpiryDate => '>=' => time) if $cfg->{payment}->{enabled};
my %catlinks = $rel->select(qw/CategoryID COUNT(ID)/ => $where)->fetchall_list;
$t = $DB->table('Category');
for (keys %catlinks) {
$t->update({ Direct_Links => $catlinks{$_} }, { ID => $_ }, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
}
$out->("\tOkay!\n");
if ($plugin and exists $plugin->{Bookmark}) {
my $bcfg = Links::Plugins::get_plugin_user_cfg('Bookmark');
$out->("Bookmark plugin detected, importing Bookmark settings...\n");
$out->("\tImporting Bookmark configuration...\n");
for (keys %$bcfg) {
$cfg->{"bookmark_$_"} = $bcfg->{$_};
}
$out->("\t\tDone!\n");
$out->("\tUninstalling Bookmark plugin...\n");
my $fakein = bless { plugin_name => "Bookmark", skip_uninstall => 1 }, "FakeCGI";
my $man = new GT::Plugins::Manager(cgi => $fakein, plugin_dir => "$cfg->{admin_root_path}/Plugins");
$ret = $man->uninstall;
$out->($ret->{error} ? "\t\tAn error occured: $ret->{error}\n" : "\t\tDone!\n");
}
add_table($out, $DB, 'Bookmark_Folders',
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' }
}
);
add_table($out, $DB, 'Bookmark_Links',
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' },
}
);
# Commented out columns were removed in 2.99.1
# add_column($out, $DB, Users => FolderSortField => { type => 'VARCHAR', size => 255, not_null => 1, regex => '^[\s\w]+$', default => 'my_folder_name', form_display => $lang->{prompt_FolderSortField} });
# add_column($out, $DB, Users => FolderSortOrd => { type => 'ENUM', values => ['ASC', 'DESC'], not_null => 1, default => 'ASC', form_display => $lang->{prompt_FolderSortOrd} });
add_column($out, $DB, Users => SortField => { type => 'VARCHAR', size => 255, not_null => 1, regex => '^[\s\w]+$', default => 'Title', form_display => $lang->{prompt_SortField} });
add_column($out, $DB, Users => SortOrd => { type => 'ENUM', values => ['ASC', 'DESC'], not_null => 1, default => 'ASC', form_display => $lang->{prompt_SortOrd} });
add_column($out, $DB, Users => PerPage => { type => 'INT', not_null => 1, unsigned => 1, default => 15, form_display => $lang->{prompt_PerPage} });
add_column($out, $DB, Users => Grouping => { type => 'TINYINT', not_null => 1, unsigned => 1, default => 0, form_display => $lang->{prompt_Grouping} });
add_column($out, $DB, Editors => CanModReview => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' });
# Integrate the user's email template changes into the new templates
$out->("Upgrading email templates...\n");
require GT::File::Tools;
require GT::Mail;
require GT::Mail::Parse;
my %files = (
'email-add.txt' => {
new_name => 'link_added.eml',
subject => 'VAL_APPROVESUB',
},
'email-del.txt' => {
new_name => 'link_rejected.eml',
subject => 'VAL_REJECTSUB',
},
'email-mod.txt' => {
new_name => 'link_modified.eml',
subject => 'VAL_APPROVECHGSUB',
},
'email-notify.txt' => {
new_name => 'link_expiry_notify.eml',
subject => 'LINKS_NOTIFY_SUBJECT',
},
'email-expired.txt' => {
new_name => 'link_expired.eml',
subject => 'LINKS_NOTIFY_SUBJECT',
},
'email-password.txt' => {
new_name => 'password.eml',
subject => 'USER_LOSTPASSSUB',
},
'email-validate.txt' => {
new_name => 'validate.eml',
subject => 'USER_VALEMAILSUB',
},
'review-email-add.txt' => {
new_name => 'review_added.eml',
subject => 'REVIEW_VAL_APPROVESUB',
},
'review-email-del.txt' => {
new_name => 'review_rejected.eml',
subject => 'VAL_REJECTSUB',
}
);
my $new_template = 'luna';
my $template_path = "$cfg->{admin_root_path}/templates";
my $fh = \do { local *FH; *FH };
opendir $fh, $template_path or die "Could not open '$template_path': $!";
while (my $template_set = readdir $fh) {
next if $template_set =~ /^\./ or $template_set eq 'admin' or $template_set eq $new_template
or $template_set =~ /_php$/ or $template_set =~ /^lang_.*\./ or not -d "$template_path/$template_set"
or $template_set eq 'browser' or $template_set eq 'CVS';
$out->("\tUpgrading $template_set template set...\n");
my $l = GT::Template::Inheritance->get_path(file => "language.txt", path => "$template_path/$template_set", use_local => 1, use_inheritance => 1);
unless (-e $l) {
$out->("\t\t(no language.txt found, not a template set?)\n");
next;
}
my $clang = GT::Config->load($l);
for my $file (keys %files) {
if (not -e "$template_path/$template_set/$files{$file}->{new_name}" and -e "$template_path/$new_template/$files{$file}->{new_name}") {
GT::File::Tools::copy("$template_path/$new_template/$files{$file}->{new_name}", "$template_path/$template_set/$files{$file}->{new_name}");
}
next unless -e "$template_path/$template_set/local/$file" and -r _ and not -e "$template_path/$template_set/local/$files{$file}->{new_name}";
$out->("\t\tCreating $files{$file}->{new_name} from $file... ");
open BODY, "$template_path/$template_set/local/$file" or die "Couldn't open template $template_path/$template_set/local/$file: $!";
my $body;
{
local $/;
$body = <BODY>;
}
close BODY;
next unless -e "$template_path/$new_template/$files{$file}->{new_name}";
my $top = GT::Mail::Parse->new(
in_file => "$template_path/$new_template/$files{$file}->{new_name}",
crlf => "\n",
headers_intact => 0
)->parse();
$top->body_data($body) if $body;
$top->set(Subject => $clang->{$files{$file}->{subject}}) if $clang->{$files{$file}->{subject}};
if ($body or $clang->{$files{$file}->{subject}}) {
my $mail = new GT::Mail;
$mail->top_part($top);
$mail->write("$template_path/$template_set/local/$files{$file}->{new_name}");
}
$out->("done!\n");
# We could also delete the subject language keys and the old email templates,
# but it's better we leave them around than delete something they might not
# want to lose or if something else is still using them.
}
$out->("\t\tOkay!\n");
}
closedir $fh;
$out->("\tOkay!\n");
if (delete $cfg->{foreign_char}) {
$cfg->{build_category_format} = '%Full_ID%';
$cfg->{build_category_dynamic} = 'ID';
}
elsif (my $f = delete $cfg->{build_directory_field}) {
$cfg->{build_category_format} = $f ? "%$f%" : '';
$cfg->{build_category_dynamic} = $f;
}
add_table($out, $DB, 'SearchLogs',
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'
);
if ($plugin and exists $plugin->{SearchLogger} and $DB->table('SearchLog')) {
$out->("SearchLogger plugin detected, importing SearchLogger settings...\n");
my $old = $DB->table('SearchLog');
my $new = $DB->table('SearchLogs');
require GT::Date;
$out->("\tTransferring old search logs...\n");
my $sth = $old->select(qw/Term HitCount Results Last_Hit/);
my $i;
if ($sth) {
while (my $row = $sth->fetchrow_hashref) {
$i++;
my %slog_row = (
slog_query => $row->{Term},
slog_count => $row->{HitCount},
slog_hits => $row->{Results},
slog_time => undef
);
my @time;
if ($row->{Last_Hit} =~ /^(\d{4}-\d\d-\d\d ( ?)\d?\d:\d\d:\d\d)/) {
@time = GT::Date::parse_format($1, "%yyyy%-%mm%-%dd% $2%H%:%MM%:%ss%");
}
elsif ($row->{Last_Hit} =~ /^(\d{4}-\d\d-\d\d)/) {
@time = GT::Date::parse_format($1, '%yyyy%-%mm%-%dd%');
}
$slog_row{slog_last} = @time ? GT::Date::timelocal(@time) : 0;
$ret = $new->insert(\%slog_row);
$out->("\t\tAn error occured: $GT::SQL::error\n") unless $ret;
}
$out->("\t\t$i rows imported.\n");
}
else {
$out->("\t\tAn error occured: $GT::SQL::error\n");
}
$out->("\tDropping SearchLog table...\n");
my $e = $DB->editor('SearchLog');
my $ret = $e->drop_table;
$out->($ret ? "\t\tOkay!\n" : "\t\tAn error occured: $GT::SQL::error\n");
$out->("\tUninstalling SearchLogger plugin...\n");
my $fakein = bless { plugin_name => "SearchLogger", skip_uninstall => 1 }, "FakeCGI";
my $man = new GT::Plugins::Manager(cgi => $fakein, plugin_dir => "$cfg->{admin_root_path}/Plugins");
$ret = $man->uninstall;
$out->("\t\tOkay!\n");
}
add_table($out, $DB, 'NewsletterSubscription',
cols => [
UserID => { type => 'CHAR', size => 50 },
CategoryID => { type => 'INT', not_null => 1 },
],
unique => {
ns_uc => ['UserID', 'CategoryID']
},
fk => {
Users => { UserID => 'Username' },
Category => { CategoryID => 'ID' }
}
);
if (exists $DB->table('Users')->cols->{Newsletter}) {
$out->("Importing User Newsletter settings...\n");
my $sth = $DB->table('Users')->select('Username', { Newsletter => 'Yes' });
my $ns = $DB->table('NewsletterSubscription');
if ($sth) {
while (my $user = $sth->fetchrow) {
$ns->insert({ UserID => $user, CategoryID => 0 });
}
$out->("\tOkay!\n");
}
else {
$out->("\tAn error occured: $GT::SQL::error\n");
}
drop_column($out, $DB, 'Users', 'Newsletter');
}
# Don't print here - the final 2.99.x -> 3.0.0 code prints the final message.
# $out->("Links SQL 2.2.1 -> 3.0.0 upgrades performed.\n");
1;
}
sub upgrade__2_2_0__2_2_1 {
# ---------------------------------------------------------------
# Upgrade from 2.2.0 to 2.2.1
#
my ($out, $cfg) = @_;
$out->(PERFORM '2.2.0' => '2.2.1');
import lib $cfg->{admin_root_path};
require GT::SQL;
$Links::DB = my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
$Links::STASH{expired_links} = 1;
require GT::Config;
my $lang = GT::Config->load($cfg->{admin_root_path} . '/templates/admin/language.txt');
# Update Rating regex
alter_column($out, $DB, Links => Rating => { type => 'DECIMAL', precision => 4, scale => 2, not_null => 1, default => 0, regex => '^(?:10(?:\.0*)?|\d(?:\.\d*)?)$', form_display => $lang->{prompt_Rating} });
# Update payments_term from CHAR(8) to CHAR(10)
alter_column($out, $DB, Payments => payments_term => { type => 'CHAR', not_null => 1, size => 10 });
# Fix fk_tables that might have been deleted due to the SQL database overwrite bug
my %fk_tables = (
Category => [qw/CatPrice CatLinks CatRelations Editors/],
Links => [qw/Payments Changes Reviews CatLinks Verify/],
Payments => [qw/PaymentLogs/],
Users => [qw/Links Changes Reviews Editors Sessions/]
);
$out->("Checking fk_tables...\n");
my $p = $DB->prefix;
while (my ($table, $tables) = each %fk_tables) {
my $tb = $DB->table($table);
my $changed;
for (@$tables) {
$tb->_add_fk_table("$p$_") and $changed++;
}
if ($changed) {
$tb->save_state;
$out->("\t\t$table table's fk_tables repaired\n");
}
}
$out->("\tOkay!\n");
$out->(DONE '2.2.0' => '2.2.1');
}
sub upgrade__2_1_2__2_2_0 {
# ---------------------------------------------------------------
# Upgrade from 2.1.2 to 2.2.0
#
my ($out, $cfg) = @_;
$out->(PERFORM '2.1.2' => '2.2.0');
import lib $cfg->{admin_root_path};
require GT::Config;
# Check to see that the PPC plugin <1.93 is not installed. Versions prior
# to 1.93 conflict with Links SQL's 'Payments' table.
my $plugin_cfg = GT::Config->load("$cfg->{admin_root_path}/Plugins/plugin.cfg");
if (exists $plugin_cfg->{PPC} and (!$plugin_cfg->{PPC}->{version} or $plugin_cfg->{PPC}->{version} < 1.93)) {
$out->("Old PPC plugin detected - you must upgrade the PPC plugin to 1.93 or above before upgrading Links SQL.");
die "Old PPC plugin detected - you must upgrade the PPC plugin to 1.93 or above before upgrading Links SQL.";
}
require GT::SQL;
$Links::DB = my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
$Links::STASH{clicktrack_cleanup} = 1;
$Links::STASH{expired_links} = 1;
my $lang = GT::Config->load("$cfg->{admin_root_path}/templates/admin/language.txt");
for my $table (qw/Users Links Category/) {
$out->("Updating $table subclasses...\n");
# Create a new GT::SQL::Table object manually as I do _not_ want to
# load the existing subclasses.
my $t = GT::SQL::Table->new(
name => "$DB->{connect}->{PREFIX}$table",
connect => $DB->{connect},
debug => $DB->{_debug},
_err_pkg => 'GT::SQL::Table'
);
$t->subclass(
table => { $table => "Links::Table::$table" },
html => { $table => "Links::HTML::$table" }
);
$t->save_state();
$out->("\tOkay!\n");
}
add_column($out, $DB, Category => Payment_Mode => { type => 'TINYINT', not_null => 1, default => 0, form_size => 1, form_names => [0,1,2,3], form_values => ['Use global settings','Not accepted','Optional','Required'], form_type => 'SELECT', form_display => $lang->{prompt_Payment_Mode} });
add_column($out, $DB, Category => Payment_Description => { type => 'TEXT', form_display => $lang->{prompt_Payment_Description} });
add_column($out, $DB, Links => ExpiryDate => { type => 'INT', not_null => 1, default => 0x7fff_ffff, form_display => $lang->{prompt_ExpiryDate}, form_size => 35 });
add_column($out, $DB, Links => ExpiryCounted => { type => 'TINYINT', not_null => 1, default => 0, form_display => $lang->{prompt_ExpiryCounted}, form_type => 'hidden' });
add_column($out, $DB, Links => ExpiryNotify => { type => 'TINYINT', not_null => 1, default => 0, form_display => $lang->{prompt_ExpiryNotify}, form_type => 'hidden' });
drop_index($out, $DB, Links => 'valndx');
add_index($out, $DB, Links => {
valexpndx => [qw/isValidated ExpiryDate/],
expiryndx => [qw/ExpiryDate ExpiryNotify/],
expcntndx => [qw/ExpiryCounted ExpiryDate/]
});
add_table($out, $DB, "CatPrice",
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 => 'FLOAT', 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' } }
);
add_table($out, $DB, "Payments",
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 => 'FLOAT', not_null => 1 },
payments_term => { type => 'CHAR', not_null => 1, size => 8 }, # e.g. 8d, 1m, 2y, 3w, 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'],
}
);
add_table($out, $DB, "PaymentLogs",
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']
}
);
recreate_table($out => $DB => ClickTrack => sub { my $table = shift; ($table->pk and @{$table->pk} != 0) },
cols => [
LinkID => { type => 'INT', not_null => 1 },
IP => { type => 'CHAR', size => 25, not_null => 1 },
ClickType => { type => 'ENUM', values => ['Rate', 'Hits','Review'], not_null => 1 },
ReviewID => { type => 'INT', not_null => 1, default => 0},
Created => { type => 'TIMESTAMP' }
],
unique => {
ct_licr => ['LinkID', 'IP', 'ClickType','ReviewID']
},
index => {
cndx => ['Created']
}
);
if (-e(my $oldconfig = "$cfg->{admin_root_path}/Links/ConfigData.pm")) {
$out->("Removing old Links/ConfigData.pm file (has been replaced with Links/Config/Data.pm)...\n");
require GT::File::Tools;
my $ret = GT::File::Tools::move($oldconfig, "$oldconfig.old");
$out->($ret ? "\tOkay!\n" : "\tAn error occured: $!\n");
}
$out->(DONE '2.1.2' => '2.2.0');
}
sub upgrade__2_1_1__2_1_2 {
# ---------------------------------------------------------------
# Upgrade from 2.1.1 to 2.1.2
#
my ($out, $cfg) = @_;
$out->(PERFORM '2.1.1' => '2.1.2');
# Add session table.
import lib $cfg->{admin_root_path};
require GT::SQL;
my $DB = GT::SQL->new( $cfg->{admin_root_path} . '/defs' );
add_table($out, $DB, 'Sessions',
cols => [
session_id => { type => 'CHAR', size => 32, not_null => 1, binary => '1' },
session_user_id => { type => 'CHAR', not_null => 1 },
session_date => { type => 'INT', not_null => 1 },
session_data => { type => 'TEXT' }
],
pk => 'session_id',
fk => { Users => { session_user_id => 'Username' } }
);
$out->(DONE '2.1.1' => '2.1.2');
}
sub upgrade__2_0_5__2_1_0 {
# ---------------------------------------------------------------
# Upgrade from 2.0.5 to 2.1.0
#
my ($out, $cfg) = @_;
$out->(PERFORM '2.0.5' => '2.1.0');
# Add the review table.
import lib $cfg->{admin_root_path};
require GT::SQL;
require GT::Config;
my $lang = GT::Config->load( $cfg->{admin_root_path} . '/templates/admin/language.txt' );
my $DB = GT::SQL->new($cfg->{admin_root_path} . '/defs');
add_table($out, $DB, 'Reviews',
cols => [
ReviewID => { type => 'INT', not_null => 1, unsigned => 1, form_display => $lang->{'prompt_ReviewID'} },
Review_LinkID => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$', form_display => $lang->{'prompt_Review_LinkID'} },
Review_Owner => { type => 'CHAR', size => 50, not_null => 1, form_display => $lang->{'prompt_Review_Owner'} },
Review_Rating => { type => 'SMALLINT', unsigned => 1, not_null => 1, default => 0, regex => '^\d+$', form_display => $lang->{'prompt_Review_Rating'} },
Review_Date => { type => 'DATE', not_null => 1, form_display => $lang->{'prompt_Review_Date'} },
Review_Subject => { type => 'CHAR', size => 100, not_null => 1, form_display => $lang->{'prompt_Review_Subject'} },
Review_Contents => { type => 'TEXT', not_null => 1, form_display => $lang->{'prompt_Review_Contents'} },
Review_ByLine => { type => 'CHAR', size => 50, form_display => $lang->{'prompt_Review_ByLine'} },
Review_WasHelpful => { type => 'INT', unsigned => 1, regex => '^\d+$', form_display => $lang->{'prompt_Review_WasHelpful'} },
Review_WasNotHelpful=> { type => 'INT', unsigned => 1, regex => '^\d+$', form_display => $lang->{'prompt_Review_WasNotHelpful'} },
Review_Validated => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => $lang->{'prompt_Review_Validated'} },
Review_GuestName => { type => 'CHAR', size => 75, form_display => $lang->{'prompt_Review_GuestName'} },
Review_GuestEmail => { type => 'CHAR', size => 75, regex => '^(?:(?:.+\@.+\..+)|\s*)$', form_display => $lang->{'prompt_Review_GuestEmail'} },
],
pk => 'ReviewID',
ai => 'ReviewID',
index => { rownerndx => ['Review_Owner'], rdatendx => ['Review_Date'], rlinkndx => ['Review_LinkID'] },
fk => { Links => { Review_LinkID => 'ID' }, Users => { Review_Owner => 'Username' }}
);
add_column($out, $DB, ClickTrack => ReviewID => { type => 'INT', not_null => 1, default => 0 });
# Set default review options.
my %default_review = (
user_review_required => 1,
reviews_per_page => 5,
review_sort_by => 'Review_Date',
review_convert_br_tags => 1,
review_days_old => 7
);
while (my ($k, $v) = each %default_review) {
$cfg->{$k} = $v unless exists $cfg->{$k};
}
$out->(DONE '2.0.5' => '2.1.0');
}
sub upgrade__2_0_3__2_0_4 {
# ---------------------------------------------------------------
# Upgrade from 2.0.3 to 2.0.4.
#
my ($out, $cfg) = @_;
$out->(PERFORM '2.0.3' => '2.0.4');
import lib $cfg->{admin_root_path};
require GT::SQL;
my $db = GT::SQL->new($cfg->{admin_root_path} . '/defs');
add_column($out, $db, Links => Contact_Name => { type => 'CHAR', size => 255 });
add_column($out, $db, Links => Contact_Email => { type => 'CHAR', size => 255 });
add_column($out, $db, Category => Category_Template => { type => 'CHAR', size => 40 });
add_column($out, $db, MailingIndex => messageformat => { type => 'ENUM', values => [qw[text html]], not_null => 1, default => 'text' });
$out->(DONE '2.0.3' => '2.0.4');
}
1;