discourse-legacysite-perl/site/glist/lib/GList.pm

1252 lines
43 KiB
Perl
Raw Normal View History

2024-06-17 11:49:12 +00:00
# ==================================================================
# Gossamer List - enhanced mailing list management system
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# CVS Info :
# Revision : $Id: GList.pm,v 1.69 2004/10/14 22:57:50 bao 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 GList;
# ==================================================================
use 5.004_04;
use strict;
use GList::Custom; # Empty module for end-users to modify, if special code is
# required. Won't be overwritten when upgrading.
use vars qw($VERSION $DEBUG $IN $DB $CFG $USER $GLOBALS $GLOB_NO_SUBS $LANGUAGE $LANG_TPL $error @ISA @EXPORT_OK %EXPORT_TAGS $MN_SELECTED);
use GT::Base qw/:all/;
use GT::CGI;
use GT::Config;
use GT::Delay;
use GT::Template;
use GT::Plugins;
use GList::Config;
require Exporter;
$DEBUG = 0;
$VERSION = '1.1.1';
use constants
READ_SIZE => 65_536,
ADMINISTRATOR => 1,
LIMITED_USER => 2,
UNLIMITED_USER => 3,
UNVALIDATED_USER => 4,
TRACK_OPEN_HTML => <<HTML,
<img src="<%cgi_url%>/glist.cgi?do=user_open;eml_code=<%eml_code%>;mailing=<%mailing%>" width="1" height="1">
<iframe src="<%cgi_url%>/glist.cgi?do=user_open;eml_code=<%eml_code%>;mailing=<%mailing%>" width="1" height="1"></iframe>
<table border=0 cellpadding=0 cellspacing=0 height="1" width="1"><tr><td background="<%cgi_url%>/glist.cgi?do=user_open;eml_code=<%eml_code%>;mailing=<%mailing%>"></td></tr></table>
HTML
TRACK_OPEN_HTML_NOIFRAME => <<HTML,
<img src="<%cgi_url%>/glist.cgi?do=user_open;eml_code=<%eml_code%>;mailing=<%mailing%>" width="1" height="1">
<table border=0 cellpadding=0 cellspacing=0 height="1" width="1"><tr><td background="<%cgi_url%>/glist.cgi?do=user_open;eml_code=<%eml_code%>;mailing=<%mailing%>"></td></tr></table>
HTML
TRACK_CLICK_URL => "<%cgi_url%>/glist.cgi?do=user_click;mailing=<%mailing%>";
@ISA = 'Exporter';
@EXPORT_OK = qw/
$VERSION $MOD_PERL $DEBUG $IN $DB $CFG $USER $GLOBALS $LANGUAGE $MN_SELECTED
ADMINISTRATOR LIMITED_USER UNLIMITED_USER UNVALIDATED_USER TRACK_OPEN_HTML TRACK_OPEN_HTML_NOIFRAME TRACK_CLICK_URL
/;
%EXPORT_TAGS = (
all => \@EXPORT_OK,
objects => [qw/$IN $DB $CFG $USER $MN_SELECTED/],
user_type => [qw/ADMINISTRATOR LIMITED_USER UNLIMITED_USER UNVALIDATED_USER/],
tracks => [qw/TRACK_OPEN_HTML TRACK_OPEN_HTML_NOIFRAME TRACK_CLICK_URL/]
);
my $basic_tables;
sub init {
# -----------------------------------------------------------------------------
# This subroutine should be called on every request with a single argument: the
# path to the private library directory.
#
# If called as a method, discard the class/object:
shift if @_ and UNIVERSAL::isa($_[0], __PACKAGE__);
my $lib_path = shift || '.';
if (PERSIST) {
GT::SQL->reset_env() if $INC{'GT/SQL.pm'};
GT::Plugins->reset_env();
GT::CGI->reset_env();
}
# Get our config object.
$CFG = GList::Config->new($lib_path);
my $debug = $CFG->{debug_level} || $DEBUG;
# create input and sql objects
$IN = GT::CGI->new();
if ($DB and not ref $DB eq 'GT::Delay') {
$DB = GT::SQL->new({
def_path => "$CFG->{priv_path}/defs",
cache => 1,
debug => $debug
});
}
else {
$DB = GT::Delay(
'GT::SQL' => 'HASH',
{
def_path => "$CFG->{priv_path}/defs",
cache => 1,
debug => $debug
}
);
}
# Set plugin debug level.
$GT::Plugins::DEBUG = $debug;
$USER = $GLOBALS = $GLOB_NO_SUBS = $LANGUAGE = $LANG_TPL = $basic_tables = undef;
}
sub init_user {
# -----------------------------------------------------------------------------
# Check to see if the request is for a valid user, if so, set $USER to the
# user.
#
# Authenticate the user.
require GList::Authenticate;
GList::Authenticate::auth('init');
$USER = undef;
my $username = shift || $IN->param('username') || undef;
my $password = shift || $IN->param('password') || undef;
# Validate the username, either through logging on, or checking the
# session.
my ($valid_user, $session_id, $use_cookie);
if (defined $username && defined $password) {
unless (test_connection()) { # Database connection is failed
if ( GList::Authenticate::auth('admin_valid_user', { username => $username, password => $password }) ) {
$USER->{username} = $username;
$USER->{usr_type} = ADMINISTRATOR;
return $USER;
}
}
elsif (GList::Authenticate::auth('valid_user', { username => $username, password => $password })) {
$valid_user = $username;
}
}
else {
unless (test_connection()) { # Database connection is failed
my $results = GList::Authenticate::auth('admin_valid_session');
if ($results) {
$USER = $results;
$USER->{usr_type} = ADMINISTRATOR;
return $USER;
}
return;
}
my $results = GList::Authenticate::auth('valid_session');
$valid_user = $results->{user_name};
$session_id = $results->{session_id};
$use_cookie = $results->{use_cookie};
}
return if !$valid_user;
# We have a valid_user, now let's get the user from database
$USER = GList::Authenticate::auth('get_user', { username => $valid_user });
return 1 if !$USER;
if ($CFG->{signup_email_validate} and $USER->{usr_validate_code}) {
return 2;
}
if ($CFG->{signup_admin_validate} and $USER->{usr_type} == UNVALIDATED_USER) {
return 3;
}
$USER->{use_cookie} = $use_cookie;
$USER->{session_id} = $session_id;
return $USER;
}
sub test_connection {
# -----------------------------------------------------------------------------
# Test the database connection by trying to establish a connection. Returns
# 1 on success, nothing on connection error. In addition to the database
# connection, this also makes sure that the Users and Users_Sessions tables
# are working, since you need them at a minimum to get to the SQL setup page.
#
$DB->driver or return;
GT::SQL::Table->new(connect => $DB->{connect})->connect or return;
unless ($basic_tables) {
defined $DB->table('Users')->count({ usr_username => undef }) or return;
defined $DB->table('Users_Sessions')->count({ session_id => undef }) or return;
$basic_tables++;
}
return 1;
}
sub environment {
# --------------------------------------------------------------------
# Return HTML formatted environment for error messages.
#
my $info = '<PRE>';
# Stack trace.
my $i = 0;
$info .= "<B>Stack Trace</B>\n======================================\n";
$info .= GT::Base::stack_trace('GList', 1);
$info .= "\n\n";
# Print GT::SQL error if it exists.
$info .= "<B>System Information</B>\n======================================\n";
$info .= "Perl Version: $]\n";
$info .= "GList SQL Version: $GList::VERSION\n" if ($GList::VERSION);
$info .= "DBI.pm Version: $DBI::VERSION\n" if ($DBI::VERSION);
$info .= "Running under mod_perl: " . (MOD_PERL ? "Yes (version " . MOD_PERL . ")" . (MOD_PERL >= 1.99 ? ', mod_perl 2 detected' : '') : "No") . "\n";
$info .= "Running under SpeedyCGI: " . (SPEEDY ? "Yes (version " . SPEEDY . ")" : "No") . "\n";
$info .= "GT::SQL::error = $GT::SQL::error\n" if ($GT::SQL::error);
$info .= "\@INC = \n\t" . join ("\n\t", @INC) . "\n";
$info .= "\$\@: $@\n" if ($@);
$info .= "\n";
# CGI Parameters and Cookies.
if (ref $IN eq 'GT::CGI') {
if ($IN->param) {
$info .= "<B>CGI INPUT</B>\n======================================\n";
foreach (sort $IN->param) { $info .= "$_ => " . $IN->param($_) . "\n"; }
$info .= "\n\n";
}
if ($IN->cookie) {
$info .= "<B>CGI Cookies</B>\n======================================\n";
foreach (sort $IN->cookie) { $info .= "$_ => " . $IN->cookie($_) . "\n"; }
$info .= "\n\n";
}
}
# Environement info.
$info .= "<B>ENVIRONMENT</B>\n======================================\n";
foreach (sort keys %ENV) { $info .= "$_ => $ENV{$_}\n"; }
$info .= "</PRE>";
return $info;
}
sub display {
# -----------------------------------------------------------------
# Returns a specified template parsed.
#
my ($template, $args) = @_;
my $template_set = $IN->param('t') || $CFG->{template_set};
my $template_dir = "$CFG->{priv_path}/templates/$template_set";
my $http = $IN->url(absolute => 0, query_string => 0);
# Add config vars.
foreach my $key (keys %$CFG) {
$args->{$key} = $CFG->{$key} unless (exists $args->{$key});
}
# Used for HTML editor
my %browser = $IN->browser_info;
delete $browser{is_ie} if $browser{is_ie} and $browser{ie_version} < 5.5;
@$args{keys %browser} = values %browser;
$args->{html}->{in} = $IN;
$args->{html}->{sql} = $DB;
$args->{html}->{cfg} = $CFG;
$args->{selected_menu} = $MN_SELECTED;
# Loads template globals
load_globals();
# Escapes HTML code
my $cgi = $IN->get_hash();
my $content = $cgi->{msg_content_html};
if ( $content ) {
$content =~ s,\r\n,\n,g;
$cgi->{msg_content_html} = $IN->html_escape($content);
}
unless (defined $args->{hidden_query}) {
my $hidden = hidden();
$args->{hidden_query} = $hidden->{hidden_query};
$args->{hidden_objects} = $hidden->{hidden_objects};
}
print $IN->header;
GT::Template->parse($template, [$args, $cgi, $GLOBALS, $USER || {}], { print => 1, root => $template_dir });
}
sub set_default_template {
#-----------------------------------------------------------
# Add default email template when adding a user
#
my ($fname, $userid) = @_;
require GT::Mail::Editor;
my $email = GT::Mail::Editor->new(dir => "$CFG->{priv_path}/templates", template => $CFG->{template_set});
$email->load($fname);
my $hsh = {};
my $cgi = $IN->get_hash();
$fname =~ s/\.eml//;
$hsh->{tpl_user_id_fk} = $userid;
$hsh->{tpl_name} = $fname;
$hsh->{tpl_to} = $email->{headers}->{To};
$hsh->{tpl_from} = $email->{headers}->{From};
$hsh->{tpl_subject}= $email->{headers}->{Subject};
$hsh->{tpl_body} = $email->{body};
$DB->table('EmailTemplates')->insert($hsh);
}
sub add {
#--------------------------------------------------------------------
# Add a record
#
my ($table, $prefix, $cgi) = @_;
my $db = $DB->table($table) or return $GT::SQL::error;
# Turn arrays into delimited fields
$cgi ||= format_insert_cgi($db);
# Save the current time
if ( $table eq 'Messages' ) {
$cgi->{msg_created} = time;
if ($cgi->{msg_content_html} =~ /^\s*<html>\s*<BODY\s*class=object\s*\s*src="">\s*<\/BODY>\s*<\/html>\s*$/mi or
$cgi->{msg_content_html} =~ /^\s*<html>\s*<body\s*src="">\s*<\/body>\s*<\/html>\s*$/mi or
$cgi->{msg_content_html} =~ /^\s*<html>\s*<BODY\s*class=object\s*\s*src=""><p>\&nbsp;<\/p><\/BODY>\s*<\/html>\s*$/mi or
$cgi->{msg_content_html} =~ /^\s*<html>\s*<BODY\s*src=""><P>\&nbsp;<\/P><\/BODY>\s*<\/html>\s*$/mi) {
$cgi->{msg_content_html} = "";
}
}
elsif ( $table eq 'Lists' ) {
$cgi->{lst_date_created} = time;
}
# Add the record's owner
$cgi->{$prefix.'_user_id_fk'} = $USER->{usr_username};
# Setup the language for GT::SQL.
local $GT::SQL::ERRORS->{ILLEGALVAL} = language('ADD_ILLEGALVAL') if ( language('ADD_ILLEGALVAL') );
local $GT::SQL::ERRORS->{UNIQUE} = language('ADD_UNIQUE') if ( language('ADD_UNIQUE') );
local $GT::SQL::ERRORS->{NOTNULL} = language('ADD_NOTNULL') if ( language('ADD_NOTNULL') );
my $cols = $db->cols;
foreach my $c ( keys % $cols ) {
my $regex = $cols->{$c}->{form_regex};
if ( $regex and $cgi->{$c} !~ /$regex/ ) {
$error .= language('SYS_REGEXFAIL', $cols->{$c}->{form_display});
}
}
return if ( $error );
if ( defined (my $ret = $db->add($cgi)) ) {
return $ret;
}
else {
local $^W;
$error = $GT::SQL::error;
}
}
sub modify {
#--------------------------------------------------------------------
# Modify a record
#
my ($table, $prefix, $cgi) = @_;
my $db = $DB->table($table) or return $GT::SQL::error;
# Format arrays for insertion
$cgi ||= format_insert_cgi($db, $cgi);
# Check if users can modify only their own records except Administrator
if ( $USER->{usr_type} != ADMINISTRATOR ) {
my $lookup = {};
my $pk = $db->pk;
foreach (@$pk) { $lookup->{$_} = $IN->param($_); }
my $rs = $db->get($lookup);
if ( $rs->{$prefix.'_user_id_fk'} ne $USER->{usr_username} ) {
$error = language('SYS_PER_DENIED');
return;
}
}
# Setup the language for GT::SQL.
local $GT::SQL::ERRORS->{ILLEGALVAL} = language('ADD_ILLEGALVAL') if ( language('ADD_ILLEGALVAL') );
local $GT::SQL::ERRORS->{UNIQUE} = language('ADD_UNIQUE') if ( language('ADD_UNIQUE') );
local $GT::SQL::ERRORS->{NOTNULL} = language('ADD_NOTNULL') if ( language('ADD_NOTNULL') );
if ( $table eq 'Messages' ) {
if ($cgi->{msg_content_html} =~ /^\s*<html>\s*<BODY\s*class=object\s*\s*src="">\s*<\/BODY>\s*<\/html>\s*$/mi or
$cgi->{msg_content_html} =~ /^\s*<html>\s*<body\s*src="">\s*<\/body>\s*<\/html>\s*$/mi or
$cgi->{msg_content_html} =~ /^\s*<html>\s*<BODY\s*class=object\s*\s*src=""><p>\&nbsp;<\/p><\/BODY>\s*<\/html>\s*$/mi or
$cgi->{msg_content_html} =~ /^\s*<html>\s*<BODY\s*src=""><P>\&nbsp;<\/P><\/BODY>\s*<\/html>\s*$/mi) {
$cgi->{msg_content_html} = "";
}
if ($cgi->{msg_mode} eq 'text') {
$cgi->{msg_content_html} = '';
}
}
if ( $db->modify($cgi) ) {
return;
}
else {
local $^W;
$error = $GT::SQL::error;
}
}
sub delete {
#--------------------------------------------------------------------
# Delete records
#
my ($table, $prefix, $cgi, $msg) = @_;
my $db = $DB->table($table);
# Create a cgi object
$cgi ||= $IN->get_hash();
# If they selected only one record to delete we still need an array ref
my $mod = ( ref $cgi->{modify} eq 'ARRAY' ) ? $cgi->{modify} : [$cgi->{modify}];
# Need to know the names of the columns for this Table.
my @columns = keys %{$db->cols};
# Need to know the number of records modified
my $rec_modified = 0;
my $rec_declined = 0;
if ( $table eq 'Messages' or $table eq 'MailingIndex' ) {
require GT::File::Tools;
}
# For through the record numbers. These are the values of the
# check boxes
foreach my $rec_num ( @{$mod} ) {
my $change = {};
foreach my $column ( @columns ) {
$change->{$column} = $cgi->{"$rec_num-$column"} if ( $cgi->{"$rec_num-$column"} );
}
# Check for delete own record
if ( $USER->{usr_type} != ADMINISTRATOR ) { # As a user
my $rs = $db->get($change);
next if ( !$rs );
if ( $rs->{$prefix.'_user_id_fk'} ne $USER->{usr_username} ) {
$rec_declined++; next;
}
}
next unless ( keys %$change );
if ( $table eq 'MailingIndex' ) {
if ( int $cgi->{fd} and $cgi->{fd} == 3 ) { # Deletes records
my $info = $db->get($change) || {};
if ( $USER->{usr_type} == ADMINISTRATOR or !$info->{mli_Done} ) { # Admin user
my $ret = $db->delete($change);
if ( defined $ret and ($ret != 0) ) {
$rec_modified++;
}
}
else {
$db->update({ mli_delete => '2', mli_cat_id_fk => 0, mli_root => '0' }, $change);
$rec_modified++;
}
}
else { # Marks records
$db->update({ mli_delete => '1', mli_cat_id_fk => 0, mli_root => '0' }, $change);
$rec_modified++;
}
}
else {
my $ret = $db->delete($change) or die $GT::SQL::error;
if ( defined $ret and ($ret != 0) ) {
$rec_modified++;
}
}
# Remove attachments
my $id = $IN->param("$rec_num-msg_id");
if ( $table eq 'Messages' and $id ) {
remove_attachments($id, 'messages');
}
$id = $IN->param("$rec_num-Mailing");
if ( $table eq 'MailingIndex' and $id and $cgi->{fd} == 3 ) {
remove_attachments($id, 'mailings');
}
}
$msg ||= ( $rec_declined ) ? GList::language('SYS_DELETED2', $rec_modified, $rec_declined) : GList::language('SYS_DELETED', $rec_modified);
return $msg;
}
sub send {
#--------------------------------------------------------
# Send a message by using GT::Mail
#
my ($head, $content, $attachments, $attach_path, $charset) = @_;
$attachments ||= [];
$charset ||= 'us-ascii';
require GT::Mail;
$GT::Mail::error ||= ''; # Silence -w
my $m = GT::Mail->new(debug => $CFG->{debug_level}, header_charset => $charset);
my $parts;
if ( $content->{text} and $content->{html} ) {
$parts = $m->new_part('Content-Type' => "multipart/alternative; charset=\"$charset\"");
$parts->parts($m->new_part(
'Content-Type' => "text/plain; charset=\"$charset\"",
body_data => $content->{text},
encoding => 'quoted-printable'
));
$parts->parts($m->new_part(
'Content-Type' => "text/html; charset=\"$charset\"",
body_data => $content->{html},
encoding => 'quoted-printable'
));
}
elsif (@$attachments) {
my $msg = $content->{text} || $content->{html};
my $type = ( $msg =~ m/(<html>|<body>)/i ? "text/html" : "text/plain" );
$type = "text/html" if ($content->{html});
$parts = $m->new_part(
'Content-Type' => "$type; charset=\"$charset\"",
body_data => $msg,
encoding => 'quoted-printable'
);
}
else {
my $msg = $content->{text} || $content->{html};
my $type = ( $msg =~ m/(<html>|<body>)/i ? "text/html" : "text/plain" );
$type = "text/html" if ($content->{html});
$parts = $m->new_part(
'Content-Type' => "$type; charset=\"$charset\"",
encoding => 'quoted-printable'
);
$head->{body_data} = $msg;
}
# Handle the attachments
if (@$attachments) {
my $apart = $m->new_part('Content-Type' => 'multipart/mixed');
$apart->parts($parts);
for (@$attachments) {
my $id = $_->{att_id} || $_->{mat_id};
my $filename = $_->{mat_file_name} || $_->{att_file_name};
my $content_type = _load_mime("$attach_path/$id", $filename);
$apart->parts($m->new_part(
body_path => "$attach_path/$id",
encoding => '-guess',
filename => $filename,
'Content-Type' => $content_type
));
}
$parts = $apart;
}
$head->{'Content-Type'} = $parts->get('Content-Type');
my $mail = GT::Mail->new(
%$head,
debug => $CFG->{debug_level},
header_charset => $charset,
);
for ($parts->parts()) {
$mail->attach($_);
}
$mail->send(
smtp => $CFG->{smtp_server},
sendmail => $CFG->{mail_path},
) or warn $GT::Mail::error;
}
sub hidden {
#--------------------------------------------------------------------
#
my $args = shift || [];
push @$args, 'users';
my $cgi = $IN->get_hash();
my ($hidden_query, $hidden_objects) = ('', '');
if ($CFG->{user_session} and ($cgi->{sid} or $USER->{session_id})) {
my $session_id = $cgi->{sid} || $USER->{session_id};
$hidden_query = ";sid=$session_id";
$hidden_objects = qq!<input type="hidden" name="sid" value="$session_id">!;
}
foreach (@$args) {
next unless $cgi->{$_};
$hidden_query .= ";$_=$cgi->{$_};$_-opt==";
$hidden_objects .= qq!<input type="hidden" name="$_" value="$cgi->{$_}">
<input type="hidden" name="$_-opt" value="=">
!;
}
return { hidden_query => $hidden_query, hidden_objects => $hidden_objects };
}
sub _search_check {
#--------------------------------------------------------------------
#
my ($cols, $cgi) = @_;
foreach (keys % $cols) {
my ($c) = $_ =~ /\.([^.]+)$/;
$c ||= $_;
if (exists $cgi->{$c} and $cgi->{$c}) {
return 1;
}
if ($cgi->{"$c-ge"} or $cgi->{"$c-le"} or $cgi->{"$c-gt"} or $cgi->{"$c-lt"}) {
return 1;
}
}
return;
}
sub search {
#--------------------------------------------------------------------
# Search engine
#
my $opts = ref $_[0] eq 'HASH' ? shift : { @_ };
my $cgi = $opts->{cgi};
my $db = $opts->{db};
my $prefix = $opts->{prefix};
my $based_on = $opts->{based_on};
my $skip_user = $opts->{skip_user};
my $search_check= $opts->{search_check};
my $search_alpha= $opts->{search_alpha};
my $search_col = $opts->{search_col};
my $return_msg = $opts->{return_msg};
my $select_all = $opts->{select_all};
my $show_user = $opts->{show_user};
my $int_field = $opts->{int_field};
$return_msg ||= uc($prefix).'_RESULTS';
my $user_field = $prefix."_user_id_fk";
my $nh = $cgi->{nh} || 1;
my $mh = $cgi->{mh} || 25;
my $ma = $cgi->{ma} || '';
my $bg = ( $nh == 1 ) ? 0 : ( $nh - 1 ) * $mh;
my $sb = $cgi->{sb} || $opts->{sb};
my $so = $cgi->{so} || $opts->{so};
my $cols = $db->cols;
my $table_name = $db->name;
my $db_prefix = $DB->prefix;
$table_name =~ s/^$db_prefix//;
$sb ||= $opts->{sb};
$so ||= $opts->{so} || 'ASC';
if ($search_check and !$cgi->{keyword} and !_search_check($cols, $cgi)) {
return { error => GList::language('SYS_SEARCH_ERROR') };
}
# Require GT's modules
require GT::SQL::Condition;
require GT::Date;
my ($cd, @words);
my $query = '';
if ( $cgi->{keyword} and $cgi->{keyword} ne '*' ) { # keyword search
$cd = new GT::SQL::Condition('OR');
if ( $ma ) { # match any
@words = split(/\s/, $cgi->{keyword});
}
else {
push @words, $cgi->{keyword};
}
foreach my $c ( keys % $cols ) {
if ( $cols->{$c}->{weight} ) { # search weight
foreach my $w ( @words ) {
$cd->add($c, 'like', "%$w%");
}
}
}
$query = "keyword=$cgi->{keyword};";
}
else {
my $bool = ( $ma ) ? 'OR' : 'AND';
$cd = new GT::SQL::Condition($bool);
if ($search_alpha) {
if ( $search_col and $search_alpha eq 'other') { # for Subscribers table only
my $tmp = GT::SQL::Condition->new('OR');
$tmp->add($search_col => '<' => '0');
$tmp->add(GT::SQL::Condition->new($search_col => '>=' => ':', $search_col => '<' => 'a'));
$tmp->add($search_col => '>=' => '[');
$cd->add($tmp);
$query .= 'alpha=other;';
}
elsif ( $search_col and $search_alpha eq 'number') { # for Subscribers table only
my $tmp = GT::SQL::Condition->new($search_col => '>=' => '0', $search_col => '<' => ':');
$cd->add($tmp);
$query .= 'alpha=number;';
}
else {
$cd->add($search_col, 'like', "$search_alpha%");
}
}
foreach my $c ( keys % $cols ) {
my $tc = $c;
if ( $based_on ) {
$tc =~ s/$based_on\.//;
}
next if ( $c and $cgi->{$tc} and ( $c eq $user_field or $cgi->{$tc} eq '*' ));
if ( $cols->{$c}->{type} =~ /date|datetime|timestamp/mi or !$cgi->{$tc} ) { # DATE fields
if ( defined $cgi->{$tc} and $cgi->{$tc} eq '0' ) {
$cd->add($c, $cgi->{"$tc-opt"} || '=', $cgi->{$tc} );
$query .= "$tc=0;";
}
else {
my $tmp = {'le' => '<=', 'ge' => '>=', 'lt' => '<', 'gt' => '>'};
my $format = $USER->{usr_date_format} || '%mm%-%dd%-%yyyy%';
foreach my $o (keys % {$tmp} ) {
next if ( !$cgi->{"$tc-$o"} );
my $v;
if ($int_field) {
$v = $cgi->{"$tc-$o"};
}
else {
$cgi->{"$tc-$o"} .= ( $o eq 'le' or $o eq 'lt' ) ? ' 23:59:58' : ' 00:00:01';
$v = GT::Date::timelocal(GT::Date::parse_format($cgi->{"$tc-$o"}, "$format %hh%:%MM%:%ss%"));
}
$cd->add($c, $tmp->{$o}, $v);
$query .= "$tc-$o=".$cgi->{"$tc-$o"}.';';
}
}
}
elsif ( $cgi->{"$tc-opt"} ) {
$cd->add($c, $cgi->{"$tc-opt"}, $cgi->{$tc});
$query .= "$tc=$cgi->{$tc};$c-opt=".$cgi->{"$tc-opt"}.";";
}
elsif ( $cols->{$c}->{type} =~ /char|varchar|text/mi ) { # TEXT fields
$cd->add($c, 'like', "%$cgi->{$tc}%");
$query .= "$tc=$cgi->{$tc};";
}
else {
$cd->add($c, '=', $cgi->{$tc});
$query .= "$tc=$cgi->{$tc};";
}
}
}
$query .= 'ma=1;' if ($ma);
my @extra = ('cs', 'mn_disable');
foreach (@extra) {
$query .= "$_=$cgi->{$_};" if ($cgi->{$_});
}
chop $query;
# System users will view their own record only
my $cond = new GT::SQL::Condition($cd);
if ( !$skip_user ) {
if ( $USER->{usr_type} != ADMINISTRATOR ) {
$cond->add($user_field, '=', $USER->{usr_username});
}
elsif ( $cgi->{$user_field} ) {
my $o = $cgi->{"$user_field-opt"} || '=';
$cond->add($user_field, $o, $cgi->{$user_field});
}
else {
my $user = load_condition($show_user);
$cond->add($user_field, $user->{opt}, $user->{id});
}
}
# Do the search and count the results.
if ( !$select_all ) {
$db->select_options("ORDER BY $sb $so LIMIT $bg, $mh ");
}
my $sth = $db->select($cond) or die $GT::SQL::error;
my $hits= $db->hits;
return language($return_msg, 0) if ( $hits == 0 );
if ( $#words == -1 and $cgi->{lu} ) {
@words = split(/\s/, $cgi->{lu});
}
my @output;
my @colors = ('#ff8888', '#88ff88', '#8888ff', '#ffff88', '#ff88ff', '#88ffff', '#ffcccc', '#cccc99', '#ffffcc', '#ffccff');
while ( my $rs = $sth->fetchrow_hashref ) {
if ( $CFG->{highlight_color} ) {
if ( $#words != -1 ) {
foreach my $c ( keys % $cols ) {
next if ( !$cols->{$c}->{weight} );
my $j = 0;
foreach my $i (0..$#words) {
$j = 0 if ( $j > $#colors );
$rs->{$c} =~ s/$words[$i]/<span style="background-color: $colors[$j]">$words[$i]<\/span>/gi;
$j++;
}
}
}
}
push @output, $rs;
}
return { hits => $hits,
results => \@output,
msg => language($return_msg, $hits),
query => $query,
mh => $mh,
nh => $nh,
lookup => $cgi->{keyword},
toolbar_table => $table_name
};
}
sub remove_attachments {
#-----------------------------------------------------------------------
#
my ($id, $dir) = @_;
my $path = "$CFG->{priv_path}/attachments/$dir/" . ($id % 10) . "/$id";
(-e $path) or return "<font color=red>Invalid path $path!</font>";
opendir (DIR, $path) or return GList::language('DIR_OPEN_ERR', $path, $!);
my @list = readdir(DIR);
closedir (DIR);
foreach my $file (@list) {
($file eq '.') and next;
($file eq '..') and next;
unlink "$path/$file";
}
rmdir $path;
return;
}
sub load_condition {
#-----------------------------------------------------------------------
# Loads the user listings in a group for searching
# It will be returned a hash
#
my $show_user = shift;
my $cgi = $IN->get_hash();
$show_user ||= $cgi->{users};
if ( $show_user and $USER->{usr_type} == ADMINISTRATOR) { # For admin
return { id => $USER->{usr_username}, opt => '<>' };
}
else { # Check current user
return { id => $USER->{usr_username}, opt => '=' };
}
}
sub get_data {
#--------------------------------------------------------------------
# Get data of a record
#
my $table = shift;
my $values;
my $mod = $IN->param('modify');
if ( $IN->param('modify') == 0 ) {
$values = $IN->get_hash;
}
else {
my $lookup = {};
my $db = $DB->table($table);
my $pk = $db->pk;
foreach ( @$pk ) { $lookup->{$_} = $IN->param("$mod-$_"); }
$values = $db->get($lookup, 'HASH');
}
return $values;
}
sub format_insert_cgi {
#-----------------------------------------------------------------------------
#
my ($db, $cgi) = @_;
$cgi ||= $IN->get_hash;
my $cols = $db->cols;
foreach ( keys % $cols ) {
if ( !exists $cgi->{$_} and uc($cols->{$_}->{form_type}) eq 'CHECKBOX' ) {
$cgi->{$_} = '';
}
next unless ( ref ($cgi->{$_}) eq 'ARRAY' );
$cgi->{$_} = join ($GT::SQL::Display::HTML::INPUT_SEPARATOR, sort (@{$cgi->{$_}}));
}
return $cgi;
}
sub check_owner {
#--------------------------------------------------------------------
# User can only modify their own record, except admin
#
my ($table, $pre_fix, $id) = @_;
my $info = $DB->table($table)->get($id);
( $info ) or return "<font color=red>$id does not exist!</font>";
# Users can only modify their own records
if ( $USER->{usr_type} != ADMINISTRATOR and $info->{$pre_fix.'_user_id_fk'} ne $USER->{usr_username} ) {
return GList::language('SYS_PER_DENIED');
}
return $info;
}
sub check_limit {
#------------------------------------------------------------------------------
# Check account limits
#
my ($type, $list_id) = @_;
return if ($USER->{usr_type} != LIMITED_USER);
$error = '';
if ($type eq 'list') { # limit number of list
if ($DB->table('Lists')->count({ lst_user_id_fk => $USER->{usr_username} }) >= $USER->{usr_limit_list}) {
$error = GList::language('SYS_OVERLIMIT_LIST');
return 1;
}
}
elsif ($type eq 'sublist') { # limit number of subscribers per list
if ($DB->table('Subscribers')->count(
{ sub_user_id_fk => $USER->{usr_username}, sub_list_id_fk => $list_id }) >= $USER->{usr_limit_sublist} ) {
$error = GList::language('SYS_OVERLIMIT_SUBLIST');
return 1;
}
}
elsif ($type eq 'email30') { # limit number of email sending out in the last 30 days
require GT::Date;
require GT::SQL::Condition;
my $last30 = GT::Date::date_sub(GT::Date::date_get(), 30);
my $unix_time = date_to_time($last30);
my $num_sent = $DB->table('MailingIndex', 'EmailMailings')->count(
GT::SQL::Condition->new(
mli_user_id_fk => '=' => $USER->{usr_username},
eml_sent => '>=' => $unix_time
)
);
if ( $num_sent >= $USER->{usr_limit_email30} ) {
$error = GList::language('SYS_OVERLIMIT_EMAIL30');
return 1;
}
return $num_sent;
}
return;
}
sub load_language {
# -----------------------------------------------------------------------------
# Loads the language.txt file. You can either pass in a template set, or let
# it auto-detect from t=, or fall back to the default.
#
my $t = shift || scalar $IN->param('t') || $CFG->{template_set} || 'gossamer';
$LANGUAGE = undef if !$LANG_TPL or $LANG_TPL ne $t;
$LANGUAGE ||= GT::Config->load("$CFG->{priv_path}/templates/$t/language.txt", { create_ok => 1, inheritance => 1, local => 1, header => <<HEADER });
# This file is auto generated and contains a perl hash of
# your language variables for the '$t' template set.
# Generated on: [localtime]
HEADER
$LANG_TPL = $t;
}
sub load_globals {
# -----------------------------------------------------------------------------
# Loads the globals.txt file into $GLOBALS. Takes one (optional) value; if
# false or absent, compile_subs will be passed to the GT::Config object, if
# true it will be omitted.
#
my $no_subs = shift;
$GLOBALS = undef if $no_subs xor $GLOB_NO_SUBS;
$GLOBALS ||= GT::Config->load("$CFG->{priv_path}/templates/common/globals.txt", {
$no_subs ? () : (compile_subs => 'GList'),
inheritance => 1,
local => 1,
cache => 1,
header => <<'HEADER'
# This file is auto generated and contains a perl hash of
# your template globals.
# Generated on: [localtime]
HEADER
});
$GLOB_NO_SUBS = $no_subs;
}
sub language {
# ------------------------------------------------------------------
# Process a language request, it's only loaded once, and saved in
# $LANGUAGE.
#
require GT::Config;
my $code = shift || '';
load_language();
if (exists $LANGUAGE->{$code}) {
return @_ ? sprintf($LANGUAGE->{$code}, @_) : $LANGUAGE->{$code};
}
else {
return $code;
}
}
sub fatal {
# --------------------------------------------------------------
# Return a fatal error message to the browser.
#
die @_ if (GT::Base->in_eval()); # Don't do anything if we are in eval.
my $msg = shift;
my $debug = defined $CFG->{debug_level} ? $CFG->{debug_level} : $DEBUG;
$IN ||= new GT::CGI;
if (defined $CFG and exists $CFG->{error_message} and $CFG->{error_message}) {
$CFG->{error_message} =~ s,<%error%>,$msg,g;
$CFG->{error_message} =~ s,<%environment%>,environment(),eg;
display('error_form.html', { msg => language('SYS_FATAL', $CFG->{error_message}) });
}
else {
display('error_form.html', { msg => language('SYS_FATAL', $msg) });
}
if ($debug) {
print environment();
}
}
sub view_file {
#---------------------------------------------------------------
# View a file
#
my $fn = $IN->param('fn');
my $fd = $IN->param('fd');
my $type = $IN->param('ft');
$fn and $fd or return display('error_form.html', { msg => language('SYS_FILE_INVALID') });
# Check file existing
my $file = $DB->table($type ? 'MessageAttachments' : 'MailingAttachments')->get($fn);
$file or return display('error_form.html', { msg => language('SYS_FILE_NOT_FOUND', $fn) });
my $full_file = "$CFG->{priv_path}/attachments/".(( $type ) ? 'messages' : 'mailings')."/".($fd % 10)."/$fd/$fn";
my $file_name = ( $type ) ? 'att_file_name' : 'mat_file_name';
my $content_type = _load_mime($file->{$file_name});
my ($ext) = $full_file =~ /\.([^.]+)$/;
my $file_size = -s $full_file;
if (open DATA, $full_file) {
if (($content_type =~ m/text/ or -T $full_file) and uc($ext) ne 'PDF') {
print $IN->header;
}
else {
warn "Content-type: $content_type, Content-Length: $file_size";
print $IN->header({
'-type' => $content_type,
'-Content-Length' => $file_size,
});
}
binmode STDOUT;
binmode DATA;
my $buffer;
print $buffer while (read(DATA, $buffer, READ_SIZE));
close DATA;
return;
}
else {
return ('error_form.html', { msg => language('SYS_FILE_ERR', $fn) });
}
}
sub download_file {
#--------------------------------------------------------------
# Download a file
#
my $fn = $IN->param('fn');
my $fd = $IN->param('fd');
my $type = $IN->param('ft');
( $fn and $fd ) or return display('error_form.html', { msg => language('SYS_FILE_INVALID') });
# Check file existing
my $file = $DB->table(( $type ) ? 'MessageAttachments' : 'MailingAttachments')->get($fn);
$file or return display('error_form.html', { msg => language('SYS_FILE_NOT_FOUND', $fn) });
my $full_file = "$CFG->{priv_path}/attachments/".($type ? 'messages' : 'mailings')."/".($fd % 10)."/$fd/$fn";
my $file_name = $type ? 'att_File_Name' : 'mat_File_Name';
my $file_size = -s $full_file;
if (open DATA, $full_file) {
print $IN->header(
'-type' => 'application/download',
'-Content-Length' => $file_size,
'-Content-Transfer-Encoding' => 'binary',
'-Content-Disposition' => \"attachment; filename=$file->{$file_name}"
);
binmode STDOUT;
binmode DATA;
my $buffer;
print $buffer while (read(DATA, $buffer, READ_SIZE));
close DATA;
return;
}
else {
return ('error_form.html', { msg => language('SYS_FILE_ERR', $fn) } );
}
}
sub encrypt {
# -------------------------------------------------------------------
my ($clear_pass, $salt) = @_;
defined $salt or ($salt = '');
require GT::MD5::Crypt;
if (! $salt) {
my @rand_salt = ('a' .. 'z', 'A' .. 'Z', 0 .. 9, '.', '/');
for (1 .. 8) { $salt .= $rand_salt[rand @rand_salt]; }
}
my $enc_pass = GT::MD5::Crypt::gt_md5_crypt($clear_pass, $salt);
return $enc_pass;
}
sub date_to_time {
my ($date, $date_format) = @_;
my $lt;
my @localtime;
require GT::Date;
$date_format ||= '%yyyy%-%mm%-%dd%';
DATE: {
# First, try the admin format:
ref($lt = GT::Date::_parse_format($date, $date_format)) eq 'ARRAY' and (@localtime = @$lt), last DATE;
# Okay, it wasn't simply them modifying what was displayed, so let's try some other common formats:
# just the date, no time:
# yyyy/mm/dd
$date =~ m|^\d{4}([-/])\d{1,2}([-/])\d{1,2}$| and (@localtime = @{GT::Date::_parse_format($date, "%yyyy%$1%m%$2%d%")}), last DATE;
# 12 hour time:
# yyyy/mm/dd hh:MM [AP]M
$date =~ m|^\d{4}([-/])\d{1,2}([-/])\d{1,2} \d{1,2}:\d{1,2} [AaPp][Mm]$| and (@localtime = @{GT::Date::_parse_format($date, "%yyyy%$1%m%$2%d% %h%:%M% %tt%")}), last DATE;
# yyyy/mm/dd hh:MM:ss [AP]M
$date =~ m|^\d{4}([-/])\d{1,2}([-/])\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} [AaPp][Mm]$| and (@localtime = @{GT::Date::_parse_format($date, "%yyyy%$1%m%$2%d% %h%:%M%:%s% %tt%")}), last DATE;
# 24 hour time:
# yyyy/mm/dd HH:MM
$date =~ m|^\d{4}([-/])\d{1,2}([-/])\d{1,2} \d{1,2}:\d{1,2}$| and (@localtime = @{GT::Date::_parse_format($date, "%yyyy%$1%m%$2%d% %H%:%M%")}), last DATE;
# yyyy/mm/dd HH:MM:ss
$date =~ m|^\d{4}([-/])\d{1,2}([-/])\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}$| and (@localtime = @{GT::Date::_parse_format($date, "%yyyy%$1%m%$2%d% %H%:%M%:%s%")}), last DATE;
# Common formats that can't be recognized:
# dd/mm/yyyy - These two are conflicting US/European formats and it would
# mm/dd/yyyy - be impossible to figure out which one you are trying to use.
}
return scalar @localtime ? GT::Date::timelocal(@localtime) : undef;
}
sub paging {
# --------------------------------------------------------------
# Returns paging variables for the templates.
# Takes 4 arguments: number of hits, hits per page, the current page, and the number of pages to show.
# Takes 1 additional optional argument - true or false, indicating whether or not a ... system will be
# used. If set, one extra number will be returned if there is just one extra number needed, and a
# 'dotdotdot' variable will be available as 1 if ... is needed.
# Example: when displaying paging of 9 with 11 pages, you would get:
# 1 2 3 4 5 6 7 8 9 and you would have the "dotdotdot" variable set, so you would put a ... and then 11.
# Now, if you were displaying paging of 9 with 10 pages, you would actually get _10_ numbers:
# 1 2 3 4 5 6 7 8 9 10 and the "dotdotdot" wouldn't be set, so you wouldn't put the ... 10, since
# 1 2 3 4 5 6 7 8 9 ... 10 would look silly.
# Returned is a hashref: { paging => LOOP, top_page => INTEGER }, and possibly dotdotdot => 1
# Inside the loop you have: <%page_num%> and <%is_current_page%>.
#
my ($num_hits, $max_hits, $current_page, $disp_pages, $want_dotdotdot) = @_;
$disp_pages ||= 20;
$max_hits ||= 25;
my $num_pages = int($num_hits / $max_hits);
$num_pages++ if $num_hits % $max_hits;
my ($start, $end);
if ($num_pages <= $disp_pages) {
$start = 1;
$end = $num_pages;
}
elsif ($current_page >= $num_pages - $disp_pages / 2) {
$end = $num_pages;
$start = $end - $disp_pages + 1;
}
elsif ($current_page <= $disp_pages / 2) {
$start = 1;
$end = $disp_pages;
}
else {
$start = $current_page - int($disp_pages / 2) + 1;
$start-- if $disp_pages % 2;
$end = $current_page + int($disp_pages / 2);
}
my $need_dotdotdot;
if ($want_dotdotdot) {
if ($num_pages == $end + 1) {
++$end;
}
elsif ($num_pages > $end) {
$need_dotdotdot = 1;
}
}
my @pages = map +{ page_num => $_, (($_ == $current_page) ? (is_current_page => 1) : ()) }, $start .. $end;
return {
paging => \@pages,
top_page => $num_pages,
($want_dotdotdot && $need_dotdotdot ? (dotdotdot => 1) : ())
};
}
sub wild_cards() {
require GT::SQL::Condition;
return $DB->table('StopLists')->select(GT::SQL::Condition->new(stl_email => LIKE => "%*%", stl_email => LIKE => "%?%", "OR"), ['stl_email'])->fetchall_arrayref;
}
sub _redirect_login_url {
# --------------------------------------------------------------
# Redirect the user to the login screen.
#
my $url = $IN->url( query_string => 1 );
$url = $CFG->{cgi_url} . "/user.cgi?url=" . $IN->escape($url);
foreach my $preserve (@{$CFG->{dynamic_preserve}}) {
my $val = $IN->param($preserve);
defined $val or next;
$url .= ";$preserve=" . $IN->escape($val);
}
return $url;
}
sub _load_mime {
# --------------------------------------------------------------------
# Load the config file into a hash.
#
my ($file, $name) = @_;
$name ||= $file;
require GT::MIMETypes;
my $guess = GT::MIMETypes->guess_type($name);
if (!$guess or $guess eq 'application/octet-stream') {
if (-e $file) {
$guess = -T _ ? 'text/plain' : 'application/octet-stream';
}
else {
$guess = 'application/octet-stream';
}
}
return $guess;
}
sub _load_global {
my $name = shift;
load_globals();
return if (!exists $GLOBALS->{$name});
my $value = $GLOBALS->{$name};
$value = $value->() if ref $value eq 'CODE';
return $value;
}
1;