Initial commit.

This commit is contained in:
2026-03-10 21:25:26 +08:00
commit 78739bf725
3089 changed files with 472990 additions and 0 deletions

171
lib/perl5/Selima.pm Normal file
View File

@@ -0,0 +1,171 @@
# Selima Website Content Management System
# Selima.pm: Selima Website Content Management System
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-23
package Selima;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw($VERSION @EXPORT @EXPORT_OK);
$VERSION = "3.10";
@EXPORT = qw();
# Import the subroutines
use Selima::A2HTML;
push @EXPORT, @Selima::A2HTML::EXPORT;
use Selima::AbsURI;
push @EXPORT, @Selima::AbsURI::EXPORT;
use Selima::Accounting;
push @EXPORT, @Selima::Accounting::EXPORT;
use Selima::AddGet;
push @EXPORT, @Selima::AddGet::EXPORT;
use Selima::AltLang;
push @EXPORT, @Selima::AltLang::EXPORT;
use Selima::Array;
push @EXPORT, @Selima::Array::EXPORT;
use Selima::Cache qw();
push @EXPORT, @Selima::Cache::EXPORT;
use Selima::CallForm;
push @EXPORT, @Selima::CallForm::EXPORT;
use Selima::ChkFunc;
push @EXPORT, @Selima::ChkFunc::EXPORT;
use Selima::ChkPriv;
push @EXPORT, @Selima::ChkPriv::EXPORT;
use Selima::ChkWrite;
push @EXPORT, @Selima::ChkWrite::EXPORT;
use Selima::CommText;
push @EXPORT, @Selima::CommText::EXPORT;
use Selima::CopyYear;
push @EXPORT, @Selima::CopyYear::EXPORT;
use Selima::Country;
push @EXPORT, @Selima::Country::EXPORT;
use Selima::DataVars qw(:all);
push @EXPORT, @Selima::DataVars::EXPORT_OK;
use Selima::DBILogin;
push @EXPORT, @Selima::DBILogin::EXPORT;
use Selima::DecForm;
push @EXPORT, @Selima::DecForm::EXPORT;
use Selima::EchoForm;
push @EXPORT, @Selima::EchoForm::EXPORT;
use Selima::Encrypt;
push @EXPORT, @Selima::Encrypt::EXPORT;
use Selima::ErrMsg;
push @EXPORT, @Selima::ErrMsg::EXPORT;
use Selima::FetchRec;
push @EXPORT, @Selima::FetchRec::EXPORT;
use Selima::FormFunc;
push @EXPORT, @Selima::FormFunc::EXPORT;
use Selima::Format;
push @EXPORT, @Selima::Format::EXPORT;
use Selima::GeoIP;
push @EXPORT, @Selima::GeoIP::EXPORT;
use Selima::GetLang;
push @EXPORT, @Selima::GetLang::EXPORT;
use Selima::Guest;
push @EXPORT, @Selima::Guest::EXPORT;
use Selima::Guestbook;
push @EXPORT, @Selima::Guestbook::EXPORT;
use Selima::HTTP;
push @EXPORT, @Selima::HTTP::EXPORT;
use Selima::HTTPS;
push @EXPORT, @Selima::HTTPS::EXPORT;
use Selima::Init;
push @EXPORT, @Selima::Init::EXPORT;
use Selima::Links;
push @EXPORT, @Selima::Links::EXPORT;
use Selima::ListFunc;
push @EXPORT, @Selima::ListFunc::EXPORT;
use Selima::ListPref;
push @EXPORT, @Selima::ListPref::EXPORT;
use Selima::LnInfo;
push @EXPORT, @Selima::LnInfo::EXPORT;
use Selima::Logging;
push @EXPORT, @Selima::Logging::EXPORT;
use Selima::LogIn;
push @EXPORT, @Selima::LogIn::EXPORT;
use Selima::LogOut;
push @EXPORT, @Selima::LogOut::EXPORT;
use Selima::LastModf;
push @EXPORT, @Selima::LastModf::EXPORT;
use Selima::MarkAbbr;
push @EXPORT, @Selima::MarkAbbr::EXPORT;
use Selima::MkAllDir;
push @EXPORT, @Selima::MkAllDir::EXPORT;
use Selima::MungAddr;
push @EXPORT, @Selima::MungAddr::EXPORT;
use Selima::NewSN;
push @EXPORT, @Selima::NewSN::EXPORT;
use Selima::PageFunc;
push @EXPORT, @Selima::Page::EXPORT;
use Selima::Page2Rel;
push @EXPORT, @Selima::Page2Rel::EXPORT;
use Selima::Passwd;
push @EXPORT, @Selima::Passwd::EXPORT;
use Selima::Picture;
push @EXPORT, @Selima::Picture::EXPORT;
use Selima::Preview;
push @EXPORT, @Selima::Preview::EXPORT;
use Selima::Query;
push @EXPORT, @Selima::Query::EXPORT;
use Selima::RelURI;
push @EXPORT, @Selima::RelURI::EXPORT;
use Selima::RemoHost;
push @EXPORT, @Selima::RemoHost::EXPORT;
use Selima::ReqURI;
push @EXPORT, @Selima::ReqURI::EXPORT;
use Selima::ScptPriv;
push @EXPORT, @Selima::ScptPriv::EXPORT;
use Selima::SetL10N;
push @EXPORT, @Selima::SetL10N::EXPORT;
use Selima::Server;
push @EXPORT, @Selima::Server::EXPORT;
use Selima::ShortCut;
push @EXPORT, @Selima::ShortCut::EXPORT;
use Selima::Unauth;
push @EXPORT, @Selima::Unauth::EXPORT;
use Selima::Unicode;
push @EXPORT, @Selima::Unicode::EXPORT;
use Selima::UserName;
push @EXPORT, @Selima::UserName::EXPORT;
use Selima::UserPref;
push @EXPORT, @Selima::UserPref::EXPORT;
use Selima::XFileIO;
push @EXPORT, @Selima::XFileIO::EXPORT;
# Pre-load :flock symbles
use Fcntl qw(:flock);
push @EXPORT, qw(LOCK_SH LOCK_EX LOCK_NB LOCK_UN);
@EXPORT_OK = @EXPORT;
# Load the classes
use Selima::AddCol;
use Selima::L10N;
use Selima::List;
use Selima::Checker;
use Selima::DBI;
use Selima::Destroy;
use Selima::Form;
use Selima::Mail;
use Selima::Page;
use Selima::Processor;
use Selima::Session;
return 1;

101
lib/perl5/Selima/A2HTML.pm Normal file
View File

@@ -0,0 +1,101 @@
# Selima Website Content Management System
# A2HTML.pm: The text to HTML converter.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-24
package Selima::A2HTML;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(a2html);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub a2html($);
}
use Email::Find qw();
use URI::Find qw();
use Selima::MarkAbbr;
use Selima::MungAddr;
use Selima::ShortCut;
use Selima::Unicode;
use vars qw(%SUBST $TEXT $uri_finder $email_finder);
$uri_finder = new URI::Find( \&uri_subst );
$email_finder = new Email::Find( \&email_subst );
# a2html: Convert a textarea input into HTML content
sub a2html($) {
local ($_, %_);
$TEXT = $_[0];
# Clear the registry
%SUBST = qw();
# Strip the URLs
$uri_finder->find(\$TEXT);
# Strip the e-mails
$email_finder->find(\$TEXT);
# Escape the HTML characters and mark the abbreviation
$TEXT = h_abbr($TEXT);
# Return the substitutied URLs and e-mails as links
$TEXT =~ s/(\[subst:(\d{9})\])/exists $SUBST{$2}? $SUBST{$2}: $1;/ge;
# Normal text-to-HTML substitution
$TEXT =~ s/^ /&nbsp;/mg;
$TEXT =~ s/ /&nbsp; /g;
$TEXT =~ s/\n/<br \/>\n/g;
return $TEXT;
}
# uri_subst: Substitute an URL in the content
sub uri_subst {
local ($_, %_);
my ($uri, $uritext);
($uri, $uritext) = @_;
do {
# Generate a random serial number
$_ = 100000000 + int rand 900000000;
} until $TEXT !~ /\[subst:$_\]/;
# Register this URI
$SUBST{$_} = "<a href=\"" . h($uri) . "\"><samp>"
. mung_email_span(h($uritext)) . "</samp></a>";
# Return the substitution
return "[subst:$_]";
}
# email_subst: Substitute an e-mail in the content
sub email_subst {
local ($_, %_);
my ($email, $emailtext);
($email, $emailtext) = @_;
do {
# Generate a random serial number
$_ = 100000000 + int rand 900000000;
} until $TEXT !~ /\[subst:$_\]/;
# Register this URI
$SUBST{$_} = "<a href=\"mailto:" . h($email->format) . "\"><samp>"
. mung_email_span(h($emailtext)) . "</samp></a>";
# Return the substitution
return "[subst:$_]";
}
return 1;

View File

@@ -0,0 +1,57 @@
# Selima Website Content Management System
# AbsURI.pm: The converter to turn all URIs to absolute URIs.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-26
package Selima::AbsURI;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(absuri);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub absuri($;$$);
}
use URI qw();
use Selima::DataVars qw(:requri);
# absuri: Convert and colonicalize to an absolute URI
sub absuri($;$$) {
local ($_, %_);
my ($uri, $base, $skip_fragment);
($uri, $base, $skip_fragment) = @_;
# Default base to $REQUEST_FULLURI
$base = defined $base? new URI($base): $REQUEST_FULLURI;
# Skip the fragment
return $uri if $skip_fragment && $uri =~ /^#/;
# Absolute path -- add the root difference
$uri = "$ROOT_DIFF$uri" if $uri =~ /^\//;
$uri = new URI($uri);
# Obtain the absolute URI
$uri = $uri->abs($base);
return $uri->canonical;
}
return 1;

View File

@@ -0,0 +1,294 @@
# Selima Website Content Management System
# Accounting.pm: The accounting subroutines.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-20
package Selima::Accounting;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(acctsubj_title acctsubj_code acctsubj_sn);
push @EXPORT, qw(acctsubj_recent_options accttrx_id accttrxid_compose);
push @EXPORT, qw(accttrx_maxord);
push @EXPORT, qw(ACCTSUBJ_CASH ACCTSUBJ_INCOME_ACUM ACCTSUBJ_INCOME_CUR);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub acctsubj_title($);
sub acctsubj_code($);
sub acctsubj_sn($);
sub acctsubj_recent_options($);
sub accttrx_id($);
sub accttrxid_compose($$);
sub accttrx_maxord(;$$);
}
use Selima::Cache qw(:account);
use Selima::ChkFunc;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo);
use Selima::EchoForm;
use Selima::GetLang;
use Selima::LnInfo;
# Certain subjects
use constant ACCTSUBJ_CASH => 1111; # 1111 庫存現金
use constant ACCTSUBJ_INCOME_ACUM => 3351; # 3351 累積盈虧
use constant ACCTSUBJ_INCOME_CUR => 3353; # 3353 本期損益
# acctsubj_title: Obtain an accounting subject title
sub acctsubj_title($) {
local ($_, %_);
my ($sn, $sql, $sth, $col);
$sn = $_[0];
# Bounce if there is any problem with $sn
return t_notset if !defined $sn;
# Return the cache
return $Account_acctsubj_title{$sn} if exists $Account_acctsubj_title{$sn};
# Check the serial number first
return ($Account_acctsubj_title{$sn} = t_na)
if !check_sn $sn;
# Query
# Unilingual
if (@ALL_LINGUAS == 1) {
$col = "acctsubj_codetitle($sn) AS title";
# Multilingual
} else {
$_ = getlang;
$col = "acctsubj_codetitle('$_', $sn) AS title";
}
$sql = "SELECT $col;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Account_acctsubj_title{$sn} = t_na)
unless $sth->rows == 1;
# Found
return ($Account_acctsubj_title{$sn} = ${$sth->fetch}[0]);
}
# acctsubj_code: Obtain an accounting subject code
sub acctsubj_code($) {
local ($_, %_);
my ($sn, $sql, $sth, $col);
$sn = $_[0];
# Bounce if there is any problem with $sn
return t_notset if !defined $sn;
# Return the cache
return $Account_acctsubj_code{$sn} if exists $Account_acctsubj_code{$sn};
# Check the serial number first
return ($Account_acctsubj_code{$sn} = t_na)
if !check_sn $sn;
# Query
$sql = "SELECT code FROM acctsubj WHERE sn=$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Account_acctsubj_code{$sn} = t_na)
unless $sth->rows == 1;
# Found
return ($Account_acctsubj_code{$sn} = ${$sth->fetch}[0]);
}
# acctsubj_sn: Obtain an accounting subject S/N
sub acctsubj_sn($) {
local ($_, %_);
my ($code, $sql, $sth, $col);
$code = $_[0];
# Bounce if there is any problem with $code
return t_notset if !defined $code;
# Return the cache
return $Account_acctsubj_sn{$code} if exists $Account_acctsubj_sn{$code};
# Query
$sql = "SELECT sn FROM acctsubj"
. " WHERE code=" . $DBH->quote($code) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Account_acctsubj_sn{$code} = t_na)
unless $sth->rows == 1;
# Found
return ($Account_acctsubj_sn{$code} = ${$sth->fetch}[0]);
}
# acctsubj_recent_options: Obtain a recently-used accounting subject options list
sub acctsubj_recent_options($) {
local ($_, %_);
my ($value, $sql, $content);
my ($sth, $count, $row, @opts, $hascur, $optlist);
$value = $_[0];
# Obtain the recently-used options
# Unilingual
if (@ALL_LINGUAS == 1) {
$content = "acctsubj_codetitle(subj) AS content";
# Multilingual
} else {
$_ = getlang;
$content = "acctsubj_codetitle('$_', subj) AS content";
}
$sql = "SELECT subj AS value, $content FROM acctrecs"
. " GROUP BY subj"
. " ORDER BY acctsubj_recent(subj) DESC;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @opts = qw(), $hascur = 0; $i < $count; $i++) {
$row = $sth->fetchrow_hashref;
push @opts, {
"value" => $$row{"value"},
"content" => $$row{"content"},
};
$hascur = 1 if defined $value && $$row{"value"} eq $value;
}
undef $sth;
# Prepend the currently selected option if available
if (!$hascur && defined $value && check_sn $value) {
# Unilingual
if (@ALL_LINGUAS == 1) {
$content = "acctsubj_codetitle($value) AS content";
# Multilingual
} else {
$_ = getlang;
$content = "acctsubj_codetitle('$_', $value) AS content";
}
$sql = "SELECT $content;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
if ($sth->rows > 0) {
$row = $sth->fetchrow_hashref;
@opts = ({
"value" => $value,
"content" => $$row{"content"},
}, @opts);
}
undef $sth;
}
# Obtain the HTML
$optlist = opt_list_array @opts;
return preselect_options $optlist, $value;
}
# accttrx_id: Obtain the accounting transaction ID.
sub accttrx_id($) {
local ($_, %_);
my ($sn, $sql, $sth, $col, $row);
$sn = $_[0];
# Bounce if there is any problem with $sn
return t_notset if !defined $sn;
# Return the cache
return $Account_accttrx_id{$sn} if exists $Account_accttrx_id{$sn};
# Check the serial number first
return ($Account_accttrx_id{$sn} = t_na)
if !check_sn $sn;
# Query
$sql = "SELECT date, ord FROM accttrx WHERE sn=$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Account_accttrx_id{$sn} = t_na)
unless $sth->rows == 1;
# Found
$row = $sth->fetchrow_hashref;
return ($Account_accttrx_id{$sn} = accttrxid_compose $$row{"date"}, $$row{"ord"});
}
# accttrxid_compose: Compose the accounting transaction ID
sub accttrxid_compose($$) {
local ($_, %_);
my ($date, $ord);
($date, $ord) = @_;
# In timestamp
if ($date =~ /^\d+$/) {
@_ = localtime $date;
$_[5] += 1900;
$_[4]++;
return sprintf "%04d%02d%02d%02d", @_[5,4,3], $ord;
}
# In ISO date YYYY-MM-DD format
return sprintf "%04d%02d%02d%02d", $1, $2, $3, $ord
if $date =~ /^(\d{4})-(\d{2})-(\d{2})$/;
# Invalid date
return undef;
}
# accttrx_maxord: Obtain the default accounting transaction order
sub accttrx_maxord(;$$) {
local ($_, %_);
my ($date, $sn, $sql, $sth, $row);
($date, $sn) = @_;
$date = time if !defined $date;
$sn = -1 if @_ < 2;
# In timestamp
if ($date =~ /^\d+$/) {
@_ = localtime $date;
$_[5] += 1900;
$_[4]++;
$date = sprintf "%04d-%02d-%02d", @_[5,4,3];
# In ISO date YYYY-MM-DD format
} elsif ($date =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
# Invalid date
} else {
return 99;
}
# Bounce if there is any problem with $sn
return 99 if !defined $sn;
# Return the cache
$Account_accttrx_id{$date} = {} if !exists $Account_accttrx_id{$date};
return ${$Account_accttrx_id{$date}}{$sn}
if exists ${$Account_accttrx_id{$date}}{$sn};
# Check the serial number first
return (${$Account_accttrx_id{$date}}{$sn} = 99)
if $sn != -1 && !check_sn $sn;
# Query
@_ = qw();
push @_, "date=" . $DBH->quote($date);
push @_, "sn!=$sn" if $sn != -1;
$sql = "SELECT count(*) AS count FROM accttrx"
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$row = $sth->fetchrow_hashref;
return (${$Account_accttrx_id{$date}}{$sn} = $$row{"count"} + 1);
}
return 1;

872
lib/perl5/Selima/AddCol.pm Normal file
View File

@@ -0,0 +1,872 @@
# Selima Website Content Management System
# AddCol.pm: The data collector/handler for SQL/XML/CSV data output ("Model").
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-26
package Selima::AddCol;
use 5.008;
use strict;
use warnings;
use Date::Parse qw(str2time);
use Digest::MD5 qw(md5_hex);
use Encode qw(encode decode is_utf8 FB_CROAK);
use Selima::Array;
use Selima::DataVars qw($DBH :addcol :input :lninfo);
use Selima::Format;
use Selima::GetLang;
use Selima::LogIn;
use Selima::Picture;
use Selima::ShortCut;
use constant TYPE_NULL => 0;
use constant TYPE_NUM => 1;
use constant TYPE_STR => 2;
use constant TYPE_DATE => 3;
use constant TYPE_IPADDR => 4;
use constant TYPE_FILE => 5;
use constant TYPE_BOOL => 6;
use constant TYPE_EXPR => 7;
# Prototype declaration
sub valout_sql($);
sub valout_xml($);
sub valout_csv($);
# new: Initialize the columns deposit
sub new : method {
local ($_, %_);
my ($class, $table, $optype, $self);
($class, $table, $optype) = @_;
$optype = ADDCOL_UPDATE if !defined $optype;
$self = bless {}, $class;
$self->{"cols"} = [];
$self->{"table"} = $table;
$self->{"optype"} = $optype;
if (defined $DBH) {
$self->{"allcols"} = [$DBH->cols($table)];
$self->{"mlcols"} = [$DBH->cols_ml($table)];
} else {
$self->{"allcols"} = [];
$self->{"mlcols"} = [];
}
return $self;
}
# addstr: Add a modified string column to the columns deposit
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addstr : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val || $val eq "") {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_STR;
$col{"value"} = $val;
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif ($col{"value"} ne $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Check if we should set it
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addstr_empty: Add a modified string column to the columns deposit,
# where empty string is allowed
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addstr_empty : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val) {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_STR;
$col{"value"} = $val;
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif ($col{"value"} ne $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Check if we should set it
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addurl: Add a modified URL to the columns deposit,
# where "http://" also means empty
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addurl : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val || $val eq "" || $val eq "http://") {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_STR;
$col{"value"} = $val;
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif ($col{"value"} ne $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addpass: Add a modified password to the columns deposit,
# where "" means "not changed". Passwords are never empty.
# Input: $name: The column name.
# $purge: If we should purge the password.
# $val: The column value.
# $curval: The current value to be compared.
sub addpass : method {
local ($_, %_);
my ($self, $name, $purge, $val, $curval, $cur_exists, %col);
($self, $name, $purge, $val, $curval) = @_;
$cur_exists = (@_ == 5);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# Purge the password with a dummy one
if ($purge) {
$col{"type"} = TYPE_STR;
$col{"value"} = "x" x 32;
# No value is supplied
} elsif (!defined $val || $val eq "") {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_STR;
%_ = map { ${$_}{"name"} => $_ } @{$self->{"cols"}};
$col{"value"} = md5_hex(${$_{"id"}}{"value"} . ":magicat:" . $val);
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# A different new value. Modify it.
if ($col{"type"} != TYPE_NULL && $col{"value"} ne $curval) {
$col{"mod"} = 1;
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addpic: Add a picture to the columns deposit
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addpic : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col, $PICS);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
$PICS = pic_deposit;
# Set the 3rd argument as the current value
if ($cur_exists) {
# Get the picture content
$curval = ${$$PICS{$curval}}{"content"}
if defined $curval;
}
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val) {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_FILE;
$col{"value"} = ${$$PICS{$val}}{"content"}
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif ($col{"value"} ne $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addnum: Add a modified numeric column to the columns deposit
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addnum : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val || $val eq "") {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_NUM;
$col{"value"} = $val;
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif ($col{"value"} != $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# adddate: Add a modified date column to the columns deposit
# Mostly the same as addstr(). Different when out.
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub adddate : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val || $val eq "") {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_DATE;
$col{"value"} = $val;
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif (str2time($col{"value"}) != $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addipaddr: Add a modified IP address column to the columns deposit
# Mostly the same as addstr(). Different when out.
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addipaddr : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value is supplied
if (!defined $val || $val eq "") {
$col{"type"} = TYPE_NULL;
$col{"value"} = undef;
# A valid value
} else {
$col{"type"} = TYPE_IPADDR;
$col{"value"} = $val;
}
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column had a value previously
if (defined $curval) {
# It has no value now. Remove it.
if ($col{"type"} == TYPE_NULL) {
$col{"mod"} = 1;
# A different new value. Modify it.
} elsif ($col{"value"} ne $curval) {
$col{"mod"} = 1;
}
# This column had no value previously
} else {
# But it has a value now.
if ($col{"type"} != TYPE_NULL) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addbool: Add a modified boolean column to the columns deposit
# Input: $name: The column name.
# $val: The column value.
# $curval: The current value to be compared.
sub addbool : method {
local ($_, %_);
my ($self, $name, $val, $curval, $cur_exists, %col);
($self, $name, $val, $curval) = @_;
$cur_exists = (scalar(@_) == 4);
$curval = decode("UTF-8", $curval, FB_CROAK) if !defined $DBH;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 0;
# No value supplied means "false"
$col{"type"} = TYPE_BOOL;
$col{"value"} = defined $val && $val;
# Check if we should set it
if ($self->{"optype"} == ADDCOL_INSERT) {
# Always set the value for INSERT
$col{"mod"} = 1;
} else {
# A current value is supplied
if ($cur_exists) {
# This column is previously true
if ($curval) {
# New value is false. Disable it.
if (!$col{"value"}) {
$col{"mod"} = 1;
}
# The current value is false
} else {
# But it is true now.
if ($col{"value"}) {
$col{"mod"} = 1;
}
}
# No current value to compare with.
} else {
# Set it anyway
$col{"mod"} = 1;
}
}
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# addexpr: Add a expression column value to the columns deposit
# Input: $name: The column name.
# $val: The column value.
sub addexpr : method {
local ($_, %_);
my ($self, $name, $val, %col);
($self, $name, $val) = @_;
# Adjust the column name for multi-lingual columns
$name .= "_" . getlang LN_DATABASE
if in_array($name, @{$self->{"mlcols"}});
# The new column value
# Always set it, since it is not possible to compare the current value
%col = qw();
$col{"name"} = $name;
$col{"mod"} = 1;
$col{"type"} = TYPE_EXPR;
$col{"value"} = $val;
# Add this column
push @{$self->{"cols"}}, {%col};
return;
}
# modified: Return if this record is modified
sub modified : method {
local ($_, %_);
my $self;
$self = $_[0];
# Find any column that is modified
foreach my $col (@{$self->{"cols"}}) {
return 1 if ${$col}{"mod"};
}
return 0;
}
# ret: Retrieve the columns deposit as an SQL statement
# Input: $timestamp: Whether we should record timestamps or not
# Log in forms should not update their timestamps.
# Output: An SQL statement in the corresponding query type.
sub ret : method {
local ($_, %_);
my ($self, $timestamp);
($self, $timestamp) = @_;
$timestamp = 1 if !defined $timestamp;
# Set the login user
if ($timestamp) {
$self->{"login"} = get_login_sn
if !exists $self->{"login"};
}
if ($self->{"optype"} == ADDCOL_INSERT) {
my (@names, @vals);
@names = qw();
@vals = qw();
foreach my $col (@{$self->{"cols"}}) {
# Skip columns that are not modified
next unless ${$col}{"mod"};
push @names, $DBH->quote_identifier(${$col}{"name"});
push @vals, valout_sql($col);
}
# Add the timestamp
if ($timestamp) {
if (in_array("created", @{$self->{"allcols"}})) {
push @names, $DBH->quote_identifier("created");
push @vals, "now()";
}
if (in_array("createdby", @{$self->{"allcols"}})) {
push @names, $DBH->quote_identifier("createdby");
push @vals, $self->{"login"};
}
if (in_array("updated", @{$self->{"allcols"}})) {
push @names, $DBH->quote_identifier("updated");
push @vals, "now()";
}
if (in_array("updatedby", @{$self->{"allcols"}})) {
push @names, $DBH->quote_identifier("updatedby");
push @vals, $self->{"login"};
}
}
# Decode from UTF-8, for easier post-processing
return decode("UTF-8", "(" . join(", ", @names) . ")"
. " VALUES (" . join(", ", @vals) . ")");
} else {
my @phrases;
@phrases = qw();
foreach my $col (@{$self->{"cols"}}) {
# Skip columns that are not modified
next unless ${$col}{"mod"};
push @phrases, $DBH->quote_identifier(${$col}{"name"}) . "="
. valout_sql($col);
}
# Add the timestamp
if ($timestamp) {
if (in_array("updated", @{$self->{"allcols"}})) {
push @phrases, $DBH->quote_identifier("updated") . "=now()";
}
if (in_array("updatedby", @{$self->{"allcols"}})) {
$_ = get_login_sn;
$_ = $POST->param("sn") if !defined $_;
push @phrases, $DBH->quote_identifier("updatedby") . "=$_";
}
}
# Decode from UTF-8, for easier post-processing
return decode("UTF-8", "SET " . join(", ", @phrases));
}
}
# retxml: Retrieve the columns deposit as an XML record.
# Input: None.
# Output: An XML record.
sub retxml : method {
local ($_, %_);
my ($self, @vals, $user);
$self = $_[0];
# XML has no engine. Output the whole record anyway,
# no matter updated or not.
@vals = map valout_xml($_), @{$self->{"cols"}};
# Add the updated information
if ($self->{"optype"} == ADDCOL_UPDATE) {
$user = (exists $ENV{"REMOTE_USER"} && $ENV{"REMOTE_USER"} ne "")?
$ENV{"REMOTE_USER"}: "(" . $ENV{"REMOTE_ADDR"} . ")";
push @vals, "<col name=\"updated\" type=\"date\">" . h(fmttime) . "</col>\n";
push @vals, "<col name=\"updatedby\">" . h($user) . "</col>\n";
}
return encode("UTF-8", "<record>\n" . join("", @vals) . "</record>\n", FB_CROAK);
}
# retcsv: Retrieve the columns deposit as a CSV row.
# Input: None.
# Output: A CSV row.
sub retcsv : method {
local ($_, %_);
my $self;
$self = $_[0];
# CSV has no engine. Output the whole record anyway,
# no matter updated or not.
return join(",", map valout_csv($_), @{$self->{"cols"}}) . "\n";
}
###########################
# Private subroutines, not to be called as methods
###########################
# valout_sql: Output a value in a proper SQL format.
sub valout_sql($) {
local ($_, %_);
my ($col, $val);
$col = $_[0];
# Encode first. $DBH->quote() does encode() anyway
if (defined $$col{"value"}) {
$val = $$col{"value"};
$val = encode("UTF-8", $val) if is_utf8($val);
}
return "NULL"
if $$col{"type"} == TYPE_NULL;
return $val
if $$col{"type"} == TYPE_NUM;
return $DBH->quote($val)
if $$col{"type"} == TYPE_STR;
return "'" . $val . "'"
if $$col{"type"} == TYPE_DATE;
return $DBH->quote_blob($val)
if $$col{"type"} == TYPE_FILE;
return "'" . $val . "'"
if $$col{"type"} == TYPE_IPADDR;
return $val? "TRUE": "FALSE"
if $$col{"type"} == TYPE_BOOL;
return $val
if $$col{"type"} == TYPE_EXPR;
}
# valout_xml: Output a value in a proper XML format.
sub valout_xml($) {
local ($_, %_);
$_ = $_[0];
return ""
if $$_{"type"} == TYPE_NULL;
return "<col name=\"" . h($$_{"name"}) . "\">"
. h($$_{"value"}) . "</col>\n"
if $$_{"type"} == TYPE_NUM;
return "<col name=\"" . h($$_{"name"}) . "\">"
. h($$_{"value"}) . "</col>\n"
if $$_{"type"} == TYPE_STR;
return "<col name=\"" . h($$_{"name"}) . "\" type=\"date\">"
. h(fmttime $$_{"value"}) . "</col>\n"
if $$_{"type"} == TYPE_DATE;
return "<col name=\"" . h($$_{"name"}) . "\">"
. h(fmttime $$_{"value"}) . "</col>\n"
if $$_{"type"} == TYPE_FILE;
return "<col name=\"" . h($$_{"name"}) . "\">"
. h($$_{"value"}) . "</col>\n"
if $$_{"type"} == TYPE_IPADDR;
return "<col name=\"" . h($$_{"name"}) . "\">"
. h($$_{"value"}? "TRUE": "FALSE") . "</col>\n"
if $$_{"type"} == TYPE_BOOL;
# XML has no engine. The following is emulated.
if ($$_{"type"} == TYPE_EXPR) {
return "<col name=\"" . h($$_{"name"}) . "\" type=\"date\">"
. h(fmttime) . "</col>\n"
if lc $$_{"value"} eq "now" || lc $$_{"value"} eq "now()";
}
}
# valout_csv: Output a value in a proper CSV format.
sub valout_csv($) {
local ($_, %_);
$_ = $_[0];
return "NULL"
if $$_{"type"} == TYPE_NULL;
return $$_{"value"}
if $$_{"type"} == TYPE_NUM;
if ($$_{"type"} == TYPE_STR) {
$_ = $$_{"value"};
s/\\/\\\\/g;
s/"/\\"/g;
s/\n/\\n/g;
s/\r/\\r/g;
s/\t/\\t/g;
s/\0/\\0/g;
return "\"" . $_ . "\"";
}
return "\"" . $$_{"value"} . "\""
if $$_{"type"} == TYPE_DATE;
return "\"" . $$_{"value"} . "\""
if $$_{"type"} == TYPE_IPADDR;
return $$_{"value"}? "TRUE": "FALSE"
if $$_{"type"} == TYPE_BOOL;
# CSV has no engine. The following is emulated.
if ($$_{"type"} == TYPE_EXPR) {
return "\"" . fmttime() . "\""
if $$_{"value"} =~ /^now(?:\(\))?$/i;
}
}
return 1;

101
lib/perl5/Selima/AddGet.pm Normal file
View File

@@ -0,0 +1,101 @@
# Selima Website Content Management System
# AddGet.pm: The subroutines to manipulate the arguments in an URL.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-30
package Selima::AddGet;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(DUP_OK DUP_NO add_get_arg rem_get_arg);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub add_get_arg($$$;$);
sub rem_get_arg($@);
}
use URI::Escape qw(uri_escape);
# Constant symbols
use constant DUP_OK => 1;
use constant DUP_NO => 0;
# add_get_arg: Add a get argument to the end of an url
sub add_get_arg($$$;$) {
local ($_, %_);
my ($url, $name, $value, $if_dup, $script);
($url, $name, $value, $if_dup) = @_;
$if_dup = DUP_NO if !defined $if_dup;
# Escape the name and value first
$name = uri_escape($name);
$value = uri_escape($value);
# No current arguments are appended yet
return "$url?$name=$value" if $url !~ /\?/;
# It is OK to have duplicated same arguments
return "$url&$name=$value" if $if_dup == DUP_OK;
# Have arguments already. We need to check if there is already
# a duplicated one and replace it if exists.
# Split the arguments
$url =~ /^(.*?)\?(.*)$/;
($script, $_) = ($1, $2);
@_ = split /&/, $_;
# Remove the matched ones
@_ = grep !/^$name=/, @_;
# Add our argument
push @_, "$name=$value";
return "$script?" . join("&", @_);
}
# rem_get_arg: Remove a get argument from the end of an url
sub rem_get_arg($@) {
local ($_, %_);
my ($url, @names, $script, $name);
($url, @names) = @_;
# No current arguments are appended yet
return $url if $url !~ /\?/;
# Escape the names
for ($_ = 0; $_ < @names; $_++) {
# Encode the name first
$names[$_] = uri_escape($names[$_]);
}
# Have arguments already. We need to check if there is already
# a duplicated one and remove it if exists.
# Split the arguments
$url =~ /^(.*?)\?(.*)$/;
($script, $_) = ($1, $2);
@_ = split /&/, $_;
# Check one by one
foreach $name (@names) {
@_ = grep !/^$name=/, @_;
}
# No arguments left
return $script if scalar(@_) == 0;
return "$script?" . join("&", @_);
}
return 1;

145
lib/perl5/Selima/AltLang.pm Normal file
View File

@@ -0,0 +1,145 @@
# Selima Website Content Management System
# AltLang.pm: The subroutines to obtain the URLs of the alternative language versions.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-03
package Selima::AltLang;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(altlang set_altlang_urls);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub altlang($$);
sub set_altlang_urls($);
}
use Data::Dumper qw();
use URI::Escape qw(uri_escape);
use Selima::AddGet;
use Selima::DataVars qw(:input :lninfo :requri);
use Selima::EchoForm;
use Selima::LnInfo;
use Selima::Session;
use Selima::Unicode;
use vars qw(%ALT_LANG %IS_SCRIPT);
# altlang: Obtain the URL of an alternative language version
sub altlang($$) {
local ($_, %_);
my ($lang, $args);
($lang, $args) = @_;
set_altlang_urls $args;
# Return it
return ${$$args{"altlang"}}{$lang};
}
# set_altlang_urls: Set the URLs of the language variants
sub set_altlang_urls($) {
local ($_, %_);
my ($args, $path, $langfile, %urls);
my ($args0, @argkeys0, $need_charset);
$args = $_[0];
# Return if already obtained
return if exists $$args{"altlang"};
# Set the page path
$path = $$args{"path"};
$path .= "index.html" if $path =~ /\/$/;
# Set the URL parameter
if ($$args{"static"}) {
$langfile = ln $$args{"lang"}, LN_FILENAME;
$path =~ s/\.$langfile$//;
$path .= ".%s";
} else {
#$args0 = $USER_INPUT{"GET_UTF8"};
eval Data::Dumper->Dump([$USER_INPUT{"GET_UTF8"}], [qw($args0)]);
@argkeys0 = @{$USER_INPUT{"GET_KEYS"}};
%_ = map { $_ => 1 } @argkeys0;
# Whether we need to specify the character set
$need_charset = !is_usascii_printable($args0->Vars);
# Remove the session ID
if (exists $_{$Selima::Session::NAME}) {
$args0->delete($Selima::Session::NAME);
@argkeys0 = grep $_ ne $Selima::Session::NAME, @argkeys0;
}
# Non-ASCII -- specify the character set
if ($need_charset) {
if (!exists $_{"charset"}) {
$args0->param("charset", "");
push @argkeys0, "charset";
}
# US-ASCII -- we do not need to specify the character set
} else {
if (exists $_{"charset"}) {
$args0->delete("charset");
@argkeys0 = grep $_ ne "charset", @argkeys0;
}
}
# Append the referer
if (auto_keep_referer) {
$args0->param("referer", $ENV{"HTTP_REFERER"});
push @argkeys0, "referer";
}
# Append the language
if (!exists $_{"lang"}) {
$args0->param("lang", "");
push @argkeys0, "lang";
}
}
# Deal with each language
%urls = qw();
foreach my $lang (@{$$args{"all_linguas"}}) {
if ($$args{"static"}) {
$urls{$lang} = sprintf $path, ln($lang, LN_FILENAME);
} else {
my ($args1, @argkeys1, $charset1);
# Make a copy of the variables
eval Data::Dumper->Dump([$args0], [qw($args1)]);
@argkeys1 = @argkeys0;
$charset1 = ln $lang, LN_CHARSET;
$args1->param("lang", $lang);
@_ = qw();
# We need to specify the character set
$args1->param("charset", $charset1)
if $need_charset;
foreach (@argkeys1) {
foreach my $val ($args1->param($_)) {
push @_, uri_escape(h_encode($_, $charset1))
. "=" . uri_escape(h_encode($val, $charset1))
}
}
$urls{$lang} = $REQUEST_FILE . "?" . join "&", @_;
}
$urls{$lang} = $urls{$lang};
}
# Record it
$$args{"altlang"} = \%urls;
return;
}
return 1;

84
lib/perl5/Selima/Array.pm Normal file
View File

@@ -0,0 +1,84 @@
# Selima Website Content Management System
# Array.pm: The array-related subroutines.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-12
package Selima::Array;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(in_array keys_ml keys_nl);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub in_array($@);
sub keys_ml(\%);
sub keys_nl(\%);
}
use Selima::DataVars qw(:lninfo);
use Selima::GetLang;
# in_array: If something is in an array
sub in_array($@) {
local ($_, %_);
my ($item, @array);
($item, @array) = @_;
%_ = map { $_ => 1 } @array;
return exists $_{$item};
}
# keys_ml: Return a list of multi-lingual keys in a hash
sub keys_ml(\%) {
local ($_, %_);
my ($hash, $lndb);
$hash = $_[0];
$lndb = getlang LN_DATABASE;
@_ = qw();
foreach (keys %$hash) {
# it has a language suffix
push @_, $_ if s/_$lndb$//;
}
return @_;
}
# keys_nl: Return a list of keys without their multi-lingual in a hash
sub keys_nl(\%) {
local ($_, %_);
my ($hash, %mlkeys);
$hash = $_[0];
%mlkeys = map { $_ => 1 } keys_ml %$hash;
%_ = qw();
foreach (keys %$hash) {
# No suffix
if (!/^(.+)_[^_]+$/) {
$_{$_} = 1;
# The prefix is one of the language columns
} elsif (exists $mlkeys{$1}) {
$_{$1} = 1;
# An ordinary prefix
} else {
$_{$_} = 1;
}
}
return keys %_;
}
return 1;

152
lib/perl5/Selima/AuthDig.pm Normal file
View File

@@ -0,0 +1,152 @@
# Selima Website Content Management System
# AuthDig.pm: The mod_perl HTTP digest authentication handler.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-12
package Selima::AuthDig;
use 5.008;
use strict;
use warnings;
use Apache::AuthDigest::API qw();
use CGI qw();
use Fcntl qw(:flock);
use File::Spec::Functions qw(catfile);
use Selima::Checker::LogIn;
use Selima::DataVars qw($AUTHINFO $IS_MP2 :db :input);
use Selima::DBI;
use Selima::HTTP;
use Selima::Init;
use Selima::LogIn;
use Selima::Session;
BEGIN {
if ($IS_MP2) {
require Apache2::Const;
import Apache2::Const qw(OK AUTH_REQUIRED);
} else {
require Apache::Constants;
import Apache::Constants qw(OK AUTH_REQUIRED);
}
}
# Full HTTP Digest Authentication is specified in RFC 2617
# handler: Handle the HTTP digest authentication
sub handler {
local ($_, %_);
my ($r, $rd, $d, $status, $response, $pkg);
my ($form, $checker);
$r = $_[0];
$rd = Apache::AuthDigest::API->new($r);
$d = new Selima::AuthDig::Destroy($rd);
# Retrieve the authentication information
($status, $response) = $rd->get_digest_auth_response;
# No authentication information available
return $status unless $status == OK;
# Initialize the environemnt
$pkg = $r->dir_config("PACKAGE");
initvars($pkg);
# Logged out
if ( defined($_ = $GET->param("logout"))
&& -e ($_ = catfile($Selima::Session::DIR, "logout_$_"))) {
unlink $_;
$rd->note_digest_auth_failure;
return AUTH_REQUIRED;
}
# Connect to the database
$DBH = Selima::DBI->new($DBI_TYPE);
%_ = ("users" => LOCK_EX, "groups" => LOCK_SH, "usermem" => LOCK_SH);
$DBH->lock(%_);
# Check the password digest
$form = new CGI("");
$form->param("id", $rd->user);
$checker = new Selima::Checker::LogIn($form);
$checker->{"rd"} = $rd;
$checker->{"response"} = $response;
$checker->{"login"} = 1;
$_ = $checker->check(qw(id authdig));
# Failed
if (defined $_) {
$rd->note_digest_auth_failure;
return AUTH_REQUIRED;
}
# Let $d->DESTROY release the database lock
# We do not update user information here. Updating user information
# requires initiating the session and set the session cookie SID.
# But cookies cannot be set under HTTP 304 Not Modified. We are
# protecting the whole directory, where there are a lot of static
# contents, including the Magicat home index.html. HTTP 304 occurs
# all the time, from the beginning of Magicat home entrance.
# Failing to set session cookie SID causes new sessions be
# reinitialited for every request. The user visits accounting
# would become non-sense. That accounting is important!
# We update user information at the script executing phrase, where
# we have more control over the output, including the cookies
# and the HTTP 304 status.
$AUTHINFO = $checker->{"row"};
return OK;
}
# Selima::AuthDig::Destroy: Object to remove program data
package Selima::AuthDig::Destroy;
use 5.008;
use strict;
use warnings;
use Selima::DataVars qw($DBH);
# new: Initialize the destroyer
sub new : method {
local ($_, %_);
my ($class, $rd, $self);
($class, $rd) = @_;
$self = bless {}, $class;
$self->{"rd"} = $rd;
return $self;
}
# DESTROY: Release the acquired locks
sub DESTROY : method {
local ($_, %_);
my ($self, $rd, $headers);
$self = $_[0];
$rd = $self->{"rd"};
# Disconnect database handle
if (defined $DBH) {
$DBH->disconnect;
undef $DBH;
}
# Destroy myself
$self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
# I cannot really undefine myself ($_[0]) after all
return;
}
return 1;

168
lib/perl5/Selima/Cache.pm Normal file
View File

@@ -0,0 +1,168 @@
# Selima Website Content Management System
# Cache.pm: The data cache.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-24
package Selima::Cache;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT %EXPORT_TAGS @EXPORT_OK);
BEGIN {
@EXPORT = qw();
%EXPORT_TAGS = (
account => [qw(%Account_acctsubj_title %Account_acctsubj_code
%Account_acctsubj_sn %Account_accttrx_id %Account_accttrx_maxord)],
callform=> [qw($CallForm_form_this %Callform_retrieve_form)],
chkfunc => [qw(%ChkFunc_check_script %ChkFunc_is_url_reachable)],
chkpriv => [qw(%ChkPriv_is_admin %ChkPriv_is_su
%ChkPriv_user_parent_groups)],
copyyear=> [qw($CopyYear_copyyear)],
country => [qw(%Country_ctname)],
echoform=> [qw(%EchoForm_opt_list)],
formfunc=> [qw($FormFunc_get_or_post $FormFunc_curform $FormFunc_isform
$FormFunc_formtype)],
getlang => [qw($GetLang_lang $GetLang_charset @GetLang_all_charsets)],
guest => [qw(%Guest_is_guest)],
links => [qw(%Links_linkcat_title %Links_link_title %Links_link_url
%Links_link_tree %Links_link_tree_full)],
listfunc=> [qw($ListFunc_listtype)],
login => [qw($LogIn_use_users)],
https => [qw($HTTPS_https_process $HTTPS_fqdn)],
mail => [qw(%Mail_MSGIDS)],
picture => [qw(%Picture_pic_deposit)],
remohost=> [qw($RemoHost_remote)],
scptpriv=> [qw(%ScptPriv_is_script_permitted %ScptPriv_is_admin_script)],
setl10n => [qw(%SetL10N_checked)],
username=> [qw(%UserName_username %UserName_userid %UserName_groupid
%UserName_groupdsc %UserName_groupsn $UserName_su_group_sn)],
userpref=> [qw(%UserPref_userpref)],
);
@EXPORT_OK = qw();
my %seen;
%seen = qw();
foreach my $tag (keys %EXPORT_TAGS) {
push @EXPORT_OK, grep !$seen{$_}++, @{$EXPORT_TAGS{$tag}};
}
# Prototype declaration
sub clear();
}
use vars qw(%Account_acctsubj_title %Account_acctsubj_code);
use vars qw(%Account_acctsubj_sn %Account_accttrx_id);
use vars qw(%Account_accttrx_maxord);
use vars qw($CallForm_form_this %Callform_retrieve_form);
use vars qw(%ChkFunc_check_script %ChkFunc_is_url_reachable);
use vars qw(%ChkPriv_is_admin %ChkPriv_is_su);
use vars qw(%ChkPriv_user_parent_groups);
use vars qw($CopyYear_copyyear);
use vars qw(%Country_ctname);
use vars qw(%EchoForm_opt_list);
use vars qw($FormFunc_get_or_post $FormFunc_curform $FormFunc_isform);
use vars qw($FormFunc_formtype);
use vars qw($GetLang_lang $GetLang_charset @GetLang_all_charsets);
use vars qw(%Guest_is_guest);
use vars qw(%Links_linkcat_title %Links_link_title %Links_link_url);
use vars qw(%Links_link_tree %Links_link_tree_full);
use vars qw($ListFunc_listtype);
use vars qw($LogIn_use_users);
use vars qw($HTTPS_https_process $HTTPS_fqdn);
use vars qw(%Mail_MSGIDS);
use vars qw(%Picture_pic_deposit);
use vars qw($RemoHost_remote);
use vars qw(%SetL10N_checked);
use vars qw(%ScptPriv_is_script_permitted %ScptPriv_is_admin_script);
use vars qw(%UserName_username %UserName_userid %UserName_groupid);
use vars qw(%UserName_groupdsc %UserName_groupsn $UserName_su_group_sn);
use vars qw(%UserPref_userpref);
# clear: Clear the cache (for mod_perl)
sub clear() {
local ($_, %_);
%Account_acctsubj_title = qw();
%Account_acctsubj_code = qw();
%Account_acctsubj_sn = qw();
%Account_accttrx_id = qw();
%Account_accttrx_maxord = qw();
undef $CallForm_form_this;
%Callform_retrieve_form = qw();
%ChkFunc_check_script = qw();
%ChkFunc_is_url_reachable = qw();
%ChkPriv_is_admin = qw();
%ChkPriv_is_su = qw();
%ChkPriv_user_parent_groups = qw();
undef $CopyYear_copyyear;
%Country_ctname = qw();
%EchoForm_opt_list = qw();
undef $FormFunc_get_or_post;
undef $FormFunc_curform;
undef $FormFunc_isform;
undef $FormFunc_formtype;
undef $GetLang_lang;
undef $GetLang_charset;
@GetLang_all_charsets = qw();
%Guest_is_guest = qw();
%Links_linkcat_title = qw();
%Links_link_title = qw();
%Links_link_url = qw();
%Links_link_tree = qw();
%Links_link_tree_full = qw();
undef $ListFunc_listtype;
undef $LogIn_use_users;
$HTTPS_https_process = 0;
undef $HTTPS_fqdn;
%Mail_MSGIDS = qw();
%Picture_pic_deposit = qw();
undef $RemoHost_remote;
%ScptPriv_is_script_permitted = qw();
%ScptPriv_is_admin_script = qw();
%SetL10N_checked = qw();
%UserName_username = qw();
%UserName_userid = qw();
%UserName_groupid = qw();
%UserName_groupdsc = qw();
%UserName_groupsn = qw();
undef $UserName_su_group_sn;
%UserPref_userpref = qw();
return;
}
return 1;

View File

@@ -0,0 +1,465 @@
# Selima Website Content Management System
# CallForm.pm: The subroutines to transfer between a form and its subform.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-24
package Selima::CallForm;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(call_form error_redirect success_redirect);
push @EXPORT, qw(suspend_form retrieve_form suspend_status retrieve_status);
push @EXPORT, qw(is_called_form form_this);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub call_form($;$$);
sub proc_status_redirect($);
sub error_redirect($);
sub success_redirect($);
sub suspend_form(;$);
sub retrieve_form(;$);
sub suspend_status($);
sub retrieve_status(;$);
sub is_called_form();
sub new_formid();
sub new_statid();
sub form_this();
}
use CGI qw();
use Data::Dumper qw();
use File::Spec::Functions qw(catfile);
use Fcntl qw(:flock);
use URI::Escape qw(uri_escape);
use Selima::AddGet;
use Selima::Cache qw(:callform);
use Selima::DataVars qw($SESSION :env :forms :requri :scptconf);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::Session;
use Selima::XFileIO;
use vars qw(%SAVESTATS);
# Settings
# The length of the ID
use constant ID_LEN => 9;
# call_form: Suspend the current form and call another selection form
sub call_form($;$$) {
local ($_, %_);
my ($form, @args, $import_func, $FORM, $formid);
($form, $_, $import_func) = @_;
@args = defined $_? @$_: qw();
# Only work for defined forms
http_500 "Calling undefined form \"$form\".\n"
if !exists $SCRIPTS{$form};
# Record the selection import function
if (defined $import_func) {
# Obtain the form
$FORM = get_or_post;
$FORM->param("import_func", $import_func);
}
# Suspend the current form
$formid = suspend_form $FORM;
# Add the caller
$_ = $REQUEST_PATH;
$_ =~ s/^$ROOT_DIFF//;
push @args, "caller=" . uri_escape($_);
# Add the caller form ID
push @args, "cformid=" . uri_escape($formid);
# Redirect
http_303 $SCRIPTS{$form} . "?" . join("&", @args);
# No need to return
exit;
}
# proc_status_redirect: Save the form status, suspend and redirect the form
sub proc_status_redirect($) {
local ($_, %_);
my ($status, $url, $FORM, $formid, $statid, $method);
$status = $_[0];
# Obtain the form
$FORM = get_or_post;
# No more form
if ( defined $status
&& exists $$status{"isform"}
&& !$$status{"isform"}) {
# Find the referring script
# Specified
if (exists $$status{"nextstep"}) {
$url = $$status{"nextstep"};
} elsif (defined $FORM->param("referer2")) {
$url = $FORM->param("referer2");
# Back to the user home on the calling site
} elsif (defined $FORM->param("hostport")) {
# To be done
$url = $FORM->param("hostport") . "/magicat/";
# Default to the current URL
} else {
$url = $REQUEST_FILE;
}
# Do not affect the previous form
delete $$status{"isform"};
# Default to this same script
} else {
$url = form_this;
# Suspend the current form
$formid = suspend_form $FORM;
$url = add_get_arg $url, "formid", $formid;
}
# Add the status
if (defined $status) {
$statid = suspend_status $status;
$url = add_get_arg $url, "statid", $statid;
}
# Redirect
$method = $IS_MODPERL? ($IS_MP2?
Apache2::RequestUtil->request->method: Apache->request->method):
$ENV{"REQUEST_METHOD"};
if ($method eq "POST") {
http_303 $url;
} else {
http_307 $url;
}
# No need to return
exit;
}
# error_redirect: Shortcut to redirect the error form
sub error_redirect($) {
local ($_, %_);
my $status;
$status = $_[0];
$$status{"status"} = "error" if defined $status;
proc_status_redirect $status;
# No need to return
exit;
}
# success_redirect: Shortcut to redirect the success form
sub success_redirect($) {
local ($_, %_);
my $status;
$status = $_[0];
$$status{"status"} = "success" if defined $status;
proc_status_redirect $status;
# No need to return
exit;
}
# suspend_form: Suspend the current form
sub suspend_form(;$) {
local ($_, %_);
my ($FORM, $formid, $dumper, $origumask);
$FORM = $_[0];
# Obtain the form
$FORM = get_or_post if !defined $FORM;
# Using session is better
if (defined $SESSION) {
# Initialize the form deposit
$$SESSION{"saveforms"} = {}
if !exists $$SESSION{"saveforms"};
# Do not use existing form ID
$formid = new_formid;
# Save the current form
${$$SESSION{"saveforms"}}{$formid} = $FORM;
# Save the form ID
${$$SESSION{"saveforms"}}{$formid}->param("formid", $formid);
${$$SESSION{"saveforms"}}{$formid}->param("ownerscript", $REQUEST_PATH);
$SESSION->flush;
# Not using session
} else {
# Do not use existing form ID
$formid = new_formid;
# Dump the form
$dumper = new Data::Dumper([$FORM], [qw($_)]);
$dumper->Indent(1);
$dumper->Sortkeys(1);
$_ = $dumper->Dump;
# Write to the file
$origumask = umask 0077;
xfwrite(catfile($Selima::Session::DIR, "saveform_$formid"), $_);
umask $origumask;
}
return $formid;
}
# retrieve_form: Retrieve a previously suspended form
# Return empty array instead of empty, to be merged directly
sub retrieve_form(;$) {
local ($_, %_);
my ($formid, $CUR_FORM, $file, $cache);
$formid = $_[0];
$cache = \%Callform_retrieve_form;
# Return the cache
if (!defined $formid) {
# $cache[0] is the default (previous form)
return $$cache{0} if exists $$cache{0};
} else {
return $$cache{$formid} if exists $$cache{$formid};
}
# Using session is better
if (defined $SESSION) {
# Initialize the form deposit
if (!exists $$SESSION{"saveforms"}) {
$$SESSION{"saveforms"} = {};
if (!defined $formid) {
return ($$cache{0} = new CGI(""));
} else {
return ($$cache{$formid} = new CGI(""));
}
}
# Obtain the previous suspended form if form not specified
if (!defined $formid) {
# Obtain the form
$CUR_FORM = get_or_post;
# Return empty if not set
return ($$cache{0} = new CGI(""))
if !defined $CUR_FORM->param("formid");
$formid = $CUR_FORM->param("formid");
# There is no such previously suspended form
if (!exists ${$$SESSION{"saveforms"}}{$formid}) {
$$cache{$formid} = new CGI("");
$$cache{0} = $$cache{$formid};
return $$cache{0};
}
$$cache{$formid} = ${$$SESSION{"saveforms"}}{$formid};
$$cache{0} = $$cache{$formid};
} else {
# There is no such previously suspended form
return ($$cache{$formid} = new CGI(""))
if !exists ${$$SESSION{"saveforms"}}{$formid};
$$cache{$formid} = ${$$SESSION{"saveforms"}}{$formid};
}
# Not using session
} else {
# Obtain the previous suspended form if form not specified
if (!defined $formid) {
# Obtain the form
$CUR_FORM = get_or_post;
# Return empty if not set
return ($$cache{0} = new CGI(""))
if !defined $CUR_FORM->param("formid");
$formid = $CUR_FORM->param("formid");
# Compose the save form file name
$file = catfile($Selima::Session::DIR, "saveform_$formid");
# There is no such previously suspended form
if (!-f $file || !-r $file || !-s $file) {
$$cache{$formid} = new CGI("");
$$cache{0} = $$cache{$formid};
return $$cache{0};
}
$$cache{$formid} = eval xfread($file);
$$cache{0} = $$cache{$formid};
} else {
# Compose the save form file name
$file = catfile($Selima::Session::DIR, "saveform_$formid");
# There is no such previously suspended form
return ($$cache{$formid} = new CGI(""))
if !-f $file || !-r $file || !-s $file;
$$cache{$formid} = eval xfread($file);
}
}
# Import the selection if needed
&$_($$cache{$formid})
if defined($_ = $$cache{$formid}->param("import_func"))
&& defined($_ = $MAIN->can($_));
# Return the form
return $$cache{$formid};
}
# suspend_status: Suspend the current status
sub suspend_status($) {
local ($_, %_);
my ($status, $statid, $dumper, $origumask);
$status = $_[0];
# Using session is better
if (defined $SESSION) {
# Initialize the status deposit
$$SESSION{"savestats"} = {}
if !exists $$SESSION{"savestats"};
# Do not use existing status ID
$statid = new_statid;
# Save the current status
${$$SESSION{"savestats"}}{$statid} = $status;
# Save the status ID
${${$$SESSION{"savestats"}}{$statid}}{"statid"} = $statid;
${${$$SESSION{"savestats"}}{$statid}}{"ownerscript"} = $REQUEST_PATH;
$SESSION->flush;
# Not using session
} else {
# Do not use existing status ID
$statid = new_statid;
# Dump the status
$dumper = new Data::Dumper([$status], [qw($_)]);
$dumper->Indent(1);
$dumper->Sortkeys(1);
$_ = $dumper->Dump;
# Write to the file
$origumask = umask 0077;
xfwrite(catfile($Selima::Session::DIR, "savestat_$statid"), $_);
umask $origumask;
}
return $statid;
}
# retrieve_status: Retrieve a previously suspended status
# Return empty array instead of empty, to be merged directly
sub retrieve_status(;$) {
local ($_, %_);
my ($statid, $CUR_FORM, $file);
$statid = $_[0];
if (!defined $statid) {
# Obtain the form
$CUR_FORM = get_or_post;
# Return nothing if there is no status ID to retrieve
return if !defined $CUR_FORM->param("statid");
$statid = $CUR_FORM->param("statid");
}
# Using session is better
if (defined $SESSION) {
return if !exists $$SESSION{"savestats"};
return if !exists ${$$SESSION{"savestats"}}{$statid};
return ${$$SESSION{"savestats"}}{$statid};
# Not using session
} else {
# Compose the save status file name
$file = catfile($Selima::Session::DIR, "savestat_$statid");
# Check if this status is available
if (!-f $file || !-r $file || !-s $file) {
# Remove expired cache
delete $SAVESTATS{$file} if exists $SAVESTATS{$file};
return;
}
# Parse the status
$SAVESTATS{$statid} = eval xfread($file)
if !exists $SAVESTATS{$statid};
# Return the status
return $SAVESTATS{$statid};
}
}
# is_called_form: If this is a called form
sub is_called_form() {
local ($_, %_);
$_ = curform;
return (defined $_->param("caller") && defined $_->param("cformid"));
}
# new_formid: Generate a new random form ID number
sub new_formid() {
local ($_, %_);
my ($min, $max, $int, $formid);
$min = 10 ** (ID_LEN - 1); # 100000000
$max = (10 ** ID_LEN) - 1; # 999999999
$int = $max - $min + 1;
# Using session is better
if (defined $SESSION) {
do {
# Generate a random serial number
$formid = $min + int rand $int;
} until !exists ${$$SESSION{"saveforms"}}{$formid};
# Not using session
} else {
do {
# Generate a random serial number
$formid = $min + int rand $int;
} until !-e catfile($Selima::Session::DIR, "saveform_$formid");
}
return $formid;
}
# new_statid: Generate a new random status ID number
sub new_statid() {
local ($_, %_);
my ($min, $max, $int, $statid);
$min = 10 ** (ID_LEN - 1); # 100000000
$max = (10 ** ID_LEN) - 1; # 999999999
$int = $max - $min + 1;
# Using session is better
if (defined $SESSION) {
do {
# Generate a random serial number
$statid = $min + int rand $int;
} until !exists ${$$SESSION{"savestats"}}{$statid};
# Not using session
} else {
do {
# Generate a random serial number
$statid = $min + int rand $int;
} until !-e catfile($Selima::Session::DIR, "savestat_$statid");
}
return $statid;
}
# form_this: Find the processing script
sub form_this() {
local ($_, %_);
return $CallForm_form_this if defined $CallForm_form_this;
$_ = get_or_post;
return ($CallForm_form_this = $_)
if defined($_ = $_->param("referer"));
return ($CallForm_form_this = $REQUEST_FILE);
}
return 1;

800
lib/perl5/Selima/Checker.pm Normal file
View File

@@ -0,0 +1,800 @@
# Selima Website Content Management System
# Checker.pm: The base form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-25
package Selima::Checker;
use 5.008;
use strict;
use warnings;
use Encode::HanConvert qw(trad_to_simp simp_to_trad);
use Regexp::Common::URI::RFC2396 qw();
use URI::Escape qw(uri_escape);
use Selima::AddGet;
use Selima::Array;
use Selima::CallForm;
use Selima::ChkFunc;
use Selima::ChkWrite;
use Selima::DataVars qw($DBH FORM_CAPTCHA :dataman :forms :l10n
:lninfo :requri);
use Selima::FormFunc;
use Selima::GetLang;
use Selima::HTTP;
use Selima::LnInfo;
use Selima::Logging;
use Selima::Picture;
use Selima::ShortCut;
# Load these classes
use Selima::Checker::User;
use Selima::Checker::Group;
use Selima::Checker::UserMem;
use Selima::Checker::GroupMem;
use Selima::Checker::UserPref;
use Selima::Checker::ScptPriv;
use Selima::Checker::LogIn;
use Selima::Checker::ListPref;
use Selima::Checker::Guestbook;
use Selima::Checker::Guestbook::Public;
use Selima::Checker::Page;
use Selima::Checker::LinkCat;
use Selima::Checker::Link;
use Selima::Checker::LinkCatz;
use Selima::Checker::Rebuild;
use Selima::Checker::MailTo;
use Selima::Checker::AcctSubj;
use Selima::Checker::AcctTrx;
use Selima::Checker::AcctRec;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $form, $table, $checker);
($class, $form, $table) = @_;
$checker = bless {}, $class;
$checker->{"form"} = $form;
if (defined $table) {
$checker->{"table"} = $table;
$checker->{"maxlens"} = { $DBH->col_lens($table) };
}
$checker->{"minlens"} = {};
${$checker->{"minlens"}}{"id"} = 3;
$checker->{"iscur"} = (keys %CURRENT > 0)? 1: 0;
$checker->{"sn"} = $form->param("sn")
if $checker->{"iscur"} && defined $form->param("sn");
return $checker;
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, @cols, $error);
($self, @cols) = @_;
# Check the list itself first
@_ = qw();
foreach my $col (@cols) {
http_500 "Called an undefined check \"$col\""
if !defined($_ = $self->can("_check_$col"));
push @_, $_;
}
# Run each checker
foreach (@_) {
$error = &$_($self);
return $error if defined $error;
}
return;
}
# redir: Redirect to another form
sub redir : method {
local ($_, %_);
my ($self, @cols);
($self, @cols) = @_;
# Check the list itself first
@_ = qw();
foreach my $col (@cols) {
http_500 "Called an undefined redirection \"$col\""
if !defined($_ = $self->can("_redir_$col"));
push @_, $_;
}
# Check each redirection
&$_($self) foreach @_;
return;
}
#
# Private column checkers. Do not call them directly.
# Add or override the column checkers when needed.
# Method names must be in the following format:
# sub _check_{column} : method { ... }
# Columns started with underlines are reserved for internal use, as usual.
#
# _check_usr: The default user checker
sub _check_usr : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("usr");
return $error if defined $error;
# Regularize it
$self->_trim("usr");
# Check if it is filled
return {"msg"=>N_("Please select a user.")}
if $form->param("usr") eq "";
# Check if this user exists
return {"msg"=>N_("This user does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("usr")}[0], "users";
# OK
return;
}
# _check_grp: The default group checker
sub _check_grp : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("grp");
return $error if defined $error;
# Regularize it
$self->_trim("grp");
# Check if it is filled
return {"msg"=>N_("Please select a group.")}
if $form->param("grp") eq "";
# Check if the group exists
return {"msg"=>N_("This group does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("grp")}[0], "groups";
# OK
return;
}
# _check_script: The default script checker
sub _check_script : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("script");
return $error if defined $error;
# Regularize it
$self->_trim("script");
# Check if it is filled
return {"msg"=>N_("Please fill in the script.")}
if $form->param("script") eq "";
# Check the length
return {"msg"=>N_("This script is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"script"}]}
if length $form->param("script") > ${$self->{"maxlens"}}{"script"};
# Check if this script exists
return {"msg"=>N_("This script is not a valid script. Please specify another one.")}
if !check_script($form->param("script"));
# OK
return;
}
# _check_author: The default author checker
sub _check_author : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("author");
return $error if defined $error;
# Regularize it
$self->_trim("author");
# Check if it is filled
return {"msg"=>N_("Please fill in the author.")}
if $form->param("author") eq "";
# Check the length
return {"msg"=>N_("This author is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"author"}]}
if length $form->param("author") > ${$self->{"maxlens"}}{"author"};
# OK
return;
}
# _check_body: The default content body checker
sub _check_body : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("body");
return $error if defined $error;
# Regularize it
$self->_trimtext("body");
# Check if it is filled
$form->param("body", "")
if $form->param("body") eq C_("Fill in the content here.");
return {"msg"=>N_("Please fill in the content.")}
if $form->param("body") eq "";
# Check the length
return {"msg"=>N_("This content is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"body"}]}
if length $form->param("body") > ${$self->{"maxlens"}}{"body"};
# OK
return;
}
# _check_date: Check the date
sub _check_date : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("date");
return $error if defined $error;
# Regularize it
$self->_trim("date");
# Check if it is filled
return {"msg"=>N_("Please fill in a date.")}
if $form->param("date") eq "";
# Check the length
return {"msg"=>N_("Please fill in a valid date in YYYY-MM-DD format.")}
if length $form->param("date") > ${$self->{"maxlens"}}{"date"};
# Check the date format
return {"msg"=>N_("Please fill in a valid date in YYYY-MM-DD format.")}
if $form->param("date") !~ /^(\d{4})-(\d{2})-(\d{2})$/;
return {"msg"=>N_("Please fill in a valid date in YYYY-MM-DD format.")}
if !check_date $1, $2, $3;
# OK
return;
}
# _check_dsc: The default description checker
sub _check_dsc : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("dsc");
return $error if defined $error;
# Regularize it
$self->_trimtext("dsc");
# Check if it is filled
$form->param("dsc", "")
if $form->param("dsc") eq C_("Fill in the description here.");
return {"msg"=>N_("Please fill in the description.")}
if $form->param("dsc") eq "";
# Check the length
return {"msg"=>N_("This description is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"dsc"}]}
if length $form->param("dsc") > ${$self->{"maxlens"}}{"dsc"};
# OK
return;
}
# _check_id: The default ID. checker
sub _check_id : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("id");
return $error if defined $error;
# Regularize it
$self->_trim("id");
# Check if it is filled
return {"msg"=>N_("Please fill in the ID.")}
if $form->param("id") eq "";
# Check the length
return {"msg"=>N_("This ID. is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"id"}]}
if length $form->param("id") > ${$self->{"maxlens"}}{"id"};
return {"msg"=>N_("This ID. is too short. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"id"}]}
if length $form->param("id") < ${$self->{"minlens"}}{"id"};
# Check if the characters used are valid
return {"msg"=>N_("Only lower-case English letters, numbers and underscores are allowed for the ID.")}
unless $form->param("id") =~ /^[a-z][a-z0-9_]*$/;
# OK
return;
}
# _check_kw: The default keyword list checker
sub _check_kw : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("kw");
return $error if defined $error;
# Regularize it
$self->_trim("kw");
# Check if it is filled
return {"msg"=>N_("Please fill in the keywords.")}
if $form->param("kw") eq "";
# Check the length
return {"msg"=>N_("This keyword list is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"kw"}]}
if length $form->param("kw") > ${$self->{"maxlens"}}{"kw"};
# OK
return;
}
# _check_message: The default message checker
sub _check_message : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("message");
return $error if defined $error;
# Regularize it
$self->_trimtext("message");
# Check if it is filled
$form->param("message", "")
if $form->param("message") eq C_("Fill in the message here.");
return {"msg"=>N_("Please fill in the message.")}
if $form->param("message") eq "";
# Check the length
return {"msg"=>N_("This message is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"message"}]}
if length $form->param("message") > ${$self->{"maxlens"}}{"message"};
# OK
return;
}
# _check_ord: The default order checker
sub _check_ord : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("ord");
return $error if defined $error;
# Regularize it
$self->_trim("ord");
# Check if it is filled
return {"msg"=>N_("Please fill in the order.")}
if $form->param("ord") eq "";
# Check the length
return {"msg"=>N_("This order is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"ord"}]}
if length $form->param("ord") > ${$self->{"maxlens"}}{"ord"};
# Check if it is a valid positive integer
return {"msg"=>N_("Please fill in a positive integer order.")}
unless $form->param("ord") =~ /^\d+$/;
# Set to an integer
$_ = $form->param("ord");
$_ += 0;
$form->param("ord", $_);
# OK
return;
}
# _check_path: The default page path checker
sub _check_path : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("path");
return $error if defined $error;
# Regularize it
$self->_trim("path");
# Remove the trailing excess "index.html"
$_ = $form->param("path");
s/\/index\.html?$/\//;
$form->param("path", $_);
# Check if it is filled
return {"msg"=>N_("Please fill in the page path.")}
if $form->param("path") eq "";
# Check the length
return {"msg"=>N_("This page path is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"path"}]}
if length $form->param("path") > ${$self->{"maxlens"}}{"path"};
# Check if this item is duplicated
@_ = qw();
push @_, "path=" . $DBH->quote($form->param("path"));
push @_, "sn!=" . $self->{"sn"} if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This page already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
# Check if the path is absolute
return {"msg"=>N_("Please fill in an absolute page path.")}
if $form->param("path") !~ /^\//;
# Check if the path is legal
return {"msg"=>N_("Please fill in a valid page path.")}
if $form->param("path") !~ /^\/$Regexp::Common::URI::RFC2396::path_segments$/;
# Check if it is the cover home page
return {"msg"=>N_("You cannot overwrite the cover home page.")}
if $form->param("path") eq "";
# Check if it is *.html
return {"msg"=>N_("You can only fill in an HTML page path (*.html).")}
if $form->param("path") !~ /(?:\/|\.html)$/;
# Check if we are permitted to write files there
if (@ALL_LINGUAS > 1) {
$_ = $DOC_ROOT . $form->param("path");
$_ =~ s/\/$/\/index.html/;
$_ .= ".%s.xhtml";
foreach my $ln (@ALL_LINGUAS) {
$error = check_writable sprintf $_, ln $ln, LN_FILENAME;
return $error if defined $error;
}
} else {
$error = check_writable $DOC_ROOT . $form->param("path") . ".xhtml";
return $error if defined $error;
}
# OK
return;
}
# _check_pic: The default picture checker
sub _check_pic : method {
local ($_, %_);
my ($self, $form, $error, $PICS);
$self = $_[0];
$form = $self->{"form"};
# Skip if there is no picture to check
return if $self->_missing("pic");
# Check if this picture exists
return {"msg"=>N_("This picture does not exist anymore. Please upload another one.")}
if !pic_exists ${$form->param_fetch("pic")}[0];
# Check the length
$PICS = pic_deposit;
return {"msg"=>N_("This picture is too large. Please upload another one. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"pic"}]}
if length ${$$PICS{$form->param("pic")}}{"content"} > ${$self->{"maxlens"}}{"pic"};
# OK
return;
}
# _check_piccap: default picture caption checker
sub _check_piccap : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Skip if there is no picture now
if ($self->_missing("pic")) {
$form->delete("piccap");
return;
}
# Check if it exists
$error = $self->_missing("piccap");
return $error if defined $error;
# Regularize it
$self->_trim("piccap");
# Check if it is filled
return {"msg"=>N_("Please fill in the picture caption.")}
if $form->param("piccap") eq "";
# Check the length
return {"msg"=>N_("This picture caption is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"piccap"}]}
if length $form->param("piccap") > ${$self->{"maxlens"}}{"piccap"};
# OK
return;
}
# _check_picpos: default picture position checker
sub _check_picpos : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Skip if there is no picture now
if ($self->_missing("pic")) {
$form->delete("picpos");
return;
}
# Check if it exists
$error = $self->_missing("picpos");
return $error if defined $error;
# Regularize it
$self->_trim("picpos");
# Check if the picture position is legal
return {"msg"=>N_("This picture position is invalid. Please choose a proper picture position.")}
if !in_array($form->param("picpos"), @PIC_VALID_POS);
# OK
return;
}
# _check_title: The default title checker
sub _check_title : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("title");
return $error if defined $error;
# Regularize it
$self->_trim("title");
# Check if it is filled
return {"msg"=>N_("Please fill in the title.")}
if $form->param("title") eq "";
# Check the length
return {"msg"=>N_("This title is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"title"}]}
if length $form->param("title") > ${$self->{"maxlens"}}{"title"};
# OK
return;
}
# _check_title_en: The default English title checker
sub _check_title_en : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("title_en");
return $error if defined $error;
# Regularize it
$self->_trim("title_en");
# Check if it is filled
return {"msg"=>N_("Please fill in the English title.")}
if $form->param("title_en") eq "";
# Check the length
return {"msg"=>N_("This English title is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"title_en"}]}
if length $form->param("title_en") > ${$self->{"maxlens"}}{"title_en"};
# OK
return;
}
#
# Spam Checkers:
# There are a series of checks for spam here. _check_spam() called each of them.
# Moved to Selima::Init. It starts at an early phrase before database initialization.
#
# _check_spam: Check the spam
sub _check_spam : method {
local ($_, %_);
# Check the CAPTCHA
$_[0]->_check_captcha;
# Check the local content filter
$_[0]->_checkspam_local if $_[0]->can("_checkspam_local");
# OK
return;
}
# _check_captcha: The default CAPTCHA checker
sub _check_captcha : method {
local ($_, %_);
my ($self, $form, $col, $error);
$self = $_[0];
$form = $self->{"form"};
$col = FORM_CAPTCHA;
# Check if it exists
$error = $self->_missing($col);
return $error if defined $error;
$self->_block_spam("_check_captcha: captcha column \"$col\" should be empty but got \""
. $form->param($col) . "\".")
if $form->param($col) ne "";
# OK
return;
}
# _block_spam: Block the spam message
sub _block_spam : method {
local ($_, %_);
my ($self, $msg);
($self, $msg) = @_;
spamlog $msg;
# Disconnect now. Leave resources for meaningful requests
$DBH->rollback;
$DBH->disconnect;
undef $DBH;
# Delay the spammer
sleep 300;
http_403(0);
# No return
}
#
# Private form redirectors. Do not call them directly.
# Add redirector definitions here.
# Method names must be in the following format:
# sub _redir_{column} : method { ... }
# Columns started with underlines are reserved for internal use, as usual.
#
# _redir_del: Suspend and move to the deletion form
sub _redir_del : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("del");
# Skip if S/N does not exist
return if $self->_missing("sn");
@_ = qw();
push @_, "form=del";
push @_, "sn=" . uri_escape($self->{"form"}->param("sn"));
call_form FORM_THIS, [@_];
}
# _redir_zhsync: Synchronize Chinese columns
sub _redir_zhsync : method {
local ($_, %_);
my ($self, $form, $lndb);
$self = $_[0];
$form = $self->{"form"};
# Skip if not requested
return if $self->_missing("zhsync");
%_ = map { $_ => 1 } @ALL_LINGUAS;
$_ = getlang;
# We are at a subordinary language
if ($_ ne $DEFAULT_LANG) {
# We are in Simplified Chinese and there is Traditional Chinese
if ($_ eq "zh-cn" && exists $_{"zh-tw"}) {
$lndb = ln "zh-tw", LN_DATABASE;
# Convert the form
$form->param($_, trad_to_simp($CURRENT{$_ . "_$lndb"}))
foreach grep s/_$lndb$//, keys %CURRENT;
# We are in Traditional Chinese and there is Simplified Chinese
} elsif ($_ eq "zh-tw" && exists $_{"zh-cn"}) {
$lndb = ln "zh-cn", LN_DATABASE;
# Convert the form
$form->param($_, simp_to_trad($CURRENT{$_ . "_$lndb"}))
foreach grep s/_$lndb$//, keys %CURRENT;
}
}
# Show the form again
success_redirect undef;
}
# _redir_cancel: Cancel the form and return to the originator
sub _redir_cancel : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("cancel");
# A calling form -- return to the caller
if (!$self->_missing("caller", "cformid")) {
$_ = $self->{"form"}->param("caller");
$_ = $self->{"form"}->param("hostport") . $_
if !$self->_missing("hostport");
$_ = add_get_arg($_, "formid", $self->{"form"}->param("cformid"));
if ($ENV{"REQUEST_METHOD"} eq "POST") {
http_303 $_;
} else {
http_307 $_;
}
}
# Referer2 specified -- return to referer2
if (!$self->_missing("referer2")) {
$_ = $self->{"form"}->param("referer2");
# Return to the hostport
} elsif (!$self->_missing("hostport")) {
$_ = $self->{"form"}->param("hostport") . "/magicat/";
} else {
$_ = "/magicat/";
}
if ($ENV{"REQUEST_METHOD"} eq "POST") {
http_303 $_;
} else {
http_307 $_;
}
}
# _redir_selgrp: Suspend and move to the group selection form
sub _redir_selgrp : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if !defined $self->{"form"}->param("selgrp");
call_form FORM_GROUPS, undef, "import_selgrp";
}
# _redir_delgrp: Remove the group
sub _redir_delgrp : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if !defined $self->{"form"}->param("delgrp");
$self->{"form"}->delete("grp");
success_redirect undef;
}
# _redir_selparent: Suspend and move to the parent selection form
sub _redir_selparent : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selparent");
call_form FORM_THIS, undef, "import_selparent";
}
# _redir_delparent: Remove the parent
sub _redir_delparent : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("delparent");
$self->{"form"}->delete("parent");
success_redirect undef;
}
#
# Private utility methods. Do not override them.
#
# _missing: Check if certain columns are submitted
sub _missing : method {
local ($_, %_);
my ($self, @cols);
($self, @cols) = @_;
%_ = map { $_ => 1 } $self->{"form"}->param;
foreach (@cols) {
return {"msg"=>N_("The following field was not received: \"[_1]\"."),
"margs"=>[$_],
"isform"=>0}
if !exists $_{$_};
}
# OK
return;
}
# _trim: Trim spaces from both sides of a field
sub _trim : method {
local ($_, %_);
my ($self, @cols);
($self, @cols) = @_;
foreach my $col (@cols) {
s/^\s*(.*?)\s*$/$1/s foreach @{$self->{"form"}->param_fetch($col)}
}
return;
}
# _trimtext: Trim spaces and blank lines from both sides of a text
sub _trimtext : method {
local ($_, %_);
my ($self, @cols);
($self, @cols) = @_;
foreach my $col (@cols) {
foreach (@{$self->{"form"}->param_fetch($col)}) {
# Trim blank lines
s/^(?:\s*\n)?(.*?)\s*$/$1/s;
# Trim the trailing spaces of each line
s/[^\S\n]+\n/\n/g;
}
}
return;
}
return 1;

View File

@@ -0,0 +1,210 @@
# Selima Website Content Management System
# AcctRec.pm: The accounting record form checker.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-22
package Selima::Checker::AcctRec;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::CallForm;
use Selima::ChkFunc;
use Selima::ShortCut;
use Selima::DataVars qw($DBH :forms);
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "acctrecs" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
${$self->{"maxlens"}}{"amount"} = 9;
return $self;
}
# _check_trx: Check the transaction
sub _check_trx : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("trx");
return $error if defined $error;
# Regularize it
$self->_trim("trx");
# Check if it is filled
return {"msg"=>N_("Please select a accounting transaction.")}
if $form->param("trx") eq "";
# Check if the transaction exists
return {"msg"=>N_("This accounting transaction does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("trx")}[0], "accttrx";
# OK
return;
}
# _check_type: Check the type
sub _check_type : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("type");
return $error if defined $error;
# Regularize it
$self->_trim("type");
# Check the option value
return {"msg"=>N_("This option is invalid. Please select a proper type.")}
unless $form->param("type") =~ /^(?:debit|credit)$/;
# OK
return;
}
# _check_ord: Check the order
# Use the default order checker
# _check_subj: Check the subject
sub _check_subj : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("subj");
return $error if defined $error;
# Regularize it
$self->_trim("subj");
# Check if it is filled
return {"msg"=>N_("Please select a accounting subject.")}
if $form->param("subj") eq "";
# Check if the subject exists
return {"msg"=>N_("This accounting subject does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("subj")}[0], "acctsubj";
# Check if this is the last level subject
$sql = "SELECT * FROM acctsubj"
. " WHERE parent=" . $form->param("subj") . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("Only a last-level accounting subject is allowed for an accounting subject.")}
if $sth->rows > 0;
# OK
return;
}
# _check_summary: Check the summary
sub _check_summary : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("summary");
return $error if defined $error;
# Regularize it
$self->_trim("summary");
# Skip if it is not filled
return if $form->param("summary") eq "";
# Check the length
return {"msg"=>N_("This summary is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"summary"}]}
if length $form->param("summary") > ${$self->{"maxlens"}}{"summary"};
# OK
return;
}
# _check_amount: Check the amount
sub _check_amount : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("amount");
return $error if defined $error;
# Regularize it
$self->_trim("amount");
$_ = $form->param("amount");
s/NT\$ ?//;
s/,//g;
s/\.0+$//;
$form->param("amount", $_);
# Check if it is filled
return {"msg"=>N_("Please fill in the amount.")}
if $form->param("amount") eq "";
# Check the length
return {"msg"=>N_("This amount is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"amount"}]}
if length $form->param("amount") > ${$self->{"maxlens"}}{"amount"};
# Check if it is a valid positive integer
return {"msg"=>N_("Please fill in a positive integer amount.")}
unless $form->param("amount") =~ /^\d+$/ && $form->param("amount") > 0;
# Set to an integer
$_ = $form->param("amount");
$_ += 0;
$form->param("amount", $_);
# OK
return;
}
# _redir_seltrx: Suspend and move to the transaction selection form
sub _redir_seltrx : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if !defined $self->{"form"}->param("seltrx");
call_form FORM_ACCTTRX, undef, "import_seltrx";
}
# _redir_deltrx: Remove the transaction
sub _redir_deltrx : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if !defined $self->{"form"}->param("deltrx");
$self->{"form"}->delete("trx");
success_redirect undef;
}
# _redir_selsubj: Suspend and move to the accounting subject selection form
sub _redir_selsubj : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if !defined $self->{"form"}->param("selsubj");
call_form FORM_ACCTSUBJ, ["list=lastlv"], "import_selsubj";
}
# _redir_delsubj: Remove the accounting subject
sub _redir_delsubj : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if !defined $self->{"form"}->param("delsubj");
$self->{"form"}->delete("subj");
success_redirect undef;
}
return 1;

View File

@@ -0,0 +1,147 @@
# Selima Website Content Management System
# AcctSubj.pm: The accounting subject form checker.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-08-23
package Selima::Checker::AcctSubj;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::CallForm;
use Selima::FetchRec;
use Selima::ShortCut;
use Selima::DataVars qw($DBH);
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "acctsubj" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
return $self;
}
# _check_code: Check the code
sub _check_code : method {
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("code");
return $error if defined $error;
# Regularize it
$self->_trim("code");
# Check if it is filled
return {"msg"=>N_("Please fill in the code.")}
if $form->param("code") eq "";
# Check the length
return {"msg"=>N_("This code is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"code"}]}
if length $form->param("code") > ${$self->{"maxlens"}}{"code"};
# Check if the characters used are valid
return {"msg"=>N_("Only numbers are allowed for the code.")}
unless $form->param("code") =~ /^\d+$/;
# Check if this item is duplicated
@_ = qw();
push @_, "code=" . $DBH->quote($form->param("code"));
push @_, "sn!=" . $self->{"sn"} if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This accounting subject already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
# Check if its parent code exists
if (length $form->param("code") > 1) {
$_ = substr $form->param("code"), 0, -1;
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE code=" . $DBH->quote($_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("Accounting subject [_1] does not exist. You cannot create a subject under that."),
"margs"=>[$_]}
if $sth->rows == 0;
}
# OK
return;
}
# _check_parent: Check the parent subject
sub _check_parent : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql, %row);
$self = $_[0];
$form = $self->{"form"};
# "topmost not set" has a different form context
return {"msg"=>N_("Please select a parent accounting subject.")}
if $self->_missing("topmost");
# Regularize it
$self->_trim("topmost");
# Check the option value
return {"msg"=>N_("This option is invalid. Please select a proper parent accounting subject.")}
unless $form->param("topmost") =~ /^(?:true|false)$/;
# Check the parent subject if not a topmost subject
if ($form->param("topmost") eq "false") {
# Check if our code says we are topmost
if (!$self->_missing("code")) {
$self->_trim("code");
return {"msg"=>N_("An accounting subject having its code with a single digit must not have a parent.")}
if length $form->param("code") < 2;
}
# Check if it exists
$error = $self->_missing("parent");
return $error if defined $error;
# Regularize it
$self->_trim("parent");
# Check if it is filled
return {"msg"=>N_("Please select a parent accounting subject.")}
if $form->param("parent") eq "";
# Check if the parent subject is itself
return {"msg"=>N_("An accounting subject cannot belong to itself. Please select another one.")}
if $self->{"iscur"} && $form->param("parent") == $self->{"sn"};
# Check if this subject exists
%row = fetchrec ${$form->param_fetch("parent")}[0], "acctsubj";
return {"msg"=>N_("This parent accounting subject does not exist anymore. Please select another one.")}
if keys %row == 0;
# Check if the parent matches our code
if (!$self->_missing("code")) {
$_ = substr $form->param("code"), 0, -1;
return {"msg"=>N_("The parent accounting subject of accounting subject [_1] must be of code [_2], not [_3]."),
"margs"=>[$form->param("code"), $_, $row{"code"}]}
if $row{"code"} ne $_;
}
# Check the parent subject if a topmost subject
} else {
# Check if our code says we are not topmost
if (!$self->_missing("code")) {
$self->_trim("code");
return {"msg"=>N_("An accounting subject having its code with more than one digit must have a parent.")}
if length $form->param("code") > 1;
}
}
# OK
return;
}
# _check_title: Check the title
# Use the default title checker
return 1;

View File

@@ -0,0 +1,268 @@
# Selima Website Content Management System
# AcctTrx.pm: The accounting transaction form checker.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-22
package Selima::Checker::AcctTrx;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::Accounting;
use Selima::CallForm;
use Selima::ChkFunc;
use Selima::FetchRec;
use Selima::ShortCut;
use Selima::DataVars qw($DBH :forms);
use Selima::Checker::AcctRec;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "accttrx" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
# Regularize the form subtype
$self->_trim("formsub") if !$self->_missing("formsub");
# Record the form subtype
$self->{"subtype"} = $self->{"form"}->param("formsub");
return $self;
}
# _check_date: Check the date
# Use the default date checker
# _check_ord: Check the order
# Use the default order checker
# _check_recs: Check the records
sub _check_recs : method {
local ($_, %_);
my ($self, $form, $error, $debtcount, $crdtcount);
$self = $_[0];
$form = $self->{"form"};
# Check the subtype
# Check if it exists
$error = $self->_missing("formsub");
return $error if defined $error;
# Check the option value
return {"msg"=>N_("This form suptype is invalid. Please specify a proper user.")}
unless $self->{"subtype"} =~ /^(?:expense|income|trans)$/;
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
$crdtcount = 0;
} else {
# Find the last-used credit record
for ( $_ = 0;
defined $form->param("crdt$_" . "subj")
&& defined $form->param("crdt$_" . "summary")
&& defined $form->param("crdt$_" . "amount"); $_++) {};
if ($_ > 0) {
for ( $_--;
$_ >= 0
&& $form->param("crdt$_" . "subj") eq ""
&& $form->param("crdt$_" . "summary") eq ""
&& $form->param("crdt$_" . "amount") eq ""; $_--) {};
}
$crdtcount = $_ + 1;
if ($crdtcount == 0) {
return {"msg"=>N_("Please fill in the credit side of the accounting transaction.")}
if $self->{"subtype"} eq "trans";
return {"msg"=>N_("Please fill in the accounting transaction content.")};
}
}
# A form to fill in a cash income transaction
if ($self->{"subtype"} eq "income") {
$debtcount = 0;
} else {
# Find the last-used debit record
for ( $_ = 0;
defined $form->param("debt$_" . "subj")
&& defined $form->param("debt$_" . "summary")
&& defined $form->param("debt$_" . "amount"); $_++) {};
if ($_ > 0) {
for ( $_--;
$_ >= 0
&& $form->param("debt$_" . "subj") eq ""
&& $form->param("debt$_" . "summary") eq ""
&& $form->param("debt$_" . "amount") eq ""; $_--) {};
}
$debtcount = $_ + 1;
if ($debtcount == 0) {
return {"msg"=>N_("Please fill in the debit side of the accounting transaction.")}
if $self->{"subtype"} eq "trans";
return {"msg"=>N_("Please fill in the accounting transaction content.")};
}
}
# Check the debit records
for ($_ = 0; $_ < $debtcount; $_++) {
my ($subform, $checker, $error);
# Regularize it
$self->_trim("debt$_" . "subj");
$self->_trim("debt$_" . "summary");
$self->_trim("debt$_" . "amount");
# Skip if it is not filled
next if $form->param("debt$_" . "subj") eq ""
&& $form->param("debt$_" . "summary") eq ""
&& $form->param("debt$_" . "amount") eq "";
# Check with the subform checker
$subform = new CGI("");
$subform->param("trx", $self->{"sn"}) if $self->{"iscur"};
$subform->param("subj", $form->param("debt$_" . "subj"));
$subform->param("summary", $form->param("debt$_" . "summary"));
$subform->param("amount", $form->param("debt$_" . "amount"));
$checker = new Selima::Checker::AcctRec($subform);
$error = $checker->check("subj", "summary", "amount");
return $error if defined $error;
$form->param("debt$_" . "subj", $subform->param("subj"));
$form->param("debt$_" . "summary", $subform->param("summary"));
$form->param("debt$_" . "amount", $subform->param("amount"));
}
# Check the credit records
for ($_ = 0; $_ < $crdtcount; $_++) {
my ($subform, $checker, $error);
# Regularize it
$self->_trim("crdt$_" . "subj");
$self->_trim("crdt$_" . "summary");
$self->_trim("crdt$_" . "amount");
# Skip if it is not filled
next if $form->param("crdt$_" . "subj") eq ""
&& $form->param("crdt$_" . "summary") eq ""
&& $form->param("crdt$_" . "amount") eq "";
# Check with the subform checker
$subform = new CGI("");
$subform->param("trx", $self->{"sn"}) if $self->{"iscur"};
$subform->param("subj", $form->param("crdt$_" . "subj"));
$subform->param("summary", $form->param("crdt$_" . "summary"));
$subform->param("amount", $form->param("crdt$_" . "amount"));
$checker = new Selima::Checker::AcctRec($subform);
$error = $checker->check("subj", "summary", "amount");
return $error if defined $error;
$form->param("crdt$_" . "subj", $subform->param("subj"));
$form->param("crdt$_" . "summary", $subform->param("summary"));
$form->param("crdt$_" . "amount", $subform->param("amount"));
}
# Check the balance
if ($self->{"subtype"} eq "trans") {
my ($sumdebit, $sumcredit);
for ($_ = 0, $sumdebit = 0; $_ < $debtcount; $_++) {
# Skip if it is not filled
next if $form->param("debt$_" . "amount") eq "";
$sumdebit += $form->param("debt$_" . "amount");
}
for ($_ = 0, $sumcredit = 0; $_ < $crdtcount; $_++) {
# Skip if it is not filled
next if $form->param("crdt$_" . "amount") eq "";
$sumcredit += $form->param("crdt$_" . "amount");
}
return {"msg"=>N_("The total amounts of the debit side and the credit side are not balanced (debit [_1], credit [_2]."),
"margs"=>[$sumdebit, $sumcredit]}
if $sumdebit != $sumcredit;
}
return;
}
# _check_note: Check the note
sub _check_note : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("note");
return $error if defined $error;
# Regularize it
$self->_trimtext("note");
# Skip if it is not filled
$form->param("note", "")
if $form->param("note") eq C_("Fill in the note here.");
return if $form->param("note") eq "";
# Check the length
return {"msg"=>N_("This note is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"note"}]}
if length $form->param("note") > ${$self->{"maxlens"}}{"note"};
# OK
return;
}
# _redir_cnvttrans: Convert to a transfer transaction
sub _redir_cnvttrans : method {
local ($_, %_);
my ($self, $form, $sum);
$self = $_[0];
$form = $self->{"form"};
# Skip if not requested
return if $self->_missing("cnvttrans");
# Skip if the form subtype not supplied
return if !defined $self->{"subtype"};
# Skip if it is not an cash expense/income transaction
return if $self->{"subtype"} !~ /^(?:expense|income)$/;
# Set to a transfer transaction
$form->param("formsub", "trans");
# Set the other side
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
$form->param("crdt0subj", acctsubj_sn(ACCTSUBJ_CASH));
$form->param("crdt0summary", undef);
$sum = 0;
foreach (grep /^debt\d+amount$/, $form->param) {
$self->_trim($_);
$_ = $form->param($_);
s/NT\$ ?//;
s/,//g;
s/\.0+$//;
$sum += $_ if /^\d+$/;
}
$form->param("crdt0amount", $sum);
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
$form->param("debt0subj", acctsubj_sn(ACCTSUBJ_CASH));
$form->param("debt0summary", undef);
$sum = 0;
foreach (grep /^crdt\d+amount$/, $form->param) {
$self->_trim($_);
$_ = $form->param($_);
s/NT\$ ?//;
s/,//g;
s/\.0+$//;
$sum += $_ if /^\d+$/;
}
$form->param("debt0amount", $sum);
}
# Show the form again
success_redirect undef;
}
# _redir_selsubj: Suspend and move to the accounting subject selection form
sub _redir_selsubj : method {
local ($_, %_);
my $self;
$self = $_[0];
@_ = sort grep /^sel(?:debt|crdt)\d+subj$/, $self->{"form"}->param;
# Skip if not requested
return if @_ == 0;
# Record the hit button
$_[0] =~ /^sel((?:debt|crdt)\d+)subj$/;
$self->{"form"}->param("caller_index", $1);
call_form FORM_ACCTSUBJ, ["list=lastlv"], "import_selsubj";
}
return 1;

View File

@@ -0,0 +1,218 @@
# Selima Website Content Management System
# Group.pm: The account group form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-12
package Selima::Checker::Group;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::CallForm;
use Selima::ChkPriv;
use Selima::DataVars qw($DBH :forms);
use Selima::ShortCut;
use Selima::UserName;
use Selima::Checker::UserMem;
use Selima::Checker::GroupMem;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my $class;
($class, @_) = @_;
$_[1] = "groups" if scalar(@_) < 2 || !defined $_[1];
return $class->SUPER::new(@_);
}
# _check_id: Check the group ID.
sub _check_id : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing a super-user group
return if $self->{"iscur"} && !is_su && $self->{"sn"} == su_group_sn;
# Check if it exists
$error = $self->_missing("id");
return $error if defined $error;
# Regularize it
$self->_trim("id");
# Check if it is filled
return {"msg"=>N_("Please fill in the group ID.")}
if $form->param("id") eq "";
# Check the length
return {"msg"=>N_("This group ID. is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"id"}]}
if length $form->param("id") > ${$self->{"maxlens"}}{"id"};
return {"msg"=>N_("This group ID. is too short. (Min. length [#,_1])"),
"margs"=>[${$self->{"minlens"}}{"id"}]}
if length $form->param("id") < ${$self->{"minlens"}}{"id"};
# Check if the characters used are valid
return {"msg"=>N_("Only lower-case English letters, numbers and underscores are allowed for the group ID.")}
unless $form->param("id") =~ /^[a-z][a-z0-9_]*$/;
# Check if this item is duplicated
@_ = qw();
push @_, "id=" . $DBH->quote($form->param("id"));
push @_, "sn!=" . $self->{"sn"} if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This group already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
# OK
return;
}
# _check_dsc: Check the group description
sub _check_dsc : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("id");
return $error if defined $error;
# Regularize it
$self->_trim("dsc");
# Check if it is filled
return {"msg"=>N_("Please fill in the privilege description.")}
if $form->param("dsc") eq "";
# Check the length
return {"msg"=>N_("This privilege description is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"dsc"}]}
if length $form->param("dsc") > ${$self->{"maxlens"}}{"dsc"};
# OK
return;
}
# _check_subuser: Check the user members
sub _check_subuser : method {
local ($_, %_);
my ($self, $form, $error, $subform, $checker);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing a super-user group
return if $self->{"iscur"} && !is_su && $self->{"sn"} == su_group_sn;
# Get the selected items
@_ = map $_ . "sn",
grep /^subuser\d+/ && defined $form->param($_ . "sn"), $form->param;
# Regularize them
$self->_trim(@_);
# Merge the duplicates
%_ = map { ($form->param($_))[0] => 1 } @_;
$subform = new CGI("");
$subform->param("grp", $self->{"sn"}) if $self->{"iscur"};
foreach (keys %_) {
$subform->param("member", $_);
$checker = new Selima::Checker::UserMem($subform);
$error = $checker->check("member");
return $error if defined $error;
}
# OK
return;
}
# _check_subgroup: Check the group members
sub _check_subgroup : method {
local ($_, %_);
my ($self, $form, $error, $subform, $checker);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing a super-user group
return if $self->{"iscur"} && !is_su && $self->{"sn"} == su_group_sn;
# Get the selected items
@_ = map $_ . "sn",
grep /^subgroup\d+/ && defined $form->param($_ . "sn"), $form->param;
# Regularize them
$self->_trim(@_);
# Merge the duplicates
%_ = map { ($form->param($_))[0] => 1 } @_;
$subform = new CGI("");
$subform->param("grp", $self->{"sn"}) if $self->{"iscur"};
foreach (keys %_) {
$subform->param("member", $_);
$checker = new Selima::Checker::GroupMem($subform);
$error = $checker->check("member");
return $error if defined $error;
}
# OK
return;
}
# _check_supgroup: Check the belonging groups
sub _check_supgroup : method {
local ($_, %_);
my ($self, $form, $error, $subform, $checker);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing a super-user group
return if $self->{"iscur"} && !is_su && $self->{"sn"} == su_group_sn;
# Get the selected items
@_ = map $_ . "sn",
grep /^supgroup\d+/ && defined $form->param($_ . "sn"), $form->param;
# Regularize them
$self->_trim(@_);
# Merge the duplicates
%_ = map { ($form->param($_))[0] => 1 } @_;
$subform = new CGI("");
$subform->param("member", $self->{"sn"}) if $self->{"iscur"};
foreach (keys %_) {
$subform->param("grp", $_);
$checker = new Selima::Checker::GroupMem($subform);
$error = $checker->check("grp");
return $error if defined $error;
}
# OK
return;
}
# _redir_selsubuser: Suspend and move to the subordinate user selection form
sub _redir_selsubuser : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selsubuser");
call_form FORM_USERS, undef, "import_selsubuser";
}
# _redir_selsubgroup: Suspend and move to the subordinate group selection form
sub _redir_selsubgroup : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selsubgroup");
call_form FORM_GROUPS, undef, "import_selsubgroup";
}
# _redir_selsupgroup: Suspend and move to the superordinate group selection form
sub _redir_selsupgroup : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selsupgroup");
call_form FORM_GROUPS, undef, "import_selsupgroup";
}
return 1;

View File

@@ -0,0 +1,143 @@
# Selima Website Content Management System
# GroupMem.pm: The group-to-group membership form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-13
package Selima::Checker::GroupMem;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::ChkFunc;
use Selima::CallForm;
use Selima::DataVars qw($DBH :forms);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my $class;
($class, @_) = @_;
$_[1] = "groupmem" if scalar(@_) < 2 || !defined $_[1];
return $class->SUPER::new(@_);
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, $error, @cols);
($self, @cols) = @_;
# Run the parent method first
$error = $self->SUPER::check(@cols);
return $error if defined $error;
# See if we need to check the duplicates. Check it in the end.
%_ = map { $_ => 1 } @cols;
if (exists $_{"grp"} && exists $_{"member"}) {
$error = $self->__check_dup();
return $error if defined $error;
}
return;
}
# _check_grp: Check the group
sub _check_grp : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Run the default group checker
$error = $self->SUPER::_check_grp;
return $error if defined $error;
# Check if the group and the member are different
return {"msg"=>N_("Please select a different belonging group.")}
if !$self->_missing("member")
&& $form->param("member") ne ""
&& $form->param("grp") == $form->param("member");
# OK
return;
}
# _check_member: Check the member
sub _check_member : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("member");
return $error if defined $error;
# Regularize it
$self->_trim("member");
# Check if it is filled
return {"msg"=>N_("Please select a member.")}
if $form->param("member") eq "";
# Check if this group exists
return {"msg"=>N_("This member does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("member")}[0], "groups AS grpmembers";
# Check if the group and the member are different
return {"msg"=>N_("Please select a different group member.")}
if !$self->_missing("grp")
&& $form->param("grp") ne ""
&& $form->param("grp") == $form->param("member");
# OK
return;
}
# __check_dup: Check if this item is duplicated
sub __check_dup : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
@_ = qw();
push @_, "grp=" . $form->param("grp");
push @_, "member=" . $form->param("member");
push @_, "sn!=" . $self->{"sn"}
if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This membership record already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
return;
}
# _redir_selmember: Suspend and move to the member selection form
sub _redir_selmember : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selmember");
call_form FORM_GROUPS, undef, "import_selmember";
}
# _redir_delmember: Remove the member
sub _redir_delmember : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("delmember");
$self->{"form"}->delete("member");
success_redirect undef;
}
return 1;

View File

@@ -0,0 +1,157 @@
# Selima Website Content Management System
# Guestbook.pm: The base administrative guestbook form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Checker::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "guestbook" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
${$self->{"maxlens"}}{"message"} = 10240;
return $self;
}
# _check_name: Check the name
sub _check_name : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("name");
return $error if defined $error;
# Regularize it
$self->_trim("name");
# Check the length
return {"msg"=>N_("This signature is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"name"}]}
if length $form->param("name") > ${$self->{"maxlens"}}{"name"};
# OK
return;
}
# _check_name_req: Check the name (required)
sub _check_name_req : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("name");
return $error if defined $error;
# Regularize it
$self->_trim("name");
# Check if it is filled
return {"msg"=>N_("Please fill in the signature.")}
if $form->param("name") eq "";
# Check the length
return {"msg"=>N_("This signature is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"name"}]}
if length $form->param("name") > ${$self->{"maxlens"}}{"name"};
# OK
return;
}
# _check_identity: Check the identity
sub _check_identity : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("identity");
return $error if defined $error;
# Regularize it
$self->_trim("identity");
# Check the length
return {"msg"=>N_("This identity is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"identity"}]}
if length $form->param("identity") > ${$self->{"maxlens"}}{"identity"};
# OK
return;
}
# _check_location: Check the location
sub _check_location : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("location");
return $error if defined $error;
# Regularize it
$self->_trim("location");
# Check the length
return {"msg"=>N_("This location is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"location"}]}
if length $form->param("location") > ${$self->{"maxlens"}}{"location"};
# OK
return;
}
# _check_email: Check the e-mail
sub _check_email : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("email");
return $error if defined $error;
# Regularize it
$self->_trim("email");
# Check the length
return {"msg"=>N_("This e-mail is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"email"}]}
if length $form->param("email") > ${$self->{"maxlens"}}{"email"};
# OK
return;
}
# _check_url: Check the URL
sub _check_url : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("url");
return $error if defined $error;
# Regularize it
$self->_trim("url");
# Check the length
return {"msg"=>N_("This website URL is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"url"}]}
if length $form->param("url") > ${$self->{"maxlens"}}{"url"};
# OK
return;
}
return 1;

View File

@@ -0,0 +1,224 @@
# Selima Website Content Management System
# Public.pm: The base guestbook form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-23
package Selima::Checker::Guestbook::Public;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker::Guestbook);
use URI::Find qw();
use Selima::DataVars qw($DBH :lninfo);
use Selima::GetLang;
use Selima::HTTP;
use Selima::ShortCut;
use vars qw($uri_finder);
# _check_name: Check the name
sub _check_name : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("name");
return $error if defined $error;
# Regularize it
$self->_trim("name");
# Skip if it is not filled
return if $form->param("name") eq "";
# Check the length
return {"msg"=>N_("Your signature is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"name"}]}
if length $form->param("name") > ${$self->{"maxlens"}}{"name"};
# OK
return;
}
# _check_name_req: Check the name (required)
sub _check_name_req : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("name");
return $error if defined $error;
# Regularize it
$self->_trim("name");
# Check if it is filled
return {"msg"=>N_("Please fill in your signature.")}
if $form->param("name") eq "";
# Check the length
return {"msg"=>N_("Your signature is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"name"}]}
if length $form->param("name") > ${$self->{"maxlens"}}{"name"};
# OK
return;
}
# _check_identity: Check the identity
sub _check_identity : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("identity");
return $error if defined $error;
# Regularize it
$self->_trim("identity");
# Skip if it is not filled
return if $form->param("identity") eq "";
# Check the length
return {"msg"=>N_("Your identity is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"identity"}]}
if length $form->param("identity") > ${$self->{"maxlens"}}{"identity"};
# OK
return;
}
# _check_location: Check the location
sub _check_location : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("location");
return $error if defined $error;
# Regularize it
$self->_trim("location");
# Skip if it is not filled
return if $form->param("location") eq "";
# Check the length
return {"msg"=>N_("Your location is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"location"}]}
if length $form->param("location") > ${$self->{"maxlens"}}{"location"};
# OK
return;
}
# _check_email: Check the e-mail
sub _check_email : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("email");
return $error if defined $error;
# Regularize it
$self->_trim("email");
# Skip if it is not filled
return if $form->param("email") eq "";
# Check the length
return {"msg"=>N_("Your e-mail is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"email"}]}
if length $form->param("email") > ${$self->{"maxlens"}}{"email"};
# OK
return;
}
# _check_url: Check the URL
sub _check_url : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("url");
return $error if defined $error;
# Regularize it
$self->_trim("url");
# Skip if it is not filled
return if $form->param("url") eq "";
# Check the length
return {"msg"=>N_("Your website URL is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"url"}]}
if length $form->param("url") > ${$self->{"maxlens"}}{"url"};
# OK
return;
}
# _check_message: The default message checker
sub _check_message : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("message");
return $error if defined $error;
# Regularize it
$self->_trimtext("message");
# Check if it is filled
$form->param("message", "")
if $form->param("message") eq C_("Fill in your message here.");
return {"msg"=>N_("Please fill in your message.")}
if $form->param("message") eq "";
# Check the length
return {"msg"=>N_("Your message is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"message"}]}
if length $form->param("message") > ${$self->{"maxlens"}}{"message"};
# OK
return;
}
# _check_flood: Check the flooding attack
sub _check_flood : method {
local ($_, %_);
my ($self, $form, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# At most 5 posts/hours from a single IP
$sql = "SELECT count(*) AS count FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE created > now() - cast('1 hour' AS interval)"
. " AND ip='" . $ENV{"REMOTE_ADDR"} . "';\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("You can post at most 5 messages in 1 hour.")}
if ${$sth->fetch}[0] > 5;
# OK
return;
}
# _check_dup: Check the duplicated message
sub _check_dup : method {
local ($_, %_);
my ($self, $form, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# No duplicated message in the recent 5 posts
$sql = "SELECT message FROM " . $DBH->quote_identifier($self->{"table"})
. " ORDER BY created DESC LIMIT 5;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
for ($_ = 0; $_ < $sth->rows; $_++) {
return {"msg"=>N_("Your message is already posted.")}
if ${$sth->fetch}[0] eq $form->param("message");
}
# OK
return;
}
return 1;

View File

@@ -0,0 +1,252 @@
# Selima Website Content Management System
# Link.pm: The related-link form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::Checker::Link;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Email::Valid;
use Selima::ChkFunc;
use Selima::DataVars qw($DBH);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "links" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
${$self->{"minlens"}}{"email"} = 5;
return $self;
}
# _check_addr: Check the address
sub _check_addr : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("addr");
return $error if defined $error;
# Regularize it
$self->_trim("addr");
# Skip if it is not filled
return if $form->param("addr") eq "";
# Check the length
return {"msg"=>N_("This address is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"addr"}]}
if length $form->param("addr") > ${$self->{"maxlens"}}{"addr"};
# OK
return;
}
# _check_cats: Check the categories list
sub _check_cats : method {
local ($_, %_);
my ($self, $form, $error, $val);
$self = $_[0];
$form = $self->{"form"};
# Loop each category
for ($_ = 0, %_ = qw(); !$self->_missing("cat$_"); $_++) {
# Regularize it
$self->_trim("cat$_");
# Skip if it is not filled
next if $form->param("cat$_") eq "";
# Check if this selection is duplicated
return {"msg"=>N_("This category is duplicated. You cannot set duplicated ones.")}
if exists $_{$form->param("cat$_")};
# Check if the category exists
return {"msg"=>N_("This category does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("cat$_")}[0], "linkcat";
$_{$form->param("cat$_")} = 1;
}
# Check if there is any category selected
return {"msg"=>N_("Please select a category.")}
if scalar(keys %_) == 0;
# OK
return;
}
# _check_email: Check the e-mail
sub _check_email : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("email");
return $error if defined $error;
# Regularize it
$self->_trim("email");
# Skip if it is not filled
return if $form->param("email") eq "";
# Check the length
return {"msg"=>N_("This e-mail is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"email"}]}
if length $form->param("email") > ${$self->{"maxlens"}}{"email"};
return {"msg"=>N_("This e-mail is too short. (Min. length [#,_1])"),
"margs"=>[${$self->{"minlens"}}{"email"}]}
if length $form->param("email") < ${$self->{"minlens"}}{"email"};
# Check the e-mail validity
return {"msg"=>N_("Please fill in a valid e-mail address.")}
if !Email::Valid->rfc822($form->param("email"));
return {"msg"=>N_("The domain of this e-mail does not exists. Check if there is any typo in it.")}
if $self->_missing("hid")
&& !Email::Valid->mx($form->param("email"));
# OK
return;
}
# _check_fax: Check the facsimile number
sub _check_fax : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("fax");
return $error if defined $error;
# Regularize it
$self->_trim("fax");
# Skip if it is not filled
return if $form->param("fax") eq "";
# Check the length
return {"msg"=>N_("This facsimile number is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"fax"}]}
if length $form->param("fax") > ${$self->{"maxlens"}}{"fax"};
# OK
return;
}
# _check_icon: Check the link icon
sub _check_icon : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("icon");
return $error if defined $error;
# Regularize it
$self->_trim("icon");
# Skip if it is not filled
return if $form->param("icon") eq "" || $form->param("icon") eq "http://";
# Check the length
return {"msg"=>N_("This link icon URL is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"icon"}]}
if length $form->param("icon") > ${$self->{"maxlens"}}{"icon"};
# Check its format
return {"msg"=>N_("Please fill in a valid link icon URL.")}
if !is_url_wellformed $form->param("icon");
# Check if it is available
return {"msg"=>N_("This link icon URL is not reachable. Check if there is any typo in it.")}
if $self->_missing("hid")
&& !is_url_reachable $form->param("icon");
# OK
return;
}
# _check_tel: Check the telephone number
sub _check_tel : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("tel");
return $error if defined $error;
# Regularize it
$self->_trim("tel");
# Skip if it is not filled
return if $form->param("tel") eq "";
# Check the length
return {"msg"=>N_("This telephone number is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"tel"}]}
if length $form->param("tel") > ${$self->{"maxlens"}}{"tel"};
# OK
return;
}
# _check_title_2ln: The 2nd language title checker
sub _check_title_2ln : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("title_2ln");
return $error if defined $error;
# Regularize it
$self->_trim("title_2ln");
# Skip if it is not filled
return if $form->param("title_2ln") eq "";
# Check the length
return {"msg"=>N_("This 2nd language title is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"title_2ln"}]}
if length $form->param("title_2ln") > ${$self->{"maxlens"}}{"title_2ln"};
# OK
return;
}
# _check_url: The URL checker
sub _check_url : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("url");
return $error if defined $error;
# Regularize it
$self->_trim("url");
# Check if it is filled
return {"msg"=>N_("Please fill in the URL.")}
if $form->param("url") eq "" || $form->param("url") eq "http://";
# Check the length
return {"msg"=>N_("This URL is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"url"}]}
if length $form->param("url") > ${$self->{"maxlens"}}{"url"};
# Check its format
return {"msg"=>N_("Please fill in a valid URL.")}
if !is_url_wellformed $form->param("url");
# Check if this item is duplicated
@_ = qw();
push @_, "url=" . $DBH->quote($form->param("url"));
push @_, "sn!=" . $self->{"sn"} if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This related link already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
# Check if it is available
return {"msg"=>N_("This URL is not reachable. Check if there is any typo in it.")}
if $self->_missing("hid")
&& !is_url_reachable $form->param("url");
# OK
return;
}
return 1;

View File

@@ -0,0 +1,121 @@
# Selima Website Content Management System
# LinkCat.pm: The related-link category form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::Checker::LinkCat;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::CallForm;
use Selima::ChkFunc;
use Selima::DataVars qw($DBH :forms);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "linkcat" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
${$self->{"maxlens"}}{"ord"} = 2;
${$self->{"minlens"}}{"id"} = 2;
return $self;
}
# _check_id: Check the ID.
sub _check_id : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Run the default ID. checker
$error = $self->SUPER::_check_id;
return $error if defined $error;
# ID. cannot be "index" to avoid overriding index.html
return {"msg"=>N_("\"index\" is dedicated to the index file index.html. You cannot set the ID. as \"index\".")}
if $form->param("id") eq "index";
# Check if this item is duplicated
if (!$self->_missing("topmost", "parent")) {
@_ = qw();
push @_, "id=" . $DBH->quote($form->param("id"));
push @_, "sn!=" . $self->{"sn"} if $self->{"iscur"};
if ($form->param("topmost") eq "true") {
push @_, "parent IS NULL";
} else {
push @_, "parent=" . $DBH->quote($form->param("parent"));
}
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This category already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
}
# OK
return;
}
# _check_parent: Check the parent category
sub _check_parent : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# "topmost not set" has a different form context
return {"msg"=>N_("Please select a parent category.")}
if $self->_missing("topmost");
# Regularize it
$self->_trim("topmost");
# Check the option value
return {"msg"=>N_("This option is invalid. Please select a proper parent category.")}
unless $form->param("topmost") =~ /^(?:true|false)$/;
# Check the parent category if not a topmost category
if ($form->param("topmost") eq "false") {
# Check if it exists
$error = $self->_missing("parent");
return $error if defined $error;
# Regularize it
$self->_trim("parent");
# Check if it is filled
return {"msg"=>N_("Please select a parent category.")}
if $form->param("parent") eq "";
# Check if this category exists
return {"msg"=>N_("This parent category does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("parent")}[0], "linkcat";
if ($self->{"iscur"}) {
# Check if the parent category is itself
return {"msg"=>N_("A category cannot belong to itself. Please select another one.")}
if $form->param("parent") == $self->{"sn"};
# Check if the parent directory is its descendant
$sql = "SELECT linkcat_ischild(" . $self->{"sn"} . ", "
. $form->param("parent") . ") AS is_child;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("A category cannot belong to its descendant. Please select another one.")}
if ${$sth->fetchrow_hashref}{"is_child"};
}
}
# OK
return;
}
return 1;

View File

@@ -0,0 +1,162 @@
# Selima Website Content Management System
# LinkCatz.pm: The related-link category membership form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-11-03
package Selima::Checker::LinkCatz;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::ChkFunc;
use Selima::CallForm;
use Selima::DataVars qw($DBH :forms);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my $class;
($class, @_) = @_;
$_[1] = "linkcatz" if scalar(@_) < 2 || !defined $_[1];
return $class->SUPER::new(@_);
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, $error, @cols);
($self, @cols) = @_;
# Run the parent method first
$error = $self->SUPER::check(@cols);
return $error if defined $error;
# See if we need to check the duplicates. Check it in the end.
%_ = map { $_ => 1 } @cols;
if (exists $_{"cat"} && exists $_{"link"}) {
$error = $self->__check_dup();
return $error if defined $error;
}
return;
}
# _check_cat: Check the category
sub _check_cat : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("cat");
return $error if defined $error;
# Regularize it
$self->_trim("cat");
# Check if it is filled
return {"msg"=>N_("Please select a category.")}
if $form->param("cat") eq "";
# Check if the category exists
return {"msg"=>N_("This category does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("cat")}[0], "linkcat";
# OK
return;
}
# _check_link: Check the related link
sub _check_link : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("link");
return $error if defined $error;
# Regularize it
$self->_trim("link");
# Check if it is filled
return {"msg"=>N_("Please select a related link.")}
if $form->param("link") eq "";
# Check if this link exists
return {"msg"=>N_("This related link does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("link")}[0], "links";
# OK
return;
}
# __check_dup: Check if this item is duplicated
sub __check_dup : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
@_ = qw();
push @_, "cat=" . $form->param("cat");
push @_, "link=" . $form->param("link");
push @_, "sn!=" . $self->{"sn"}
if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This categorization record already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
return;
}
# _redir_selcat: Suspend and move to the category selection form
sub _redir_selcat : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selcat");
call_form FORM_LINKCAT, undef, "import_selcat";
}
# _redir_delcat: Remove the category
sub _redir_delcat : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("delcat");
$self->{"form"}->delete("cat");
success_redirect undef;
}
# _redir_sellink: Suspend and move to the related link selection form
sub _redir_sellink : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("sellink");
call_form FORM_LINKS, undef, "import_sellink";
}
# _redir_dellink: Remove the related link
sub _redir_dellink : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("dellink");
$self->{"form"}->delete("link");
success_redirect undef;
}
return 1;

View File

@@ -0,0 +1,132 @@
# Selima Website Content Management System
# ListPref.pm: The list preference form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Checker::ListPref;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::CallForm;
use Selima::ChkFunc;
use Selima::DataVars qw($DBH :forms);
use Selima::HTTP;
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "userpref" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
${$self->{"maxlens"}}{"listsize"} = 4;
return $self;
}
# _check_domain: Check the preference domain
sub _check_domain : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("domain");
return $error if defined $error;
# Regularize it
$self->_trim("domain");
# Check if it is filled
return {"msg"=>N_("Please fill in the preference domain.")}
if $form->param("domain") eq "";
# Check the length
return {"msg"=>N_("This preference domain is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"domain"}]}
if length $form->param("domain") > ${$self->{"maxlens"}}{"domain"};
# OK
return;
}
# _check_listcols: Check the list columns
sub _check_listcols : method {
local ($_, %_);
my ($self, $listcols, $errmsg);
$self = $_[0];
# No need to check the validility. Invalids are simply ignored.
@_ = grep s/^listcols_//, $self->{"form"}->param;
# Obtain the preference value
$listcols = join " ", @_;
# Skip if it is not filled
return if $listcols eq "";
# Check the length
if (length "listcols" > ${$self->{"maxlens"}}{"name"}) {
$errmsg = sprintf "Maximum preference name length too short (%d for \"%s\" %d)",
${$self->{"maxlens"}}{"name"}, "listcols", length "listcols";
http_500 $errmsg;
}
if (length $listcols > ${$self->{"maxlens"}}{"value"}) {
$errmsg = sprintf "Maximum preference value length too short (%d for \"%s\" %d)",
${$self->{"maxlens"}}{"name"}, "listcols", length $listcols;
http_500 $errmsg;
}
# OK
return;
}
# _check_listsize: Check the list size
sub _check_listsize : method {
local ($_, %_);
my ($self, $form, $error, $errmsg);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("listsize");
return $error if defined $error;
# Regularize it
$self->_trim("listsize");
# Check if it is filled
return {"msg"=>N_("Please fill in the number of rows per page.")}
if $form->param("listsize") eq "";
# If there is any non-digit character
return {"msg"=>N_("Please fill in a positive integer number of rows per page.")}
unless $form->param("listsize") =~ /^[1-9][0-9]*$/;
# Set to an integer
$_ = $form->param("listsize");
$_ += 0;
$form->param("listsize", $_);
# Check the length
return {"msg"=>N_("This number of rows per page is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"listsize"}]}
if length $form->param("listsize") > ${$self->{"maxlens"}}{"listsize"};
# Check the length
if (length "listsize" > ${$self->{"maxlens"}}{"name"}) {
$errmsg = sprintf "Maximum preference name length too short (%d for \"%s\" %d)",
${$self->{"maxlens"}}{"name"}, "listsize", length "listsize";
http_500 $errmsg;
}
if (length $form->param("listsize") > ${$self->{"maxlens"}}{"value"}) {
$errmsg = sprintf "Maximum preference value length too short (%d for \"%s\" %d)",
${$self->{"maxlens"}}{"name"}, "listsize", length $form->param("listsize");
http_500 $errmsg;
}
# OK
return;
}
return 1;

View File

@@ -0,0 +1,238 @@
# Selima Website Content Management System
# LogIn.pm: The log-in form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-26
package Selima::Checker::LogIn;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker::User);
use Digest::MD5 qw(md5_hex);
use Selima::Array;
use Selima::ChkPriv;
use Selima::DataVars qw($DBH :hostconf);
use Selima::Guest;
use Selima::HTTP;
use Selima::Logging;
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "users" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
$self->{"row"} = undef;
$self->{"allcols"} = [ $DBH->cols($self->{"table"}) ];
return $self;
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, $error, @cols);
($self, @cols) = @_;
# See if a log in is attemped.
%_ = map { $_ => 1 } @cols;
$self->{"login"} = exists $_{"id"} && exists $_{"passwd"}
if !exists $self->{"login"};
# Run the parent method first
$error = $self->SUPER::check(@cols);
return $error if defined $error;
return;
}
# _check_id: Check the user ID
sub _check_id : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("id");
return $error if defined $error;
# Regularize it
$self->_trim("id");
# Check if it is filled
return {"msg"=>N_("Please fill in your user ID.")}
if $form->param("id") eq "";
# Check the length
if (length $form->param("id") > ${$self->{"maxlens"}}{"id"}) {
actlog("Log in failed for user " . $form->param("id")
. " because user ID is too long.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
if (length $form->param("id") < ${$self->{"minlens"}}{"id"}) {
actlog("Log in failed for user " . $form->param("id")
. " because user ID is too short.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
# Check if this user exists
@_ = qw();
push @_, "id=" . $DBH->quote($form->param("id"));
push @_, "NOT deleted" if in_array("deleted", @{$self->{"allcols"}});
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
if ($sth->rows != 1) {
actlog("Log in failed for user " . $form->param("id")
. " because user ID does not exist.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
# Save it for further reference
$self->{"row"} = $sth->fetchrow_hashref;
$self->{"sn"} = ${$self->{"row"}}{"sn"};
# Check if log-in is closed
if ($NOLOGIN && !is_su $self->{"sn"}) {
actlog("Log in failed for user " . $form->param("id")
. " because website is temporarily closed.")
if $self->{"login"};
# This message is duplicated
return {};
}
# Check if this user is disabled
if (${$self->{"row"}}{"disabled"}) {
actlog("Log in failed for user " . $form->param("id")
. " because account is disabled.")
if $self->{"login"};
return {"msg"=>N_("Your account is disabled. Contact our system administrator for assistence.")};
}
# OK
return;
}
# _check_passwd: Check the user password
sub _check_passwd : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Skip password checks for guests
return if exists $self->{"sn"} && is_guest $self->{"sn"};
# Check if it exists
$error = $self->_missing("passwd");
return $error if defined $error;
# Regularize it
$self->_trim("passwd");
# Check if it is filled
return {"msg"=>N_("Please fill in your password.")}
if $form->param("passwd") eq "";
# Check the length
if (length $form->param("passwd") > ${$self->{"maxlens"}}{"passwd"}) {
actlog("Log in failed for user " . $form->param("id")
. " because password is too long.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
if (length $form->param("passwd") < ${$self->{"minlens"}}{"passwd"}) {
actlog("Log in failed for user " . $form->param("id")
. " because password is too short.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
# Check if the password is correct
if ( defined $self->{"row"}
&& md5_hex($form->param("id") . ":magicat:"
. $form->param("passwd")) eq ${$self->{"row"}}{"passwd"}) {
actlog("Log in failed for user " . $form->param("id")
. " because password is incorrect.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
# OK
return;
}
# _check_authdig: Check the user credential using HTTP Digest Authentication
sub _check_authdig : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Skip credential checks for guests
return if exists $self->{"sn"} && is_guest $self->{"sn"};
# Check if it exists
http_500 "Apache::AuthDigest::API \"rd\" not supplied"
if !exists $self->{"rd"};
http_500 "client response \"response\" not supplied"
if !exists $self->{"response"};
http_500 "\"id\" did not checked before \"authdig\""
if !defined $self->{"row"};
# Check if the credential is correct
if ( !$self->{"rd"}->compare_digest_response($self->{"response"},
${$self->{"row"}}{"passwd"})) {
actlog("Log in failed for user " . $form->param("id")
. " because password is incorrect.")
if $self->{"login"};
return {"msg"=>N_("Log in failed. Either your user ID or your password is incorrect.")};
}
# OK
return;
}
# _check_admin: Check if the user is an administrator
sub _check_admin : method {
local ($_, %_);
my ($self, $form);
$self = $_[0];
$form = $self->{"form"};
# Skip checking for guests
return if is_guest $self->{"sn"};
# Skip checking for super users
return if is_su $self->{"sn"};
# Check if this user is an administrator
if (!is_admin $self->{"sn"}) {
actlog("Log in failed for user " . $form->param("id")
. " because user is not an administrator.")
if $self->{"login"};
return {"msg"=>N_("You are not an administrator and cannot log into here.")};
}
# OK
return;
}
# _check_nonadmin: Check if the user is not an administrator
sub _check_nonadmin : method {
local ($_, %_);
my ($self, $form);
$self = $_[0];
$form = $self->{"form"};
# Skip checking for guests
return if is_guest $self->{"sn"};
# Check if this user is an administrator
if (is_admin $self->{"sn"}) {
actlog("Log in failed for user " . $form->param("id")
. " because user is an administrator.")
if $self->{"login"};
return {"msg"=>N_("You are an administrator and cannot log into here.")};
}
# OK
return;
}
return 1;

View File

@@ -0,0 +1,58 @@
# Selima Website Content Management System
# MailTo.pm: The e-mail hyperlink redirection form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-25
package Selima::Checker::MailTo;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Email::Valid qw();
use Selima::ShortCut;
# _check_email: Check the submitted e-mail
sub _check_email : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("email");
return $error if defined $error;
# Regularize it
$self->_trim("email");
# Check if it is filled
return {"msg"=>N_("Please fill in the e-mail.")}
if $form->param("email") eq "";
# Un-mung e-mail to its original format
$_ = $form->param("email");
s/ at /\@/;
$form->param("email", $_);
# Check the e-mail validity
return {"msg"=>N_("Please fill in a valid e-mail address.")}
if !Email::Valid->rfc822($form->param("email"));
return {"msg"=>N_("The domain of this e-mail does not exists. Check if there is any typo in it.")}
if !Email::Valid->mx($form->param("email"));
# OK
return;
}
return 1;

View File

@@ -0,0 +1,52 @@
# Selima Website Content Management System
# Page.pm: The base web page form checker.
# Copyright (c) 2005-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2005-02-28
package Selima::Checker::Page;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "pages" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
return $self;
}
# _check_path: Check the page path
# Use the default page path checker
# _check_ord: Check the order
# Use the default order checker
# _check_title: Check the title
# Use the default title checker
# _check_body: Check the content
# Use the default content checker
# _check_kw: Check the keywords list
# Use the default keywords list checker
return 1;

View File

@@ -0,0 +1,51 @@
# Selima Website Content Management System
# Rebuild.pm: The web page rebuild form checker.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-04-04
package Selima::Checker::Rebuild;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::DataVars qw(:scptconf);
use Selima::ShortCut;
# _check_type: Check the page type
sub _check_type : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("type");
return $error if defined $error;
# Regularize it
$self->_trim("addr");
# Check if it is filled
return {"msg"=>N_("Please select the type.")}
if $form->param("type") eq "";
# Check if this link exists
return {"msg"=>N_("This type does not exist anymore. Please select another one.")}
unless defined $MAIN->can("rebuild_" . $form->param("type"));
# OK
return;
}
return 1;

View File

@@ -0,0 +1,82 @@
# Selima Website Content Management System
# ScptPriv.pm: The script privilege form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Checker::ScptPriv;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::DataVars qw($DBH);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my $class;
($class, @_) = @_;
$_[1] = "scptpriv" if scalar(@_) < 2 || !defined $_[1];
return $class->SUPER::new(@_);
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, $error, @cols);
($self, @cols) = @_;
# Run the parent method first
$error = $self->SUPER::check(@cols);
return $error if defined $error;
# See if we need to check the duplicates. Check it in the end.
%_ = map { $_ => 1 } @cols;
if (exists $_{"grp"} && exists $_{"member"}) {
$error = $self->__check_dup();
return $error if defined $error;
}
return;
}
# _check_script: Check the script
# Use the default script checker
# _check_grp: Check the group
# Use the default group checker
# __check_dup: Check if this item is duplicated
sub __check_dup : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
@_ = qw();
push @_, "script=" . $form->param("script");
push @_, "grp=" . $form->param("grp");
push @_, "sn!=" . $self->{"sn"}
if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This script privilege record already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
return;
}
return 1;

View File

@@ -0,0 +1,296 @@
# Selima Website Content Management System
# User.pm: The user account form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-26
package Selima::Checker::User;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Crypt::Cracklib qw(fascist_check);
use Email::Valid qw();
$Crypt::Cracklib::DICT = "/usr/share/dict/pw_dict";
use Selima::Array;
use Selima::ChkPriv;
use Selima::DataVars qw($DBH :groups);
use Selima::LogIn;
use Selima::UserName;
use Selima::Passwd;
use Selima::ShortCut;
use Selima::Checker::UserMem;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my ($class, $self);
($class, @_) = @_;
$_[1] = "users" if scalar(@_) < 2 || !defined $_[1];
$self = $class->SUPER::new(@_);
${$self->{"maxlens"}}{"passwd"} = 16;
${$self->{"minlens"}}{"passwd"} = 6;
${$self->{"minlens"}}{"email"} = 5;
return $self;
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, @cols, $error);
($self, @cols) = @_;
# Check the guest flag first
$self->_is_guest;
# Run the parent method
return $self->SUPER::check(@cols);
}
# _is_guest: If the user being edited is a guest
sub _is_guest : method {
local ($_, %_);
my ($self, $form);
$self = $_[0];
$form = $self->{"form"};
# Checked before
return $form->param("_is_guest") if !$self->_missing("_is_guest");
%_ = map { $_ => 1 } $form->param;
for ($_ = 0; exists $_{"supgroup$_" . "sn"}; $_++) {
# Skip unselected groups
next if !exists $_{"supgroup$_"};
# Check if this is the guest group
return $form->param("_is_guest", 1)
if groupid($form->param("supgroup$_" . "sn")) eq GUEST_GROUP;
}
# No guest group was found
return $form->param("_is_guest", 0);
}
# _check_id: Check the user ID.
sub _check_id : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing a super-user
return if $self->{"iscur"} && !is_su && is_su $self->{"sn"};
# Check if it exists
$error = $self->_missing("id");
return $error if defined $error;
# Regularize it
$self->_trim("id");
# Check if it is filled
return {"msg"=>N_("Please fill in the user ID.")}
if $form->param("id") eq "";
# Check the length
return {"msg"=>N_("This user ID. is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"id"}]}
if length $form->param("id") > ${$self->{"maxlens"}}{"id"};
return {"msg"=>N_("This user ID. is too short. (Min. length [#,_1])"),
"margs"=>[${$self->{"minlens"}}{"id"}]}
if length $form->param("id") < ${$self->{"minlens"}}{"id"};
# Check if the characters used are valid
return {"msg"=>N_("Only English letters, numbers, at-signs, dots, dashes and underscores are allowed for the user ID.")}
unless $form->param("id") =~ /^[a-z][a-z0-9@\.\-_]*$/;
# Check if this item is duplicated
@_ = qw();
push @_, "id=" . $DBH->quote($form->param("id"));
push @_, "sn!=" . $self->{"sn"} if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This user already has an account. You cannot create a duplicated one.")}
if $sth->rows > 0;
# OK
return;
}
# _check_passwd: Check the user password
sub _check_passwd : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing a super-user
return if $self->{"iscur"} && !is_su && is_su $self->{"sn"};
# Set the passwords with the password registry
sync_saved_passwd($form, "*" x ${$self->{"maxlens"}}{"passwd"});
# Skip password checking for guests
return if $self->_is_guest;
# Check if it exists
$error = $self->_missing("passwd", "passwd2");
return $error if defined $error;
# Regularize it
$self->_trim("passwd", "passwd2");
# Check if it is filled
return {"msg"=>N_("Please fill in the password.")}
if !$self->{"iscur"} && $form->param("passwd") eq "";
return {"msg"=>N_("Please confirm the password.")}
if $form->param("passwd") ne "" && $form->param("passwd2") eq "";
# Check the length
return {"msg"=>N_("This password is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"passwd"}]}
if length $form->param("passwd") > ${$self->{"maxlens"}}{"passwd"};
return {"msg"=>N_("This password is too short. (Min. length [#,_1])"),
"margs"=>[${$self->{"minlens"}}{"passwd"}]}
if $form->param("passwd") ne ""
&& length $form->param("passwd") < ${$self->{"minlens"}}{"passwd"};
# Check if two passwords are consistent
return {"msg"=>N_("The 2 passwords are different. Please fill in the password again.")}
if $form->param("passwd") ne $form->param("passwd2");
if ($form->param("passwd") ne "") {
# Check the password strength with cracklib
if (($_ = fascist_check($form->param("passwd"))) ne "ok") {
# See the message from cracklib/fscist.c
# FascistGecos()
#return {"msg"=>N_("You are not registered.")}
# if $_ eq "you are not registered in the password file";
return {"msg"=>N_("This password is based on the user ID.")}
if $_ eq "it is based on your username";
#return {"msg"=>N_("This password is based upon the personal information.")}
# if $_ eq "it is based upon your password entry";
#return {"msg"=>N_("This password is derived from the personal information.")}
# if $_ eq "it is derived from your password entry"
# || $_ eq "it's derived from your password entry";
#return {"msg"=>N_("This password is derivable from the personal information.")}
# if $_ eq "it is derivable from your password entry"
# || $_ eq "it's derivable from your password entry";
# FascistLook()
#return {"msg"=>N_("This password is too short. (Min. length [#,_1])"),
# "margs"=>[${$self->{"minlens"}}{"passwd"}]}
# if $_ eq "it's WAY too short"
# || $_ eq "it is too short";
return {"msg"=>N_("This password does not contain enough different characters.")}
if $_ eq "it does not contain enough DIFFERENT characters";
#return {"msg"=>N_("This password is all whitespace.")}
# if $_ eq "it is all whitespace";
return {"msg"=>N_("This password is too simplistic/systematic.")}
if $_ eq "it is too simplistic/systematic";
#return {"msg"=>N_("This password looks like a National Insurance number.")}
# if $_ eq "it looks like a National Insurance number";
return {"msg"=>N_("This password is based on a dictionary word.")}
if $_ eq "it is based on a dictionary word";
return {"msg"=>N_("This password is based on a (reversed) dictionary word.")}
if $_ eq "it is based on a (reversed) dictionary word";
return {"msg"=>N_("This password is too simple.")};
}
return {"msg"=>$_}
if ($_ = fascist_check($form->param("passwd"))) ne "ok";
# Check if the group and the member are different
return {"msg"=>N_("You cannot use a password that is based on your user ID.")}
if defined($_ = $form->param("id"))
&& $form->param("passwd") =~ /$_/i;
}
# OK
return;
}
# _check_name: Check the user name
sub _check_name : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("name");
return $error if defined $error;
# Regularize it
$self->_trim("name");
# Check if it is filled
return {"msg"=>N_("Please fill in the name.")}
if $form->param("name") eq "";
# Check the length
return {"msg"=>N_("This name is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"name"}]}
if length $form->param("name") > ${$self->{"maxlens"}}{"name"};
# OK
return;
}
# _check_email: Check the user e-mail
sub _check_email : method {
local ($_, %_);
my ($self, $form, $error, $col);
($self, $col) = @_;
$form = $self->{"form"};
$col = "email" if !defined $col;
# Check if it exists
$error = $self->_missing($col);
return $error if defined $error;
# Regularize it
$self->_trim($col);
# Check if it is filled
return {"msg"=>N_("Please fill in the e-mail.")}
if $form->param($col) eq "";
# Check the length
return {"msg"=>N_("This e-mail is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{$col}]}
if length $form->param($col) > ${$self->{"maxlens"}}{$col};
return {"msg"=>N_("This e-mail is too short. (Min. length [#,_1])"),
"margs"=>[${$self->{"minlens"}}{$col}]}
if length $form->param($col) < ${$self->{"minlens"}}{$col};
# Check the e-mail validity
return {"msg"=>N_("Please fill in a valid e-mail address.")}
if !Email::Valid->rfc822($form->param($col));
return {"msg"=>N_("The domain of this e-mail does not exists. Check if there is any typo in it.")}
if !Email::Valid->mx($form->param($col));
# OK
return;
}
# _check_supgroup: Check the belonging groups
sub _check_supgroup : method {
local ($_, %_);
my ($self, $form, $error, %items);
$self = $_[0];
$form = $self->{"form"};
# Skip for a non-super-user editing herself
return if $self->{"iscur"} && !is_su && $self->{"sn"} == get_login_sn;
for (my $i = 0, %items = qw(); !$self->_missing("supgroup$i" . "sn"); $i++) {
my ($subform, $checker);
# Skip unselected ones
next if $self->_missing("supgroup$i");
# Regularize it
$self->_trim("supgroup$i" . "sn");
# Check if this selection is duplicated
return {"msg"=>N_("This belonging group is duplicated. You cannot set duplicated ones.")}
if exists $items{$form->param("supgroup$i" . "sn")};
$items{$form->param("supgroup$i" . "sn")} = 1;
# Check with the subform checker
$subform = new CGI("");
$subform->param("grp", $form->param("supgroup$i" . "sn"));
$subform->param("member", $self->{"sn"}) if $self->{"iscur"};
$checker = new Selima::Checker::UserMem($subform);
$error = $checker->check("grp");
return $error if defined $error;
# Check if a special group is submitted
$_ = groupid($form->param("supgroup$i" . "sn"));
return {"msg"=>N_("You cannot submit the super-user group along with other groups.")}
if $_ eq SU_GROUP;
return {"msg"=>N_("You cannot set the administrators group.")}
if $_ eq ADMIN_GROUP;
return {"msg"=>N_("You cannot set the all-users group.")}
if $_ eq ALLUSERS_GROUP;
}
# OK
return;
}
return 1;

View File

@@ -0,0 +1,123 @@
# Selima Website Content Management System
# UserMem.pm: The user-to-group membership form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-10
package Selima::Checker::UserMem;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::ChkFunc;
use Selima::CallForm;
use Selima::DataVars qw($DBH :forms);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my $class;
($class, @_) = @_;
$_[1] = "usermem" if scalar(@_) < 2 || !defined $_[1];
return $class->SUPER::new(@_);
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, $error, @cols);
($self, @cols) = @_;
# Run the parent method first
$error = $self->SUPER::check(@cols);
return $error if defined $error;
# See if we need to check the duplicates. Check it in the end.
%_ = map { $_ => 1 } @cols;
if (exists $_{"grp"} && exists $_{"member"}) {
$error = $self->__check_dup();
return $error if defined $error;
}
return;
}
# _check_grp: Check the group
# Use the default group checker
# _check_member: Check the member
sub _check_member : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("member");
return $error if defined $error;
# Regularize it
$self->_trim("member");
# Check if it is filled
return {"msg"=>N_("Please select a member.")}
if $form->param("member") eq "";
# Check if this user exists
return {"msg"=>N_("This member does not exist anymore. Please select another one.")}
if !check_sn_in ${$form->param_fetch("member")}[0], "users AS usrmembers";
# OK
return;
}
# __check_dup: Check if this item is duplicated
sub __check_dup : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
@_ = qw();
push @_, "grp=" . $form->param("grp");
push @_, "member=" . $form->param("member");
push @_, "sn!=" . $self->{"sn"}
if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This membership record already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
return;
}
# _redir_selmember: Suspend and move to the member selection form
sub _redir_selmember : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selmember");
call_form FORM_USERS, undef, "import_selmember";
}
# _redir_delmember: Remove the member
sub _redir_delmember : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("delmember");
$self->{"form"}->delete("member");
success_redirect undef;
}
return 1;

View File

@@ -0,0 +1,207 @@
# Selima Website Content Management System
# UserPref.pm: The user preference form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Checker::UserPref;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker);
use Selima::CallForm;
use Selima::DataVars qw($DBH :forms);
use Selima::ShortCut;
# new: Initialize the checker
sub new : method {
local ($_, %_);
my $class;
($class, @_) = @_;
$_[1] = "userpref" if scalar(@_) < 2 || !defined $_[1];
return $class->SUPER::new(@_);
}
# check: Run a list of checks
sub check : method {
local ($_, %_);
my ($self, $error, @cols);
($self, @cols) = @_;
# Run the parent method first
$error = $self->SUPER::check(@cols);
return $error if defined $error;
# See if we need to check the duplicates. Check it in the end.
%_ = map { $_ => 1 } @cols;
if (exists $_{"usr"} && exists $_{"domain"} && exists $_{"name"}) {
$error = $self->__check_dup();
return $error if defined $error;
}
return;
}
# _check_usr: Check the user
sub _check_usr : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# "everyone not set" has a different form context
return {"msg"=>N_("Please select the user.")}
if $self->_missing("everyone");
# Regularize it
$self->_trim("everyone");
# Check the option value
return {"msg"=>N_("This option is invalid. Please select a proper user.")}
unless $form->param("everyone") =~ /^(?:true|false)$/;
# Check the user if not everyone
if ($form->param("everyone") eq "false") {
$error = $self->SUPER::_check_usr;
return $error if defined $error;
}
# OK
return;
}
# _check_domain: Check the domain
sub _check_domain : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# "everywhere not set" has a different form context
return {"msg"=>N_("Please set the preference domain.")}
if $self->_missing("everywhere");
# Regularize it
$self->_trim("everywhere");
# Check the option value
return {"msg"=>N_("This option is invalid. Please set a proper preference domain.")}
unless $form->param("everywhere") =~ /^(?:true|false)$/;
# Check the domain if not everywhere
if ($form->param("everywhere") eq "false") {
# Check if it exists
$error = $self->_missing("domain");
return $error if defined $error;
# Regularize it
$self->_trim("domain");
# Check if it is filled
return {"msg"=>N_("Please fill in the preference domain.")}
if $form->param("domain") eq "";
# Check the length
return {"msg"=>N_("This preference domain is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"domain"}]}
if length $form->param("domain") > ${$self->{"maxlens"}}{"domain"};
}
# OK
return;
}
# _check_name: Check the preference name
sub _check_name : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("name");
return $error if defined $error;
# Regularize it
$self->_trim("name");
# Check if it is filled
return {"msg"=>N_("Please fill in the preference name.")}
if $form->param("name") eq "";
# Check the length
return {"msg"=>N_("This preference name is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"name"}]}
if length $form->param("name") > ${$self->{"maxlens"}}{"name"};
# OK
return;
}
# _check_value: Check the preference value
sub _check_value : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("value");
return $error if defined $error;
# Regularize it
$self->_trim("value");
# Check if it is filled
return {"msg"=>N_("Please fill in the preference value.")}
if $form->param("value") eq "";
# Check the length
return {"msg"=>N_("This preference value is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"value"}]}
if length $form->param("value") > ${$self->{"maxlens"}}{"value"};
# OK
return;
}
# __check_dup: Check if this item is duplicated
sub __check_dup : method {
local ($_, %_);
my ($self, $form, $error, $sth, $sql);
$self = $_[0];
$form = $self->{"form"};
@_ = qw();
if ($form->param("everyone") eq "true") {
push @_, "usr IS NULL";
} else {
push @_, "usr=" . $form->param("usr");
}
if ($form->param("everywhere") eq "true") {
push @_, "domain IS NULL";
} else {
push @_, "domain=" . $DBH->quote($form->param("domain"));
}
push @_, "name=" . $DBH->quote($form->param("name"));
push @_, "sn!=" . $self->{"sn"}
if $self->{"iscur"};
$sql = "SELECT * FROM " . $DBH->quote_identifier($self->{"table"})
. " WHERE " . join(" AND ", @_) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return {"msg"=>N_("This user preference already exists. You cannot create a duplicated one.")}
if $sth->rows > 0;
return;
}
# _redir_selusr: Suspend and move to the user selection form
sub _redir_selusr : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("selusr");
call_form FORM_USERS, undef, "import_selusr";
}
# _redir_delusr: Remove the user
sub _redir_delusr : method {
local ($_, %_);
my $self;
$self = $_[0];
# Skip if not requested
return if $self->_missing("delusr");
$self->{"form"}->delete("usr");
success_redirect undef;
}
return 1;

141
lib/perl5/Selima/ChkFunc.pm Normal file
View File

@@ -0,0 +1,141 @@
# Selima Website Content Management System
# ChkFunc.pm: The data checkers.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-24
package Selima::ChkFunc;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(check_sn check_sn_in check_script check_date);
push @EXPORT, qw(is_url_wellformed is_url_reachable);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub check_sn(\$);
sub check_sn_in(\$$);
sub check_script($);
sub check_date($$$);
sub is_url_wellformed($);
sub is_url_reachable($);
}
use LWP::UserAgent;
use Net::Telnet;
use Regexp::Common;
use Time::Local qw(timelocal);
use URI;
use Selima::Cache qw(:chkfunc);
use Selima::DataVars qw($DBH :input :requri);
use Selima::ShortCut;
use vars qw($URIRE);
$URIRE = "(?:" . $RE{"URI"} . "|" . $RE{"URI"}{"HTTP"}{-scheme=>"https"} . ")";
# check_sn: Check if a serial number is valid
# Rule for a serial number:
# An integer of 9 digits within 100000000 - 999999999
sub check_sn(\$) {
local ($_, %_);
$_ = $_[0];
return 0 unless defined $$_ && $$_ =~ /^[1-9][0-9]{8}$/;
$$_ += 0;
return 1;
}
# check_sn_in: Check if a serial number exists in a table
sub check_sn_in(\$$) {
local ($_, %_);
my ($sn, $table, $sql, $sth);
($sn, $table) = @_;
# Check the validity of the serial number first
return 0 if !check_sn $$sn;
if ($table =~ /^(.+) AS (.+)$/) {
$table = $DBH->quote_identifier($1)
. " AS " . $DBH->quote_identifier($2);
} else {
$table = $DBH->quote_identifier($table);
}
$sql = "SELECT * FROM $table WHERE sn=$$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return ($sth->rows == 1);
}
# check_script: Check if a script exists
sub check_script($) {
local ($_, %_);
$_ = $_[0];
# Return the cache
return $ChkFunc_check_script{$_}
if exists $ChkFunc_check_script{$_};
# Not a CGI script
return ($ChkFunc_check_script{$_} = 0)
unless /\.(cgi|pl|plx)$/;
# Not exists
return ($ChkFunc_check_script{$_} = 0)
unless -x $DOC_ROOT . $_;
# OK
return ($ChkFunc_check_script{$_} = 1);
}
# check_date: Check if a date is valid
sub check_date($$$) {
local ($_, %_);
my ($year, $month, $day);
($year, $month, $day) = @_;
eval { $_ = timelocal(0, 0, 0, $day, $month-1, $year-1900); };
return undef if $@ ne "";
return $_;
}
# is_url_wellformed: Check if an URL is well-formed
sub is_url_wellformed($) { $_[0] =~ /^$URIRE$/; }
# is_url_reachable: Check if the target of an URL is reachable
sub is_url_reachable($) {
local ($_, %_);
my ($uri, $UA, $r);
$_ = $_[0];
# Return the cache
return $ChkFunc_is_url_reachable{$_}
if exists $ChkFunc_is_url_reachable{$_};
# Check if it is available
# LWP::UserAgent cannot handle telnet. We check it with Net::Telnet.
if (/^telnet:\/\//) {
$uri = new URI($_);
%_ = (
Host => $uri->host,
Port => $uri->port,
);
eval { new Net::Telnet(%_) };
return ($ChkFunc_is_url_reachable{$_} = ($@ eq ""));
# Use LWP::UserAgent
} else {
$UA = new LWP::UserAgent;
$r = $UA->get($_);
return ($ChkFunc_is_url_reachable{$_} = !$r->is_error);
}
}
return 1;

133
lib/perl5/Selima/ChkPriv.pm Normal file
View File

@@ -0,0 +1,133 @@
# Selima Website Content Management System
# ChkPriv.pm: The privilege checkers.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-26
package Selima::ChkPriv;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(is_admin is_su user_parent_groups);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub is_admin(;$);
sub is_su(;$);
sub user_parent_groups($);
}
use Selima::Array;
use Selima::Cache qw(:chkpriv);
use Selima::ChkFunc;
use Selima::DataVars qw($DBH :groups);
use Selima::LogIn;
use Selima::UserName;
# is_admin: If the user is an administrator (by user sn)
sub is_admin(;$) {
local ($_, %_);
$_ = $_[0];
# Default to the current logged-in user
return is_su || in_array(ADMIN_GROUP, get_login_groups)
if !defined $_ || (defined get_login_sn && $_ == get_login_sn);
# Return the cache
return $ChkPriv_is_admin{$_} if exists $ChkPriv_is_admin{$_};
# Super user is always an administrator
return ($ChkPriv_is_admin{$_} = 1) if is_su($_);
# Check the groups
return ($ChkPriv_is_admin{$_} =
in_array(ADMIN_GROUP, user_parent_groups($_)));
}
# is_su: If the user is a super user
sub is_su(;$) {
local ($_, %_);
$_ = $_[0];
# Default to the current logged-in user
return in_array(SU_GROUP, get_login_groups)
if !defined $_ || (defined get_login_sn && $_ == get_login_sn);
# Return the cache
return $ChkPriv_is_su{$_} if exists $ChkPriv_is_su{$_};
# Check the groups
return ($ChkPriv_is_admin{$_} =
in_array(SU_GROUP, user_parent_groups($_)));
}
# user_parent_groups: Return the full list of groups a user belongs to
sub user_parent_groups($) {
local ($_, %_);
my ($sn, $sth, $sql, $count, %current, $group);
$sn = $_[0];
# Bounce for null
return if !defined $sn;
# Return the cache
return @{$ChkPriv_user_parent_groups{$sn}}
if exists $ChkPriv_user_parent_groups{$sn};
# Check the validity of the user first
if (defined get_login_sn && $sn != get_login_sn) {
if (!check_sn_in $sn, "users") {
$ChkPriv_user_parent_groups{$sn} = [];
return;
}
}
# Find the direct parent groups
$sql = "SELECT grp FROM usermem"
. " WHERE member=$sn"
. " ORDER BY grp;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
# Obtain the direct parent groups
for ($_ = 0, %current = qw(); $_ < $count; $_++) {
$current{${$sth->fetch}[0]} = 1;
}
# ALLUSERS_GROUP is automatically added to all the valid users
$current{groupsn(ALLUSERS_GROUP)} = 1;
# Trace all their ancester groups
while (1) {
$sql = "SELECT grp FROM groupmem"
. " WHERE " . join(" OR ", map "member=$_", keys %current)
. " GROUP BY grp ORDER BY grp;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, @_ = qw(); $_ < $count; $_++) {
push @_, ${$sth->fetch}[0];
}
@_ = grep !exists $current{$_}, @_;
last if scalar(@_) == 0;
$current{$_} = 1 foreach @_;
}
# Find their ID
$sql = "SELECT id FROM groups"
. " WHERE " . join(" OR ", map "sn=$_", keys %current)
. " ORDER BY id;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, @_ = qw(); $_ < $count; $_++) {
push @_, ${$sth->fetch}[0];
}
# Cache it
$ChkPriv_user_parent_groups{$sn} = [@_];
return @_;
}
return 1;

View File

@@ -0,0 +1,80 @@
# Selima Website Content Management System
# ChkWrite.pm: The write-permission checker.
# Copyright (c) 2005-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2005-02-28
package Selima::ChkWrite;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(check_writable);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub check_writable($);
}
use File::Basename qw(dirname);
use Selima::ShortCut;
# check_writable: Check the permission to write to a file.
# Input: File full pathname $file.
# Output: true if success, false if failed.
sub check_writable($) {
local ($_, %_);
my ($file, $parent);
$file = $_[0];
# Standardize it
# File exists.
if (-e $file) {
# If it is a file
return {"msg"=>N_("[_1]: It is not a file."),
"margs"=>[$file]}
if !-f $file;
# If it is writable
return {"msg"=>N_("[_1]: You have no permission to overwrite this file."),
"margs"=>[$file]}
if !-w $file;
# Not an existing file. See if we can create it.
} else {
$parent = $file;
# Find the nearest existing parent
$parent = dirname($parent)
while $parent ne "" && !-e $parent;
# Creat files from root --- You are insane
return {"msg"=>N_("[_1]: You cannot create anything under the root directory."),
"margs"=>[$file]}
if $parent eq "";
# This parent is not a directory
return {"msg"=>N_("[_1]: One of the parents of this file ([_2]) is not a directory. You cannot create any new file inside."),
"margs"=>[$file, $parent]}
if !-d $parent;
# If it is possible to create entries in this directory
return {"msg"=>N_("[_1]: You have no permission to create any file under [_2]."),
"margs"=>[$file, $parent]}
if !-w $parent;
}
# OK
return;
}
return 1;

View File

@@ -0,0 +1,42 @@
# Selima Website Content Management System
# CommText.pm: The core common text messages.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-03
package Selima::CommText;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(t_notset t_none t_na);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub t_notset();
sub t_none();
sub t_na();
}
use Selima::ShortCut;
sub t_notset() { C_("(not set)"); }
sub t_none() { C_("(none)"); }
sub t_na() { C_("(N/A)"); }
return 1;

View File

@@ -0,0 +1,51 @@
# Selima Website Content Management System
# CopyYear.pm: The copyright year text generator.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-17
package Selima::CopyYear;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(copyyear);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub copyyear($);
}
use Selima::Cache qw(:copyyear);
use Selima::DataVars qw(:proctime);
# copyyear: Return the copyright year
sub copyyear($) {
local ($_, %_);
my ($startyear, $thisyear);
$startyear = $_[0];
# Return the cache
return $CopyYear_copyyear if defined $CopyYear_copyyear;
$thisyear = (localtime $T_START)[5] + 1900;
$CopyYear_copyyear = $startyear;
$CopyYear_copyyear .= "-" . $thisyear
if $thisyear != $startyear;
return $CopyYear_copyyear;
}
return 1;

101
lib/perl5/Selima/Country.pm Normal file
View File

@@ -0,0 +1,101 @@
# Selima Website Content Management System
# Country.pm: The subroutines to query the country name from the database.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-17
package Selima::Country;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(ctname ctname_zhtw);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub ctname($);
sub ctname_zhtw($);
}
use Selima::Cache qw(:country);
use Selima::CommText;
use Selima::DataVars qw($DBH :lninfo);
use Selima::GetLang;
use Selima::LnInfo;
# The default language here is always English
use constant DEFAULT_LANG => "en";
use constant TRAD_CHINESE => "zh-tw";
# ctname: Obtain a country name
sub ctname($) {
local ($_, %_);
my ($id, $name, $col, $defcol, $sql, $sth);
$id = $_[0];
# Bounce if there is any problem with $id
return t_notset unless defined $id;
# Return the cache
return $Country_ctname{$id} if exists $Country_ctname{$id};
# Default language
if (getlang eq DEFAULT_LANG) {
$name = "name_" . getlang(LN_DATABASE) . " AS name";
# Fall back to the default language
} else {
$col = "name_" . getlang LN_DATABASE;
$defcol = "name_" . ln DEFAULT_LANG, LN_DATABASE;
$name= "COALESCE($col, $defcol) AS name";
}
# Query
$sql = "SELECT $name FROM country"
. " WHERE id=" . $DBH->quote($id) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Found
return ($Country_ctname{$id} = ${$sth->fetch}[0])
if $sth->rows == 1;
# Not found
return ($Country_ctname{$id} = t_na);
}
# ctname_zhtw: Obtain a country name in Traditional Chinese
sub ctname_zhtw($) {
local ($_, %_);
my ($id, $name, $col, $defcol, $sql, $sth);
$id = $_[0];
# Bounce if there is any problem with $id
return t_notset unless defined $id;
# Fall back to the default language
$col = "name_" . ln TRAD_CHINESE, LN_DATABASE;
$defcol = "name_" . ln DEFAULT_LANG, LN_DATABASE;
$name= "COALESCE($col, $defcol) AS name";
# Query
$sql = "SELECT $name FROM country"
. " WHERE id=" . $DBH->quote($id) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Found
return ${$sth->fetch}[0] if $sth->rows == 1;
# Not found
return t_na;
}
return 1;

454
lib/perl5/Selima/DBD/Pg.pm Normal file
View File

@@ -0,0 +1,454 @@
# Selima Website Content Management System
# Pg.pm: The extended PostgreSQL database driver.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-08
package Selima::DBD::Pg;
use 5.008;
use strict;
use warnings;
use MIME::Base64 qw(decode_base64);
use Term::ReadKey qw(ReadMode);
use Selima::DataVars qw(:db :env :siteconf :scptconf);
use Selima::DBILogin;
use Selima::HTTP;
#use vars qw($DBHC $PGDATABASE $PGHOST $PGPORT $PGUSER $PGPASSWORD);
use vars qw($DBHC);
# new: Connect and establish a new PostgreSQL database source
sub new : method {
local ($_, %_);
my ($class, $dbiclass, $dbh);
$class = ref($_[0]) || $_[0];
$dbiclass = (caller)[0];
# Login with from SQLLOGIN environment variable as a web application
if ($IS_CGI) {
my ($dsn, %r);
# Prepare the connection information
%r = get_dbi_login_info DBI_POSTGRESQL;
# Return the available cached handle and clear the cache
if (defined $DBHC && $DBHC->{"Name"} eq $r{"PGDATABASE"} && $DBHC->ping) {
$dbh = $DBHC;
# Clear the cache to remove static reference to the database handle,
# to avoid leaving dead handles that owns table locks
undef $DBHC;
return $dbh;
}
# Clear the cache
undef $DBHC if defined $DBHC;
# Compose the DSN
@_ = qw();
push @_, "host=" . $r{"PGHOST"} . ";" if defined $r{"PGHOST"};
push @_, "dbname=" . $r{"PGDATABASE"} . ";" if defined $r{"PGDATABASE"};
$dsn = "dbi:Pg:" . join "", @_;
# Try to log in, handling the failure later
%_ = ( "PrintError" => 0 );
$dbh = $dbiclass->connect($dsn, $r{"PGUSER"}, $r{"PGPASSWORD"}, {%_});
# Login failed
http_500 $dbiclass->errstr if !defined $dbh;
# Ask the password from the console
} else {
my ($dsn, $subseq, $user, $passwd);
$dsn = "dbi:Pg:dbname=$PACKAGE;";
$subseq = 0;
# Try to log in
while (!defined($dbh = $dbiclass->connect($dsn, $user, $passwd, { PrintError => 0 }))) {
$_ = DBI->errstr;
if ($subseq) {
print STDERR $_;
sleep 5;
}
$subseq = 1;
# Obtain the current login user
$user = $1 if !defined $user && / failed for user "(.+?)"/;
# Disable console echo
ReadMode 2;
print STDERR defined $user? "PostgreSQL password for $user: ":
"PostgreSQL password: ";
$passwd = <STDIN>;
print STDERR "\n";
die "$THIS_FILE: Failed connecting to the PostgreSQL server\n"
if !defined $passwd;
chomp $passwd;
# Restore console echo status
ReadMode 0;
}
}
# Bless the object, name it as the current class
$dbh = bless $dbh, $dbiclass . "::db";
# Set the client encoding to UTF-8
$_ = "SET NAMES 'utf8';\n";
$dbh->do($_);
return $dbh;
}
# park_handle: Suspend the database handle for further use (mod_perl)
sub park_handle : method { $DBHC = $_[1]; }
# Selima::DBD::Pg::db: The database-handler driver class
package Selima::DBD::Pg::db;
use 5.008;
use strict;
use warnings;
use Fcntl qw(:flock);
use Selima::GetLang;
use Selima::HTTP;
use Selima::DataVars qw(:db :lninfo);
# support: Return if a DBI feature is supported
sub support : method {
local ($_, %_);
my ($self, $feature);
($self, $feature) = @_;
# PostgreSQL has VIEWs.
return 1 if $feature eq DBI_FEATHER_VIEW;
# Default to yes. We assume everyone is a good guy.
return 1;
}
# lock: PostgreSQL table-locking handler
# PostgreSQL has no unlock
# Input:
# %locks: A hash table, where its keys are the tables to lock,
# and its values can be one of the following:
# LOCK_SH: Request a read lock
# LOCK_EX: Request a write lock
# LOCK_UN: No effect
# Return: None. Errors are directed to error handlers
sub lock : method {
local ($_, %_);
my ($self, %locks, @reads, @writes, $sth);
($self, %locks) = @_;
# Bounce for nothing
return if scalar(keys %locks) == 0;
# Remove the table aliases -- compatibility with stupid MySQL
%_ = qw();
foreach my $table (keys %locks) {
# Remove the table aliases
$_ = $table;
s/\s+AS\s+.+?$//i;
# No override previous write lock
next if exists $_{$_} && $_{$_} == LOCK_EX;
# Set the lock
$_{$_} = $locks{$table};
}
%locks = %_;
# Split into different lock modes
@reads = qw();
@writes = qw();
foreach (keys %locks) {
if ($locks{$_} == LOCK_SH) {
push @reads, $_;
} elsif ($locks{$_} == LOCK_EX) {
push @writes, $_;
} else {
http_500 "Bad SQL lock request: \"" . $locks{$_} . "\""
. " on table \"$_\".";
}
}
# Start the transaction
$self->begin_work if $self->{"AutoCommit"};
# Request the locks
if (@reads > 0) {
$_ = "LOCK TABLE " . join(", ", @reads)
. " IN SHARE MODE;\n";
$self->do($_);
}
if (@writes > 0) {
$_ = "LOCK TABLE " . join(", ", @writes)
. " IN ACCESS EXCLUSIVE MODE;\n";
$self->do($_);
}
return;
}
# tables: Return the tables and views
sub tables : method {
local ($_, %_);
my ($self, $schema, $cache, $sth, @tables);
($self, $schema) = @_;
# Default schema
$schema = $self->current_schema if !defined $schema;
# Initialize the cache
${$self->{"private_selima"}}{"tables"} = {}
if !exists ${$self->{"private_selima"}}{"tables"};
$cache = ${$self->{"private_selima"}}{"tables"};
# Return the cache
return @{${$cache}{$schema}} if exists ${$cache}{$schema};
# Get the tables list
$sth = $self->table_info(undef, $schema, "%", "%")
or http_500 $self->errstr;
@tables = qw();
push @tables, ${$_}{"TABLE_NAME"}
while defined($_ = $sth->fetchrow_hashref);
# Cache it
${$cache}{$schema} = [@tables];
return @tables;
}
# cols: Return the columns of a table (or view)
sub cols : method {
local ($_, %_);
my ($self, $table, $schema, $cache, $sth, @cols);
($self, $table, $schema) = @_;
# Default schema
$schema = $self->current_schema if !defined $schema;
# Initialize the cache
${$self->{"private_selima"}}{"cols"} = {}
if !exists ${$self->{"private_selima"}}{"cols"};
${${$self->{"private_selima"}}{"cols"}}{$schema} = {}
if !exists ${${$self->{"private_selima"}}{"cols"}}{$schema};
$cache = ${${$self->{"private_selima"}}{"cols"}}{$schema};
# Return the cache
return @{${$cache}{$table}} if exists ${$cache}{$table};
# Get the columns list
$sth = $self->column_info(undef, $schema, $table, "%")
or http_500 $self->errstr;
@cols = qw();
push @cols, ${$_}{"COLUMN_NAME"}
while defined($_ = $sth->fetchrow_hashref);
s/^"(.+)"/$1/ foreach @cols;
# Cache it
${$cache}{$table} = [@cols];
return @cols;
}
# col_lens: Obtain the column lengths of a table
sub col_lens : method {
local ($_, %_);
my ($self, $table, $schema, $cache, $sth, $sql, $count, %lens, $lndb);
($self, $table, $schema) = @_;
# Default schema
$schema = $self->current_schema if !defined $schema;
# Initialize the cache
${$self->{"private_selima"}}{"col_lens"} = {}
if !exists ${$self->{"private_selima"}}{"col_lens"};
${${$self->{"private_selima"}}{"col_lens"}}{$schema} = {}
if !exists ${${$self->{"private_selima"}}{"col_lens"}}{$schema};
$cache = ${${$self->{"private_selima"}}{"col_lens"}}{$schema};
# Return the cache
return %{${$cache}{$table}} if exists ${$cache}{$table};
# Query
$sql = "SELECT pg_attribute.attname AS col,"
. " pg_type.typname AS type,"
. " pg_attribute.attlen AS len,"
. " pg_attribute.atttypmod AS typmod"
. " FROM pg_attribute"
. " INNER JOIN pg_class ON pg_attribute.attrelid=pg_class.oid"
. " INNER JOIN pg_type ON pg_attribute.atttypid=pg_type.oid"
. " INNER JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid"
. " WHERE pg_namespace.nspname=" . $self->quote($schema)
. " AND pg_class.relname=" . $self->quote($table)
. " AND pg_class.relkind='r'"
. " AND pg_attribute.attnum>0"
. " ORDER BY pg_attribute.attnum;\n";
$sth = $self->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, %lens = qw(); $i < $count; $i++) {
%_ = %{$sth->fetchrow_hashref};
# Integer -- Digits of the largest number - 1
if ($_{"type"} =~ /^int[248]$/) {
$lens{$_{"col"}} = int(log(256**$_{"len"})/log 10);
# Refer to typmod for char and varchar
} elsif ($_{"type"} =~ /^(?:var|bp)char$/) {
$lens{$_{"col"}} = $_{"typmod"} - 4;
# Set text and bytea to 4294967296 (2^32) (infinite actually)
} elsif ($_{"type"} =~ /^(?:text|bytea)$/) {
$lens{$_{"col"}} = 4294967296;
# Set timestamp to 19
} elsif ($_{"type"} eq "timestamp" || $_{"type"} eq "timestamptz") {
$lens{$_{"col"}} = 19;
# Set date to 10
} elsif ($_{"type"} eq "date") {
$lens{$_{"col"}} = 10;
# Set time to 8
} elsif ($_{"type"} eq "time") {
$lens{$_{"col"}} = 8;
# Set numeric to precision + 1 decimal point
# Refer to http://archives.postgresql.org/pgsql-hackers/1999-01/msg00127.php
} elsif ($_{"type"} eq "numeric") {
my ($typmod, $scale, $precision);
$typmod = $_{"typmod"} - 4;
$scale = $typmod & 0xFFFF;
$precision = $typmod >> 16;
$lens{$_{"col"}} = $precision + 1;
# Set boolean to 1
} elsif ($_{"type"} eq "bool") {
$lens{$_{"col"}} = 1;
# Set inet to 18 (nnn.nnn.nnn.nnn/nn)
} elsif ($_{"type"} eq "inet") {
$lens{$_{"col"}} = 18;
# Bounce for unknown columns
} else {
http_500 "Unknown column type " . $_{"type"}
. " for table $table.\n";
}
}
# Hash the multi-lingual columns
$lndb = getlang(LN_DATABASE);
$lens{$_} = $lens{$_ . "_$lndb"} foreach $self->cols_ml($table);
# Cache it
${$cache}{$table} = {%lens};
return %lens;
}
# quote_blob: Quote a piece of BLOB octet
sub quote_blob : method {
local ($_, %_);
my ($self, $octet, $sth, $sql);
($self, $octet) = @_;
$sql = "SELECT ?;\n";
$sth = $self->prepare($sql);
$sth->bind_param(1, $octet, { pg_type => DBD::Pg::PG_BYTEA() })
or http_500 $sql . $sth->errstr;
$sth->execute;
return "'" . ${$sth->fetch}[0] . "'";
}
# strcat: Concatenate strings
sub strcat : method {
local ($_, %_);
my ($self, @strings);
($self, @strings) = @_;
return join " || ", @strings;
}
# lastupd: Obtain the last updated time of a list of tables
sub lastupd : method {
local ($_, %_);
my ($self, @tables, $sql, $sth);
($self, @tables) = @_;
# Bounce if no tables supplied
return if scalar(@tables) == 0;
# Remove table aliases
s/^(\S+) AS \S+$/$1/ foreach @tables;
# Remove duplicates
%_ = map { $_ => 1 } @tables;
@tables = keys %_;
# Query
$sql = "SELECT mtime FROM mtime"
. " WHERE " . join(" OR ", map "tabname=" . $self->quote($_), @tables)
. " ORDER BY mtime DESC LIMIT 1;\n";
$sth = $self->prepare($sql);
$sth->execute;
# Bounce if no data found
return if $sth->rows != 1;
# Return the result
return ${$sth->fetchrow_hashref}{"mtime"};
}
# current_schema: Obtain the current schema
sub current_schema : method {
local ($_, %_);
my ($self, $sth, $sql);
$self = $_[0];
# Return the cache
return ${$self->{"private_selima"}}{"current_schema"}
if exists ${$self->{"private_selima"}}{"current_schema"};
$sql = "SELECT current_schema();\n";
$sth = $self->prepare($sql);
$sth->execute;
$_ = ${$sth->fetch}[0];
# Cache it
${$self->{"private_selima"}}{"current_schema"} = $_;
return $_;
}
# Selima::DBD::Pg::st: The statement-handler driver class
package Selima::DBD::Pg::st;
use 5.008;
use strict;
use warnings;
# typecols: Return the list of columns in specific types
sub typecols : method {
local ($_, %_);
my ($self, $types, %cols);
$self = $_[0];
$types = $self->{"pg_type"};
%cols = (
"date" => [],
"num" => [],
"bigint" => [],
"numeric" => [],
"text" => [],
);
for ($_ = 0; $_ < @$types; $_++) {
if ($$types[$_] =~ /^(?:date|timestamp)$/) {
push @{$cols{"date"}}, $_;
} elsif ($$types[$_] =~ /^(?:int2|int4|float4|float8)$/) {
push @{$cols{"num"}}, $_;
} elsif ($$types[$_] eq "int8") {
push @{$cols{"bigint"}}, $_;
} elsif ($$types[$_] eq "numeric") {
push @{$cols{"numeric"}}, $_;
} elsif ($$types[$_] =~ /^(?:varchar|text)$/) {
push @{$cols{"text"}}, $_;
}
}
return \%cols;
}
return 1;

View File

@@ -0,0 +1,301 @@
# Selima Website Content Management System
# mysql.pm: The extended MySQL database driver.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-23
package Selima::DBD::mysql;
use 5.008;
use strict;
use warnings;
use MIME::Base64 qw(decode_base64);
use Term::ReadKey qw(ReadMode);
use Selima::DataVars qw(:db :env :siteconf :scptconf);
use Selima::DBILogin;
use Selima::HTTP;
#use vars qw(%DBH $MYSQL_HOST $MYSQL_TCP_PORT $MYSQL_UNIX_PORT $MYSQL_DB $MYSQL_USER $MYSQL_PWD);
use vars qw(%DBH);
# new: Connect and establish a new MySQL database source
sub new : method {
local ($_, %_);
my ($class, $dbiclass, $dbh);
$class = ref($_[0]) || $_[0];
# Login with from SQLLOGIN environment variable as a web application
if ($IS_CGI) {
my ($dsn, %r);
# Prepare the connection information
%r = get_dbi_login_info DBI_MYSQL;
# Compose the DSN
@_ = qw();
push @_, "host=" . $r{"MYSQL_HOST"} . ";" if defined $r{"MYSQL_HOST"};
$dsn = "dbi:mysql:" . join "", @_;
# Return the available cached handle and clear the cache
if (exists $DBH{$dsn} && $DBH{$dsn}->ping) {
# Obtain the cached database handle
$dbh = $DBH{$dsn};
# Clear the cache to remove static reference to the database handle,
# to avoid leaving dead handles that owns table locks
delete $DBH{$dsn};
# New connection
} else {
$dbiclass = (caller)[0];
# Try to log in, handling the failure later
%_ = ( "PrintError" => 0 );
$dbh = $dbiclass->connect($dsn, $r{"MYSQL_USER"}, $r{"MYSQL_PWD"}, {%_});
# Login failed
http_500 $dbiclass->errstr if !defined $dbh;
# Bless the object, name it as the current class
$dbh = bless $dbh, $dbiclass . "::db";
# Set the client encoding to UTF-8
$_ = "SET NAMES 'utf8';\n";
$dbh->do($_);
}
# Set the database
$_ = "USE " . $r{"MYSQL_DB"} . ";\n";
$dbh->do($_);
# Ask the password from the console
} else {
my ($dsn, $subseq, $user, $passwd);
$dsn = "dbi:mysql:database=$PACKAGE;";
$subseq = 0;
# Try to log in
while (!defined($dbh = DBI->connect($dsn, $user, $passwd, { PrintError => 0 }))) {
$_ = DBI->errstr;
if ($subseq) {
print STDERR "$_\n";
sleep 5;
}
$subseq = 1;
# Obtain the current login user
$user = $1 if !defined $user && / denied for user: '(.+?)\@.+?'/;
# Disable console echo
ReadMode 2;
print STDERR defined $user? "MySQL password for $user: ":
"MySQL password: ";
$passwd = <STDIN>;
print STDERR "\n";
die "$THIS_FILE: Failed connecting to the MySQL server\n"
if !defined $passwd;
chomp $passwd;
# Restore console echo status
ReadMode 0;
}
}
return $dbh;
}
# park_handle: Suspend the database handle for further use (mod_perl)
sub park_handle : method { $DBH{"dbi:mysql:" . $_[1]->{"Name"}} = $_[1]; }
# Selima::DBD::mysql::db: The database-handler driver class
package Selima::DBD::mysql::db;
use 5.008;
use strict;
use warnings;
use Fcntl qw(:flock);
use Selima::GetLang;
use Selima::HTTP;
use Selima::DataVars qw(:db :lninfo);
# support: Return if a DBI feature is supported
sub support : method {
local ($_, %_);
my ($self, $feature);
($self, $feature) = @_;
# MySQL has VIEW since 5.0
return 1 if $feature eq DBI_FEATHER_VIEW;
# Default to yes. We assume everyone is a good guy.
return 1;
}
# tables: Return the tables
sub tables : method {
local ($_, %_);
my ($self, $sth, $cache, @tables);
$self = $_[0];
# Initialize the cache
$cache = $self->{"private_selima"};
# Return the cache
return @{${$cache}{"tables"}} if exists ${$cache}{"tables"};
# Get the tables list
$sth = $self->table_info(undef, undef, "%", undef)
or http_500 $self->errstr;
@tables = qw();
push @tables, $$_{"TABLE_NAME"}
while defined($_ = $sth->fetchrow_hashref);
# Cache it
${$cache}{"tables"} = [@tables];
return @tables;
}
# cols: Return the columns of a table
sub cols : method {
local ($_, %_);
my ($self, $table, $cache, $sth, @cols);
($self, $table) = @_;
# Initialize the cache
${$self->{"private_selima"}}{"cols"} = {}
if !exists ${$self->{"private_selima"}}{"cols"};
$cache = ${$self->{"private_selima"}}{"cols"};
# Return the cache
return @{${$cache}{$table}} if exists ${$cache}{$table};
# Get the columns list
$sth = $self->column_info(undef, undef, $table, "%")
or http_500 $self->errstr;
@cols = qw();
push @cols, $_ while defined($_ = $sth->fetchrow_hashref);
@cols = map $$_{"COLUMN_NAME"},
sort { ${$a}{"ORDINAL_POSITION"} <=> ${$b}{"ORDINAL_POSITION"} } @cols;
# Cache it
${$cache}{$table} = [@cols];
return @cols;
}
# col_lens: Obtain the column lengths of a table
sub col_lens : method {
local ($_, %_);
my ($self, $table, $cache, $sth, $sql, $count, %lens, $lndb);
($self, $table) = @_;
# Initialize the cache
${$self->{"private_selima"}}{"col_lens"} = {}
if !exists ${$self->{"private_selima"}}{"col_lens"};
$cache = ${$self->{"private_selima"}}{"col_lens"};
# Return the cache
return %{${$cache}{$table}} if exists ${$cache}{$table};
# Use column_info here
$sth = $self->column_info(undef, undef, $table, "%")
or http_500 $self->errstr;
%_ = qw();
$_{$$_{"COLUMN_NAME"}} = $$_{"COLUMN_SIZE"}
while defined($_ = $sth->fetchrow_hashref);
# Hash the multi-lingual columns
$lndb = getlang(LN_DATABASE);
$lens{$_} = $lens{$_ . "_$lndb"} foreach $self->cols_ml($table);
# Cache it
${$cache}{"tables"} = {%_};
return %_;
}
# quote_blob: Quote a piece of BLOB octet
sub quote_blob : method {
local ($_, %_);
my ($self, $octet);
($self, $octet) = @_;
return $self->quote($octet);
}
# strcat: Concatenate strings
sub strcat : method {
local ($_, %_);
my ($self, @strings);
($self, @strings) = @_;
return "CONCAT(" . join(", ", @strings) . ")";
}
# lastupd: Obtain the last updated time of a list of tables
sub lastupd : method {
local ($_, %_);
my ($self, @tables, $sql, $sth);
($self, @tables) = @_;
# Bounce if no tables supplied
return if scalar(@tables) == 0;
# Remove duplicates
%_ = map { $_ => 1 } @tables;
@tables = keys %_;
# Query
$sql = "SELECT mtime FROM mtime"
. " WHERE " . join(" OR ", map "tabname=" . $self->quote($_), @tables)
. " ORDER BY mtime DESC LIMIT 1;\n";
$sth = $self->prepare($sql);
$sth->execute;
# Bounce if no data found
return if $sth->rows != 1;
# Return the result
return ${$sth->fetchrow_hashref}{"mtime"};
}
# Selima::DBD::mysql::st: The statement-handler driver class
package Selima::DBD::mysql::st;
use 5.008;
use strict;
use warnings;
# typecols: Return the list of columns in specific types
sub typecols : method {
local ($_, %_);
my ($self, $types, %cols);
$self = $_[0];
$types = $self->{"mysql_type_name"};
%cols = (
"date" => [],
"num" => [],
"bigint" => [],
"numeric" => [],
"text" => [],
);
for ($_ = 0; $_ < @$types; $_++) {
if ($$types[$_] =~ /^(?:date|datetime|timestamp)$/) {
push @{$cols{"date"}}, $_;
} elsif ($$types[$_] =~ /^(?:tinyint|smallint|middleint|integer|float|double)$/) {
push @{$cols{"num"}}, $_;
} elsif ($$types[$_] eq "bigint") {
push @{$cols{"bigint"}}, $_;
} elsif ($$types[$_] eq "decimal") {
push @{$cols{"numeric"}}, $_;
} elsif ($$types[$_] =~ /^(?:varchar|blob)$/) {
push @{$cols{"text"}}, $_;
}
}
return \%cols;
}
return 1;

440
lib/perl5/Selima/DBI.pm Normal file
View File

@@ -0,0 +1,440 @@
# Selima Website Content Management System
# DBI.pm: The extended DBI (database interface).
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-09
package Selima::DBI;
use 5.008;
use strict;
use warnings;
use base qw(DBI);
use Selima::DataVars qw(:db);
# new: Connect and establish a new database source
sub new : method {
local ($_, %_);
my ($type, $dbh, $class, $methods);
$type = $_[1];
# PostgreSQL
if ($type eq DBI_POSTGRESQL) {
require Selima::DBD::Pg;
$dbh = Selima::DBD::Pg->new;
# MySQL
} elsif ($type eq DBI_MYSQL) {
require Selima::DBD::mysql;
$dbh = Selima::DBD::mysql->new;
}
# Keep the imported methods for cached DBH (mod_perl)
undef $methods;
$methods = ${$dbh->{"private_selima"}}{"methods"}
if exists $dbh->{"private_selima"}
&& exists ${$dbh->{"private_selima"}}{"methods"};
# Initialize the private attributes
$dbh->{"private_selima"} = {};
# Import the methods
if (defined $methods) {
${$dbh->{"private_selima"}}{"methods"} = $methods ;
} else {
$dbh->import_methods;
}
return $dbh;
}
# Selima::DBI::db: The database-handler class
package Selima::DBI::db;
use 5.008;
use strict;
use warnings;
use base qw(DBI::db);
use vars qw($METHODS_DEFINED @IMPORT_METHODS);
$METHODS_DEFINED = 0;
@IMPORT_METHODS = qw(support lock tables cols col_lens quote_blob
strcat lastupd current_schema);
use Encode qw(encode);
use Selima::DataVars qw(:env :lninfo);
use Selima::HTTP;
use Selima::GetLang;
use Selima::Guest;
# import_methods: Import our own methods
sub import_methods : method {
local ($_, %_);
my ($self, $methods, $class, $driver);
$self = $_[0];
# Initialize the methods pool and the class and driver name
${$self->{"private_selima"}}{"methods"} = {};
$class = ref($self);
$driver = $class;
$driver =~ s/::DBI::db$//;
$driver .= "::DBD::" . $self->{"Driver"}->{"Name"} . "::db";
# Define the methods once
if (!$METHODS_DEFINED) {
# Short-cut to the methods pool
$methods = "\${\$_[0]->{\"private_selima\"}}{\"methods\"}";
foreach (@IMPORT_METHODS) {
eval << "EOT";
*$_ = sub { return \&{\${$methods}{"$_"}}(\@_); }
EOT
}
$METHODS_DEFINED = 1;
}
# Short-cut to the methods pool
$methods = ${$self->{"private_selima"}}{"methods"};
# Import each method
foreach my $func (@IMPORT_METHODS) {
if (defined($_ = $driver->can($func))) {
${$methods}{$func} = $_;
} elsif (defined($_ = $class->can("SUPER::$func"))) {
${$methods}{$func} = $_;
} else {
${$methods}{$func} = sub {};
}
}
return;
}
# do: run SUPER::do() and handle errors
sub do : method {
local ($_, %_);
my ($self, $sql, $rv);
($self, $sql, @_) = @_;
# $sql should always be a decoded text
$sql = encode("UTF-8", $sql);
# Run and handle errors
$rv = $self->SUPER::do($sql, @_)
or http_500 $sql . $self->errstr;
# Update the mtime
if ($sql =~ /^(?:INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+(\S+)/i) {
my ($table, $sql, $sth);
$table = $1;
$table =~ s/^"(.+?)"$/$1/;
$table = $self->quote($table);
$sql = "SELECT * FROM mtime WHERE tabname=$table;\n";
$sth = $self->prepare($sql);
$sth->execute;
# Found
if ($sth->rows == 1) {
# Update the mtime
$sql = "UPDATE mtime SET mtime=now() WHERE tabname=$table;\n";
$self->SUPER::do($sql);
# Not found
} else {
# Set the mtime
$sql = "INSERT INTO mtime (tabname, mtime) VALUES ($table, now());\n";
$self->SUPER::do($sql);
}
}
return $rv;
}
# gdo: only run do() when user is not a guest
sub gdo : method {
local ($_, %_);
my $self;
($self, @_) = @_;
# Skip for guests
return 1 if is_guest;
return $self->do(@_);
}
# prepare: run SUPER::prepare, handle errors and import our extension methods
sub prepare : method {
local ($_, %_);
my ($self, $sql, $sth);
($self, $sql, @_) = @_;
# Run and handle errors
$sth = $self->SUPER::prepare($sql, @_)
or http_500 $sql . $self->errstr;
# Import our extension methods
$sth->{"private_selima"} = {};
$sth->import_methods;
return $sth;
}
# begin_work, commit, rollback do not need to handle their errors.
# errors can be silently ignored. See DBI(3) for more details.
#
# Methods below are driver-indepedent. Override is not required.
#
# cols_ml: Return the multi-lingual columns list of a table (or view)
sub cols_ml : method {
local ($_, %_);
my ($self, $table, $sth, @cols, @cols_ml, $suf);
($self, $table) = @_;
$self->{"private_cols_ml"} = {}
if !exists $self->{"private_cols_ml"};
# Return the cache
return @{${$self->{"private_cols_ml"}}{$table}}
if exists ${$self->{"private_cols_ml"}}{$table};
@cols = $self->cols($table);
@cols_ml = qw();
$suf = "_" . getlang LN_DATABASE;
foreach (@cols) {
push @cols_ml, $_ if $_ =~ s/$suf$//;
}
# Cache it
${$self->{"private_cols_ml"}}{$table} = [@cols_ml];
return @cols_ml;
}
# is_ml_table: Check if a table is multi-lingual
sub is_ml_table : method {
local ($_, %_);
my ($self, $table);
($self, $table) = @_;
return scalar($self->cols_ml($table)) > 0;
}
# esclike: Escape a phrase by the LIKE matching rule
# Double quote should never be used, according to
# the column name rules in the SQL standard.
sub esclike : method {
local ($_, %_);
my $self;
($self, $_) = @_;
s/\\/\\\\\\\\/g;
s/%/\\\\%/g;
s/_/\\\\_/g;
# By the SQL standard
s/'/''/g; # ' gettext
# Non-standard, but this also works for most SQL DBMS
#s/'/\\\\\\'/g;
return $_;
}
# disconnect: Disconnect from the database server
sub disconnect : method {
local ($_, %_);
my ($self, $class);
$self = $_[0];
$class = ref($self);
$class =~ s/::DBI::db$//;
$class .= "::DBD::" . $self->{"Driver"}->{"Name"};
# Rollback the changes that are not committed and unlock the tables
$self->rollback if !$self->{"AutoCommit"};
# mod_perl: Suspend the database handle for further use, but not
# really disconnect it.
# Disabled. Save system from server load too high.
#if ($IS_MODPERL) {
# return $class->park_handle($self);
#} else {
$_ = $self->SUPER::disconnect or http_500 $self->errstr;
return $_;
#}
}
# Selima::DBI::st: The statement-handler class
package Selima::DBI::st;
use 5.008;
use strict;
use warnings;
use base qw(DBI::st);
use vars qw($METHODS_DEFINED @IMPORT_METHODS);
$METHODS_DEFINED = 0;
@IMPORT_METHODS = qw(typecols);
use DBI qw(:sql_types);
use Encode qw(decode_utf8 FB_CROAK is_utf8);
use Date::Parse qw(str2time);
use Selima::HTTP;
# import_methods: Import our own methods
sub import_methods : method {
local ($_, %_);
my ($self, $methods, $class, $driver);
$self = $_[0];
# Initialize the methods pool and the class and driver name
${$self->{"private_selima"}}{"methods"} = {};
$class = ref($self);
$driver = $class;
$driver =~ s/::DBI::st$//;
$driver .= "::DBD::" . $self->{"Database"}->{"Driver"}->{"Name"} . "::st";
# Define the methods once
if (!$METHODS_DEFINED) {
# Short-cut to the methods pool
$methods = "\${\$_[0]->{\"private_selima\"}}{\"methods\"}";
foreach (@IMPORT_METHODS) {
eval << "EOT";
*$_ = sub { return \&{\${$methods}{"$_"}}(\@_); }
EOT
}
$METHODS_DEFINED = 1;
}
# Short-cut to the methods pool
$methods = ${$self->{"private_selima"}}{"methods"};
# Import each method
foreach my $func (@IMPORT_METHODS) {
if (defined($_ = $driver->can($func))) {
${$methods}{$func} = $_;
} elsif (defined($_ = $class->can("SUPER::$func"))) {
${$methods}{$func} = $_;
} else {
${$methods}{$func} = sub {};
}
}
return;
}
# execute: run SUPER::execute and handle errors
sub execute : method {
local ($_, %_);
my $self;
($self, @_) = @_;
# Run and handle errors
$_ = $self->SUPER::execute(@_)
or http_500 $self->{"Statement"} . $self->errstr;
return $_;
}
# fetch: fetch and decode from UTF-8
# DBI::st returns a same array reference each time, with
# only the values changed. Then, after the first fetch,
# the values returned are all tagged as "wide characters"
# (decoded) and cannot be decode()ed again. To avoid this
# problem, we make a different copy of the returned values
# and decode that copy, instead of decoding those in the
# original array reference returned.
sub fetch : method {
local ($_, %_);
my ($self, @args, $row, $types);
($self, @args) = @_;
# Fetch the row first
$row = $self->SUPER::fetch(@args);
# No record found or some error occurs
return undef if !defined $row;
# Not called from within fetchrow_hashref()
if (exists $self->{"TYPE"}) {
# Make a copy of the record
$row = [@$row];
# Obtain the type classes
${$self->{"private_selima"}}{"types"} = $self->typecols
if !exists ${$self->{"private_selima"}}{"types"};
$types = ${$self->{"private_selima"}}{"types"};
# Convert the date/datetime columns
foreach (@{$$types{"date"}}) {
$$row[$_] = str2time $$row[$_]
if defined $$row[$_];
}
# Convert the numeric columns
foreach (@{$$types{"num"}}) {
$$row[$_] = $$row[$_] + 0
if defined $$row[$_];
}
# Decode the text columns
foreach (@{$$types{"text"}}) {
$$row[$_] = decode_utf8($$row[$_], FB_CROAK)
if defined $$row[$_] && !is_utf8($$row[$_]);
}
}
return $row;
}
# fetchrow_hashref: fetch and read some fields
sub fetchrow_hashref : method {
local ($_, %_);
my ($self, @args, $row, $types);
($self, @args) = @_;
# Fetch the row first
$row = $self->SUPER::fetchrow_hashref(@args);
# No record found or some error occurs
return undef if !defined $row;
# Make a copy of the record
$row = {%$row};
# Obtain the type classes
${$self->{"private_selima"}}{"types"} = $self->typecols
if !exists ${$self->{"private_selima"}}{"types"};
$types = ${$self->{"private_selima"}}{"types"};
# Convert the date/datetime columns
foreach (@{$$types{"date"}}) {
$$row{${$self->{"NAME"}}[$_]} = str2time $$row{${$self->{"NAME"}}[$_]}
if defined $$row{${$self->{"NAME"}}[$_]};
}
# Convert the numeric columns
foreach (@{$$types{"num"}}) {
$$row{${$self->{"NAME"}}[$_]} = $$row{${$self->{"NAME"}}[$_]} + 0
if defined $$row{${$self->{"NAME"}}[$_]};
}
# Decode the text columns
foreach (@{$$types{"text"}}) {
$$row{${$self->{"NAME"}}[$_]} = decode_utf8($$row{${$self->{"NAME"}}[$_]}, FB_CROAK)
if defined $$row{${$self->{"NAME"}}[$_]} && !is_utf8($$row{${$self->{"NAME"}}[$_]});
}
return $row;
}
# fetchrow_arrayref: fetch and read some fields
sub fetchrow_arrayref : method {
local ($_, %_);
my ($self, @args, $row, $types);
($self, @args) = @_;
# Fetch the row first
$row = $self->SUPER::fetchrow_arrayref(@args);
# No record found or some error occurs
return undef if !defined $row;
# Make a copy of the record
$row = [@$row];
# Obtain the type classes
${$self->{"private_selima"}}{"types"} = $self->typecols
if !exists ${$self->{"private_selima"}}{"types"};
$types = ${$self->{"private_selima"}}{"types"};
# Convert the date/datetime columns
foreach (@{$$types{"date"}}) {
$$row[$_] = str2time $$row[$_]
if defined $$row[$_];
}
# Convert the numeric columns
foreach (@{$$types{"num"}}) {
$$row[$_] = $$row[$_] + 0
if defined $$row[$_];
}
# Decode the text columns
foreach (@{$$types{"text"}}) {
$$row[$_] = decode_utf8($$row[$_], FB_CROAK)
if defined $$row[$_];
}
return $row;
}
return 1;

View File

@@ -0,0 +1,93 @@
# Selima Website Content Management System
# DBILogin.pm: The subroutine to extract the database log in information.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-02-02
package Selima::DBILogin;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(get_dbi_login_info);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub get_dbi_login_info($;$$);
}
use MIME::Base64 qw(decode_base64);
use Selima::DataVars qw(:db :siteconf);
# get_dbi_login_info: Obtain the DBI log-in information
sub get_dbi_login_info($;$$) {
local ($_, %_);
my ($type, $host, $user, @names, %login);
($type, $host, $user) = @_;
# DBMS naming convensions
if ($type eq DBI_POSTGRESQL) {
@names = qw(PGHOST PGUSER PGPASSWORD PGDATABASE);
} elsif ($type eq DBI_MYSQL) {
@names = qw(MYSQL_HOST MYSQL_USER MYSQL_PW MYSQL_DB);
}
# Initialize the return values
%login = (
$names[0] => undef,
$names[1] => undef,
$names[2] => undef,
$names[3] => "test",
);
# Obtain the DBI log-in information from the environment
if (exists $ENV{"SQLLOGIN"}) {
# Parse the DBI log-in information
# The first matched line is used, so put the default first
foreach my $line (split /\n/, decode_base64($ENV{"SQLLOGIN"})) {
@_ = split /\t/, $line;
# Skip malformed lines
next unless @_ == 4;
# Not this type
next if $_[0] ne $type;
# Not this host
next if defined $host && $_[1] ne $host;
# Not this user
next if defined $user && $_[2] ne $user;
# Found
# Deal with null values
foreach (@_) {
undef $_ if $_ eq "null";
}
$login{$names[0]} = $_[1];
$login{$names[1]} = $_[2];
$login{$names[2]} = $_[3];
$login{$names[3]} = $PACKAGE if defined $PACKAGE;
last;
}
}
# Alternative information set in %DBILOGIN
foreach (keys %DBILOGIN) {
$login{$_} = $DBILOGIN{$_} if exists $login{$_};
}
return %login;
}
return 1;

View File

@@ -0,0 +1,243 @@
# Selima Website Content Management System
# DataVars.pm: The core constants and variables.
# Copyright (c) 2003-2020 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-24
package Selima::DataVars;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT %EXPORT_TAGS @EXPORT_OK);
BEGIN {
@EXPORT = qw();
%EXPORT_TAGS = (
absuri => [qw(ABSURI_SKIP_FRAGMENT)],
addcol => [qw(ADDCOL_INSERT ADDCOL_UPDATE ADDCOL_NOTIMESTAMP)],
author => [qw($AUTHOR $COPYRIGHT)],
db => [qw($DBH $DBI_TYPE DBI_NONE DBI_POSTGRESQL DBI_MYSQL
DBI_FEATHER_VIEW %DBILOGIN)],
dataman => [qw($THIS_TABLE %CURRENT %REQUEST %PREVIEW)],
env => [qw($IS_CGI $IS_MODPERL $IS_MP2 $IS_PERLIS)],
forms => [qw(%SCRIPTS FORM_THIS FORM_USERS FORM_GROUPS FORM_USERMEM
FORM_GROUPMEM FORM_USERPREF FORM_SCPTPRIV FORM_PIC FORM_PAGES
FORM_NEWS FORM_LINKCAT FORM_LINKS FORM_ACCTSUBJ FORM_ACCTTRX
FORM_CAPTCHA)],
groups => [qw(SU_GROUP ADMIN_GROUP ALLUSERS_GROUP GUEST_GROUP)],
hostconf=> [qw($VIRTUAL_HOST $HTTPS_HOST $NOLOGIN)],
input => [qw(%USER_INPUT $POST $GET $UPLOAD %COOKIES $SESSION $AUTHINFO)],
l10n => [qw($DEFAULT_LANG @ALL_LINGUAS $LH $CLH $ALH
$LOCALEDIR COMMON_DOMAIN COMMON_LOCALEDIR)],
lastmod => [qw($LAST_MODIFIED)],
libdir => [qw($SITE_LIBDIR COMMON_LIBDIR)],
list => [qw($PAGEBAR_RANGE)],
lninfo => [qw(LN_NAME LN_CHARSET LN_FILENAME LN_LOCALE LN_DATABASE
LN_HTMLID LN_SPACE_BREAK LN_COUNTRY_FIRST
LN_DESC LN_DESC_CURLC LN_DESC_SELFLC LN_SWITCH_TITLE)],
mail => [qw(SMTP_HOST)],
output => [qw($CONTENT_TYPE $NO_AUTO_OUTPUT %NEWCOOKIES %HTTP_HEADERS
$PAGE_PARAM $ALT_PAGE_PARAM)],
proctime=> [qw($LOGTIME $T_START)],
rebuild => [qw(@REBUILD_TABLES %REBUILD_LABELS)],
requri => [qw($DOC_ROOT $ROOT_DIFF $REQUEST_PATH $REQUEST_FILE
$REQUEST_URI $REQUEST_FILEQS $REQUEST_SCHEME $REQUEST_HOST
$REQUEST_HOSTPORT $REQUEST_HOSTPATH $REQUEST_FULLURI)],
scptconf=> [qw($THIS_FILE $MAIN)],
siteconf=> [qw($PACKAGE $WEBMASTER $SITENAME_ABBR)],
user => [qw(%USERPREF)],
);
@EXPORT_OK = qw();
my %seen;
%seen = qw();
foreach my $tag (keys %EXPORT_TAGS) {
push @EXPORT_OK, grep !$seen{$_}++, @{$EXPORT_TAGS{$tag}};
}
$EXPORT_TAGS{"all"} = [@EXPORT_OK];
# Prototype declaration
sub clear();
}
use vars qw($AUTHOR $COPYRIGHT);
use vars qw($DBH $DBI_TYPE %DBILOGIN);
use vars qw($THIS_TABLE %CURRENT %REQUEST);
use vars qw($IS_CGI $IS_MODPERL $IS_MP2 $IS_PERLIS);
use vars qw(%SCRIPTS);
use vars qw($VIRTUAL_HOST $HTTPS_HOST $NOLOGIN);
use vars qw(%USER_INPUT $POST $GET $UPLOAD %COOKIES $SESSION $AUTHINFO);
use vars qw($DEFAULT_LANG @ALL_LINGUAS $LH $CLH $ALH);
use vars qw($LOCALEDIR);
use vars qw($LAST_MODIFIED);
use vars qw($SITE_LIBDIR);
use vars qw($PAGEBAR_RANGE);
use vars qw($CONTENT_TYPE $NO_AUTO_OUTPUT %NEWCOOKIES %HTTP_HEADERS);
use vars qw($PAGE_PARAM $ALT_PAGE_PARAM);
use vars qw($LOGTIME $T_START);
use vars qw(@REBUILD_TABLES %REBUILD_LABELS);
use vars qw($DOC_ROOT $ROOT_DIFF $REQUEST_PATH $REQUEST_FILE);
use vars qw($REQUEST_URI $REQUEST_FILEQS $REQUEST_SCHEME $REQUEST_HOST);
use vars qw($REQUEST_HOSTPORT $REQUEST_HOSTPATH $REQUEST_FULLURI);
use vars qw($THIS_FILE $MAIN);
use vars qw($PACKAGE $WEBMASTER $SITENAME_ABBR);
use vars qw(%USERPREF);
use constant ABSURI_SKIP_FRAGMENT => 1;
use constant ADDCOL_INSERT => 0;
use constant ADDCOL_UPDATE => 1;
use constant ADDCOL_NOTIMESTAMP => 0;
use constant DBI_NONE => "";
use constant DBI_POSTGRESQL => "PostgreSQL";
use constant DBI_MYSQL => "MySQL";
use constant DBI_FEATHER_VIEW => "view";
use constant FORM_THIS => 0;
use constant FORM_USERS => 1;
use constant FORM_GROUPS => 2;
use constant FORM_USERPRIV => 3;
use constant FORM_USERMEM => 4;
use constant FORM_GROUPMEM => 5;
use constant FORM_USERPREF => 6;
use constant FORM_SCPTPRIV => 7;
use constant FORM_PIC => 8;
use constant FORM_PAGES => 9;
use constant FORM_NEWS => 10;
use constant FORM_LINKCAT => 11;
use constant FORM_LINKS => 12;
use constant FORM_ACCTSUBJ => 13;
use constant FORM_ACCTTRX => 14;
# The column name of the CAPTCHA
# This is used to deceive the spambots
use constant FORM_CAPTCHA => "lastname";
use constant SU_GROUP => "root";
use constant ADMIN_GROUP => "admin";
use constant ALLUSERS_GROUP => "users";
use constant GUEST_GROUP => "guests";
use constant COMMON_DOMAIN => "selima";
use constant COMMON_LOCALEDIR => $ENV{"DOCUMENT_ROOT"} . "/../../locale";
use constant COMMON_LIBDIR => $ENV{"DOCUMENT_ROOT"} . "/../../lib/perl5";
use constant LN_NAME => 0;
use constant LN_CHARSET => 1;
use constant LN_FILENAME => 2;
use constant LN_LOCALE => 3;
use constant LN_DATABASE => 4;
use constant LN_HTMLID => 5;
use constant LN_SPACE_BREAK => 6;
#use constant LN_IGNORE_CASE => 7;
use constant LN_COUNTRY_FIRST => 8;
use constant LN_DESC => 9;
use constant LN_DESC_CURLC => 10;
use constant LN_DESC_SELFLC => 11;
use constant LN_SWITCH_TITLE => 12;
use constant SMTP_HOST => "localhost";
# Check the environment type in advance. These variables does not change
# at all even under mod_perl.
# GATEWAY_INTERFACE is not available yet when this script is loaded
$IS_CGI = exists $ENV{"GATEWAY_INTERFACE"} || exists $ENV{"MOD_PERL"};
$IS_MODPERL = exists $ENV{"MOD_PERL"};
$IS_MP2 = exists $ENV{"MOD_PERL_API_VERSION"} && $ENV{"MOD_PERL_API_VERSION"} >= 2;
$IS_PERLIS = exists $ENV{"PERLXS"} && $ENV{"PERLXS"} eq "PerlIS";
# clear: Clear the data variables
sub clear() {
local ($_, %_);
# Reset all the data variables
undef $AUTHINFO;
undef $AUTHOR;
undef $COPYRIGHT;
undef $DBH;
undef $DBI_TYPE;
%DBILOGIN = qw();
undef $THIS_TABLE;
%CURRENT = qw();
%REQUEST = qw();
%SCRIPTS = qw();
undef $VIRTUAL_HOST;
undef $HTTPS_HOST;
undef $NOLOGIN;
%USER_INPUT = qw();
undef $POST;
undef $GET;
undef $UPLOAD;
%COOKIES = qw();
undef $DEFAULT_LANG;
@ALL_LINGUAS = qw();
undef $LH;
undef $CLH;
undef $ALH;
undef $LOCALEDIR;
undef $LAST_MODIFIED;
undef $SITE_LIBDIR;
undef $PAGEBAR_RANGE;
undef $CONTENT_TYPE;
undef $NO_AUTO_OUTPUT;
%NEWCOOKIES = qw();
%HTTP_HEADERS = qw();
undef $PAGE_PARAM;
undef $ALT_PAGE_PARAM;
undef $LOGTIME;
undef $T_START;
@REBUILD_TABLES = qw();
%REBUILD_LABELS = qw();
undef $DOC_ROOT;
undef $ROOT_DIFF;
undef $REQUEST_PATH;
undef $REQUEST_FILE;
undef $REQUEST_URI;
undef $REQUEST_FILEQS;
undef $REQUEST_SCHEME;
undef $REQUEST_HOST;
undef $REQUEST_HOSTPORT;
undef $REQUEST_HOSTPATH;
undef $REQUEST_FULLURI;
undef $SESSION;
undef $THIS_FILE;
undef $MAIN;
undef $PACKAGE;
undef $WEBMASTER;
undef $SITENAME_ABBR;
%USERPREF = qw();
return;
}
return 1;

443
lib/perl5/Selima/DecForm.pm Normal file
View File

@@ -0,0 +1,443 @@
# Selima Website Content Management System
# DecForm.pm: The subroutines to decode the the input form from various character sets.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-10
package Selima::DecForm;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(init_forms decode_forms decode_forms_delay);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub init_forms();
sub decode_forms();
sub decode_forms_delay();
sub try_decode_form($$);
sub split_upload($);
}
use Encode qw(decode FB_CROAK);
use URI::Escape qw(uri_escape);
use HTML::Entities qw(%entity2char);
use Selima::DataVars qw(:env :input :l10n :lninfo);
use Selima::GetLang;
use Selima::HTTP;
use Selima::LnInfo;
use Selima::Unicode;
use vars qw(@TRY);
@TRY = qw(US-ASCII Big5 GB2312 Shift-JIS UTF-8 ISO-8859-1 ISO-8859-15);
# init_forms: Initialize the user-input form data
sub init_forms() {
local ($_, %_);
my ($r, $rawget, $rawpost, $postdelay);
# Do not redo
return if scalar(keys %USER_INPUT) > 0;
$r = $IS_MP2? Apache2::RequestUtil->request: Apache->request
if $IS_MODPERL;
# Scan the GET query
$rawget = $IS_MODPERL? (defined $r->args? $r->args: ""):
(exists $ENV{"QUERY_STRING"}? $ENV{"QUERY_STRING"}: "");
$GET = new CGI($rawget);
# Scan the POST form
$postdelay = 0;
if (($IS_MODPERL? $r->method: $ENV{"REQUEST_METHOD"}) eq "POST") {
if ($IS_MODPERL) {
# Ordinary POST form
if ( !defined($_ = $r->headers_in->get("Content-Type"))
|| $_ eq "application/x-www-form-urlencoded") {
if ($IS_MP2) {
$r->read($rawpost, $r->headers_in->get("Content-Length"));
} else {
$rawpost = $r->content;
}
$POST = new CGI($rawpost);
$r->headers_in->set("X-Selima-POST", $rawpost);
# Others (file uploads, etc) and CGI environment is ready
# - parse with CGI.pm default parser
} elsif (exists $ENV{"SERVER_SOFTWARE"}) {
$POST = new CGI;
@_ = qw();
foreach my $name ($POST->param) {
foreach my $val ($POST->param($name)) {
push @_, uri_escape($name) . "=" . uri_escape($val);
}
}
$rawpost = join "&", @_;
# Others (file uploads, etc) and CGI environment is not ready yet
# - delay the parsing
} else {
$rawpost = "";
$POST = new CGI($rawpost);
$postdelay = 1;
}
} else {
# POST form was obtained by mod_perl handler before
if (exists $ENV{"HTTP_X_SELIMA_POST"}) {
$rawpost = $ENV{"HTTP_X_SELIMA_POST"};
$POST = new CGI($rawpost);
# Ordinary POST form -- parse the STDIN
} elsif ( !exists $ENV{"HTTP_CONTENT_TYPE"}
|| $ENV{"HTTP_CONTENT_TYPE"} eq "application/x-www-form-urlencoded") {
$rawpost = <STDIN>;
$POST = new CGI($rawpost);
# Others (file uploads, etc) - parse with CGI.pm default parser
} else {
$POST = new CGI;
@_ = qw();
foreach my $name ($POST->param) {
foreach my $val ($POST->param($name)) {
push @_, uri_escape($name) . "=" . uri_escape($val);
}
}
$rawpost = join "&", @_;
}
}
} else {
$rawpost = "";
$POST = new CGI($rawpost);
}
# Split the uploaded files
($POST, $UPLOAD) = split_upload $POST;
# Initialize the data deposit
%USER_INPUT = (
"GET_RAW" => $GET,
"GET_UTF8" => $GET,
"GET_CHARSET" => undef,
"GET_CSERR" => 1,
"GET_RAWDATA" => $rawget,
"GET_KEYS" => [ $GET->param ],
"POST_RAW" => $POST,
"POST_UTF8" => $POST,
"POST_CHARSET" => undef,
"POST_CSERR" => 1,
"POST_RAWDATA" => $rawpost,
"POST_DELAY" => $postdelay,
"UPLOAD" => $UPLOAD,
);
return;
}
# decode_forms: Decode user FORM input from some character set
sub decode_forms() {
local ($_, %_);
my (@charsets, @charsets_site);
# The possible character sets of this website
@charsets_site = map ln($_, LN_CHARSET), @ALL_LINGUAS;
# The GET arguments
# The character set candidates
@_ = qw();
push @_, $GET->param("charset") if defined $GET->param("charset");
push @_, getlang LN_CHARSET;
push @_, @charsets_site;
push @_, @TRY;
@charsets = qw();
%_ = qw();
foreach (@_) {
if (lc $_ eq "big5") {
if (!exists $_{"Big5-ETen"}) {
push @charsets, "Big5-ETen";
$_{"Big5-ETen"} = 1;
}
if (!exists $_{"Big5"}) {
push @charsets, "Big5";
$_{"Big5"} = 1;
}
if (!exists $_{"CP950"}) {
push @charsets, "CP950";
$_{"CP950"} = 1;
}
if (!exists $_{"Big5-HKSCS"}) {
push @charsets, "Big5-HKSCS";
$_{"Big5-HKSCS"} = 1;
}
} elsif (lc $_ eq "big5-hkscs") {
if (!exists $_{"Big5-HKSCS"}) {
push @charsets, "Big5-HKSCS";
$_{"Big5-HKSCS"} = 1;
}
} elsif (lc $_ eq "gb2312" || lc $_ eq "gb18030") {
if (!exists $_{"GB2312"}) {
push @charsets, "GB2312";
$_{"GB2312"} = 1;
}
if (!exists $_{"GB18030"}) {
push @charsets, "GB18030";
$_{"GB18030"} = 1;
}
} else {
if (!exists $_{$_}) {
push @charsets, $_;
$_{$_} = 1;
}
}
}
# Check each character set
foreach my $charset (@charsets) {
$_ = $USER_INPUT{"GET_RAW"};
# In this character set
if (defined($_ = try_decode_form $_, $charset)) {
$GET = $_;
$USER_INPUT{"GET_UTF8"} = $_;
$USER_INPUT{"GET_CHARSET"} = $charset;
$USER_INPUT{"GET_CSERR"} = 0;
$USER_INPUT{"GET_KEYS"} = [ $_->param ];
last;
}
}
# The POSTed form
# The character set candidates
@_ = qw();
push @_, $POST->param("charset") if defined $POST->param("charset");
push @_, getlang LN_CHARSET;
push @_, @charsets_site;
push @_, @TRY;
@charsets = qw();
%_ = qw();
foreach (@_) {
if (lc $_ eq "big5") {
if (!exists $_{"Big5-ETen"}) {
push @charsets, "Big5-ETen";
$_{"Big5-ETen"} = 1;
}
if (!exists $_{"Big5"}) {
push @charsets, "Big5";
$_{"Big5"} = 1;
}
if (!exists $_{"CP950"}) {
push @charsets, "CP950";
$_{"CP950"} = 1;
}
if (!exists $_{"Big5-HKSCS"}) {
push @charsets, "Big5-HKSCS";
$_{"Big5-HKSCS"} = 1;
}
} elsif (lc $_ eq "big5-hkscs") {
if (!exists $_{"Big5-HKSCS"}) {
push @charsets, "Big5-HKSCS";
$_{"Big5-HKSCS"} = 1;
}
} elsif (lc $_ eq "gb2312" || lc $_ eq "gb18030") {
if (!exists $_{"GB2312"}) {
push @charsets, "GB2312";
$_{"GB2312"} = 1;
}
if (!exists $_{"GB18030"}) {
push @charsets, "GB18030";
$_{"GB18030"} = 1;
}
} else {
if (!exists $_{$_}) {
push @charsets, $_;
$_{$_} = 1;
}
}
}
# Check each character set
foreach my $charset (@charsets) {
$_ = $USER_INPUT{"POST_RAW"};
# In this character set
if (defined($_ = try_decode_form $_, $charset)) {
$POST = $_;
$USER_INPUT{"POST_UTF8"} = $_;
$USER_INPUT{"POST_CHARSET"} = $charset;
$USER_INPUT{"POST_CSERR"} = 0;
last;
}
}
# No valid character set was found
http_500 "Unable to detect the character set of your submitted information. Please specify the input character set with charset= parameter."
if $USER_INPUT{"GET_CSERR"} || $USER_INPUT{"POST_CSERR"};
return;
}
# decode_forms_delay: Initialize and decode multipart/form-data (uploads)
# for mod_perl. We cannot obtain it before the CGI environment is ready.
sub decode_forms_delay() {
local ($_, %_);
my ($r, $rawpost, @charsets, @charsets_site);
# Parsing not delayed
return unless $USER_INPUT{"POST_DELAY"};
# Obtain the POST form
$POST = new CGI;
@_ = qw();
foreach my $name ($POST->param) {
foreach my $val ($POST->param($name)) {
push @_, uri_escape($name) . "=" . uri_escape($val);
}
}
$rawpost = join "&", @_;
# Split the uploaded files
($POST, $UPLOAD) = split_upload $POST;
# Record it
$USER_INPUT{"POST_RAW"} = $POST;
$USER_INPUT{"POST_UTF8"} = $POST;
$USER_INPUT{"POST_CHARSET"} = undef;
$USER_INPUT{"POST_CSERR"} = 1;
$USER_INPUT{"POST_RAWDATA"} = $rawpost;
$USER_INPUT{"UPLOAD"} = $UPLOAD;
# The character set candidates
@_ = qw();
push @_, $POST->param("charset") if defined $POST->param("charset");
push @_, getlang LN_CHARSET;
push @_, map ln($_, LN_CHARSET), @ALL_LINGUAS;
push @_, @TRY;
@charsets = qw();
%_ = qw();
foreach (@_) {
if (lc $_ eq "big5") {
if (!exists $_{"Big5-ETen"}) {
push @charsets, "Big5-ETen";
$_{"Big5-ETen"} = 1;
}
if (!exists $_{"Big5"}) {
push @charsets, "Big5";
$_{"Big5"} = 1;
}
if (!exists $_{"CP950"}) {
push @charsets, "CP950";
$_{"CP950"} = 1;
}
if (!exists $_{"Big5-HKSCS"}) {
push @charsets, "Big5-HKSCS";
$_{"Big5-HKSCS"} = 1;
}
} elsif (lc $_ eq "big5-hkscs") {
if (!exists $_{"Big5-HKSCS"}) {
push @charsets, "Big5-HKSCS";
$_{"Big5-HKSCS"} = 1;
}
} elsif (lc $_ eq "gb2312" || lc $_ eq "gb18030") {
if (!exists $_{"GB2312"}) {
push @charsets, "GB2312";
$_{"GB2312"} = 1;
}
if (!exists $_{"GB18030"}) {
push @charsets, "GB18030";
$_{"GB18030"} = 1;
}
} else {
if (!exists $_{$_}) {
push @charsets, $_;
$_{$_} = 1;
}
}
}
# Check each character set
foreach my $charset (@charsets) {
$_ = $USER_INPUT{"POST_RAW"};
# In this character set
if (defined($_ = try_decode_form $_, $charset)) {
$POST = $_;
$USER_INPUT{"POST_UTF8"} = $_;
$USER_INPUT{"POST_CHARSET"} = $charset;
$USER_INPUT{"POST_CSERR"} = 0;
last;
}
}
# No valid character set was found
http_500 "Unable to detect the character set of your submitted information. Please specify the input character set with charset= parameter."
if $USER_INPUT{"GET_CSERR"} || $USER_INPUT{"POST_CSERR"};
return;
}
# try_decode_form: Try to decode a CGI form with a specific character set
sub try_decode_form($$) {
local ($_, %_);
my ($SOURCE, $charset, $RESULT, $name, $is_upload);
($SOURCE, $charset) = @_;
# Obtain a copy of the converted result
$RESULT = new CGI("");
$is_upload = 0;
foreach $name ($SOURCE->param) {
@_ = $SOURCE->param($name);
return if !defined($name = hcref_decode($charset, $name));
foreach (@_) {
s/\x00//g;
return if !defined($_ = hcref_decode($charset, $_));
}
$RESULT->param($name, @_);
}
return $RESULT;
}
# split_upload: Split the uploaded files from the POST form
sub split_upload($) {
local ($_, %_);
my ($FORM, $UPLOAD, $name);
$FORM = $_[0];
$UPLOAD = $FORM;
$FORM = new CGI("");
foreach $name ($UPLOAD->param) {
my (@valp, @valu);
@valp = qw();
@valu = qw();
foreach ($UPLOAD->param($name)) {
# A scalar value
if (ref $_ eq "") {
push @valp, $_;
# Others (file uploads Fh, etc.)
} else {
push @valu, $_;
}
}
# Some scalar found
if (@valp > 0) {
# Move to the POST form
$FORM->param($name, @valp);
# There are some uploaded files for this column
if (@valu > 0) {
$UPLOAD->param($name, @valu);
# No uploaded file for this column
} else {
$UPLOAD->delete($name);
}
}
}
return ($FORM, $UPLOAD);
}
return 1;

226
lib/perl5/Selima/Destroy.pm Normal file
View File

@@ -0,0 +1,226 @@
# Selima Website Content Management System
# Destroy.pm: The script-environment cleaner.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-27
package Selima::Destroy;
use 5.008;
use strict;
use warnings;
use CGI qw(header);
use HTTP::Date qw(time2str);
use IO::NestedCapture qw(CAPTURE_STDOUT);
use Time::HiRes qw();
BEGIN {
if (exists $ENV{"MOD_PERL_API_VERSION"} && $ENV{"MOD_PERL_API_VERSION"} >= 2) {
require Apache2::Response;
}
}
use Selima::AltLang;
use Selima::Cache qw();
use Selima::DataVars qw($DBH $SESSION :env :input :l10n :lastmod
:lninfo :output :proctime :requri :scptconf :siteconf);
use Selima::GetLang;
use Selima::HTTP;
use Selima::Logging;
use Selima::LogIn;
use Selima::Page2Rel;
use Selima::PageFunc;
use Selima::Unicode;
use Selima::XHTML;
# new: Initialize the object
sub new : method { bless {}, $_[0]; }
# DESTROY: Clean all the variables
sub DESTROY : method {
local ($_, %_);
my ($self, $html);
$self = $_[0];
# Disconnect the database handle
if (defined $DBH) {
$DBH->disconnect;
undef $DBH;
}
# Flush, close and release the session and its lock
if (defined $SESSION) {
$SESSION->close;
undef $SESSION;
}
# Output the content
if (!$NO_AUTO_OUTPUT) {
my ($type, $r, $charset, $is_html, $is_text);
# Obtain the output
$html = "";
while ( exists IO::NestedCapture->instance->{"STDOUT_current"}
&& @{IO::NestedCapture->instance->{"STDOUT_current"}} > 0) {
my $FD;
IO::NestedCapture->stop(CAPTURE_STDOUT);
$FD = IO::NestedCapture->get_last_out;
$html .= join "", <$FD>;
}
# No content -- HTTP 204
http_204 if $html eq "";
$type = defined $CONTENT_TYPE? $CONTENT_TYPE: xhtml_content_type;
$is_html = ($type =~ /^(?:text\/html|application\/xhtml\+xml)\b/)? 1: 0;
$is_text = ($type =~ /^(?:text\/plain|text\/csv)\b/)? 1: 0;
# Do the run-time replacements
if ( ($is_text || $is_html)
&& defined($_ = $MAIN->can("page_replacements"))) {
%_ = %{&$_};
$html =~ s/<!--selima:$_-->/${$_{$_}}{"content"}/g
foreach keys %_;
}
# Fix the HTML output
if ($is_html) {
if ($type =~ /; charset=([^ ;]+)/) {
$charset = $1;
} else {
$charset = getlang(LN_CHARSET);
$type .= "; charset=$charset";
}
# Convert the URLs to relative
$html = page2rel($html, $REQUEST_PATH);
# Encode the e-mail at-signs (@)
$html =~ s/@/&#64;/g;
# Decode the e-mail at-signs (@) of spamtrap
$html =~ s/spamtrap&#64;/spamtrap@/g;
# Convert to the desired character set
$html = page_encode($html, $charset);
# Fix the plain text output
} elsif ($is_text) {
if ($type =~ /; charset=([^ ;]+)/) {
$charset = $1;
} else {
$charset = "UTF-8";
$type .= "; charset=$charset";
}
# Encode the e-mail at-signs (@)
$html =~ s/@/-at-/g;
# Decode the e-mail at-signs (@) of spamtrap
$html =~ s/spamtrap-at-/spamtrap@/g;
# Convert to the desired character set
$html = page_encode($html, $charset);
}
# Only output headers to CGI
if ($IS_CGI) {
# The mod_perl way
if ($IS_MODPERL) {
$r = $IS_MP2? Apache2::RequestUtil->request:
Apache->request;
$r->content_type($type);
if ($IS_MP2) {
$r->set_content_length(length $html);
} else {
$r->headers_out->set("Content-Length"=>length $html);
}
$r->headers_out->set("Accept-Ranges"=>"none");
$r->content_languages([getlang LN_NAME])
if $type =~ /^text\// || $is_html;
# Client cache
if (defined $LAST_MODIFIED) {
if (defined get_login_sn) {
$r->headers_out->set("Cache-Control"=>"private");
} else {
$r->headers_out->set("Cache-Control"=>"public");
}
$r->headers_out->set("Last-Modified"=>time2str($LAST_MODIFIED));
} else {
$r->headers_out->set("Cache-Control"=>"no-cache");
}
# Content negotiation, see HTTP/1.1 section 13.6
if ( @ALL_LINGUAS > 1
&& $r->method ne "POST"
&& $r->method ne "PUT"
&& !defined $GET->param("lang")) {
$r->headers_out->set("Content-Location"=>altlang(getlang, page_param));
$r->headers_out->set("Vary"=>"accept-language,cookie");
}
$r->headers_out->add("Set-Cookie"=>$_)
foreach values %NEWCOOKIES;
$r->headers_out->add($_=>$HTTP_HEADERS{$_})
foreach keys %HTTP_HEADERS;
$r->send_http_header if !$IS_MP2;
# Ordinary CGI
} else {
my %h;
%h = ( -type=>$type,
-Content_Length=>length $html,
-Accept_Ranges=>"none");
$h{"-Content_Language"} = getlang LN_NAME
if $type =~ /^text\// || $is_html;
# Content negotiation, see HTTP/1.1 section 13.6
if ( @ALL_LINGUAS > 1
&& $ENV{"REQUEST_METHOD"} ne "POST"
&& $ENV{"REQUEST_METHOD"} ne "PUT"
&& !defined $GET->param("lang")) {
$h{"-Content_Location"} = altlang getlang, page_param;
$h{"-Vary"} = "accept-language,cookie";
}
# Client cache
if (defined $LAST_MODIFIED) {
if (defined get_login_sn) {
$h{"-Cache_Control"} = "private";
} else {
$h{"-Cache_Control"} = "public";
}
$h{"-Last_Modified"} = time2str($LAST_MODIFIED);
} else {
$h{"-Cache_Control"} = "no-cache";
}
$h{"-cookie"} = [values %NEWCOOKIES];
$h{$_} = $HTTP_HEADERS{$_} foreach keys %HTTP_HEADERS;
print header(%h);
}
}
# Print the page body
print $html if $ENV{"REQUEST_METHOD"} ne "HEAD";
}
# Print the processing time for debugging purpose
log_warn "Process time: " . (Time::HiRes::time - $T_START) . " sec.\n"
if $LOGTIME;
# Clear the data variables
$_ = "Selima::" . $PACKAGE . "::Config";
&$_ if defined($_ = $_->can("clear"));
Selima::DataVars::clear;
# Clear the cache
Selima::Cache::clear;
# Run the parent DESTROY method
$self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
# I cannot really undefine myself ($_[0]) after all
return;
}
return 1;

View File

@@ -0,0 +1,99 @@
# Selima Website Content Management System
# EchoForm.pm: The subroutines to output various form elements.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-30
package Selima::EchoForm;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(auto_keep_referer);
push @EXPORT, qw(opt_list opt_list_array preselect_options);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub opt_list($$);
sub opt_list_array(\@);
sub preselect_options($$);
sub auto_keep_referer();
}
use Selima::Cache qw(:echoform);
use Selima::CommText;
use Selima::DataVars qw($DBH);
use Selima::ShortCut;
use Selima::Unicode;
# opt_list: Return an options list
sub opt_list($$) {
local ($_, %_);
my ($sql, $curval, $sth, $count, $html);
($sql, $curval) = @_;
# Not cached yet
if (!exists $EchoForm_opt_list{$sql}) {
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, @_ = qw(); $_ < $count; $_++) {
push @_, $sth->fetchrow_hashref;
}
$EchoForm_opt_list{$sql} = opt_list_array @_;
}
return preselect_options($EchoForm_opt_list{$sql}, $curval);
}
# opt_list_array: Return an options list from an array
sub opt_list_array(\@) {
local ($_, %_);
my ($opts, $html);
$opts = $_[0];
# Obtain the HTML
$html = " <option value=\"\">"
. h(t_notset) . "</option>\n";
foreach (@$opts) {
$html .= " <option value=\""
. h($$_{"value"}) . "\">"
. h($$_{"content"}) . "</option>\n";
}
return $html;
}
# preselect_options: Presect an option in an option list
sub preselect_options($$) {
local ($_, %_);
my ($html, $value);
($html, $value) = @_;
# Not selected if value not set
return $html if !defined $value;
$value = h_encode($value);
$html =~ s/<(option value="$value")>/<$1 selected="selected">/;
return $html;
}
# auto_keep_referer: If we should keep the referer information
# To be done
sub auto_keep_referer() {
local ($_, %_);
# False for now;
return 0;
}
return 1;

View File

@@ -0,0 +1,62 @@
# Selima Website Content Management System
# Encrypt.pm: The simple symmetric encrypter/decrypter.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-26
package Selima::Encrypt;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(encrypt decrypt);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub encrypt($);
sub decrypt($);
}
use Crypt::Rijndael qw();
use MIME::Base64 qw(encode_base64 decode_base64);
use vars qw($cipher);
$cipher = new Crypt::Rijndael(decode_base64("MDkxcTc3dGNvMkRZTTYxM0dqRmZ2R2xvQmNLcUZkNVo="));
# This use symmetric encryption/decryption. It is not safe.
# Nothing is exported. Use it by the full package name.
# encrypt: Encrypt
sub encrypt($) {
local ($_, %_);
$_ = $_[0];
$_ = length($_) . " " . $_;
# Pad with US-ASCII printable characters
$_ .= chr(32 + int rand 95) while length($_) % 16 != 0;
return encode_base64($cipher->encrypt($_));
}
# decrypt: Decrypt
sub decrypt($) {
local ($_, %_);
$_ = $cipher->decrypt(decode_base64($_[0]));
s/^(\d+) //;
return substr $_, 0, $1;
}
return 1;

View File

@@ -0,0 +1,57 @@
# Selima Website Content Management System
# ErrMsg.pm: The Maketext error message composer.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-23
package Selima::ErrMsg;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(err2msg);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub err2msg($);
}
use Selima::DataVars qw(:l10n :lninfo);
use Selima::LnInfo;
use Selima::ShortCut;
# err2msg: Compose the error message from the $error hash reference
sub err2msg($) {
local (%_, $_);
my $status;
$status = $_[0];
# Empty string
return "" if !exists $$status{"msg"};
$_ = $$status{"msg"};
@_ = qw();
@_ = @{$$status{"margs"}} if exists $$status{"margs"};
foreach (@_) {
if ($_ eq "_DEFAULT_LANG") {
$_ = h(ln $DEFAULT_LANG, LN_DESC_CURLC);
}
}
# Maketext
return F_($_, @_);
}
return 1;

View File

@@ -0,0 +1,56 @@
# Selima Website Content Management System
# FetchRec.pm: The subroutine to fetch a record from a database table.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-28
package Selima::FetchRec;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(fetchrec);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub fetchrec(\$$);
}
use Selima::ChkFunc;
use Selima::DataVars qw($DBH :lninfo);
use Selima::GetLang;
# fetchrec: Try to fetch a record by its serial number
sub fetchrec(\$$) {
local ($_, %_);
my ($sn, $table, $sth, $sql, %row);
($sn, $table) = @_;
return if !check_sn $$sn;
$sql = "SELECT * FROM " . $DBH->quote_identifier($table)
. " WHERE sn=$$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
return if $sth->rows != 1;
%row = %{$sth->fetchrow_hashref};
# Hash the multi-lingual columns
$row{$_} = $row{$_ . "_" . getlang LN_DATABASE}
foreach $DBH->cols_ml($table);
return %row;
}
return 1;

2657
lib/perl5/Selima/Form.pm Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
# Selima Website Content Management System
# AcctRec.pm: The accounting record form.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-23
package Selima::Form::AcctRec;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::Accounting;
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "acctrecs"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this accounting record")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new accounting record.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current accounting record.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete an accounting record.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(trx type ord subj summary amount)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn trx type ord subj summary amount
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add a New Accounting Record");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Edit a Current Accounting Record");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete an Accounting Record");
}
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_trx: The accounting transaction
sub _html_col_trx : method {
$_[0]->_html_coltmpl_call("trx", h_abbr(C_("Accounting transaction:")), \&accttrx_id);
}
# _html_col_type: The type
sub _html_col_type : method {
local ($_, %_);
my ($self, @opts);
$self = $_[0];
@opts = ( { "val" => "debit",
"title" => C_("Debit"), },
{ "val" => "credit",
"title" => C_("Credit"), }, );
return $self->_html_coltmpl_radio("type", h_abbr(C_("Type:")), \@opts, undef, 1);
}
# _html_col_subj: The accounting subject
sub _html_col_subj : method {
local ($_, %_);
my ($self, $form, $current, $label, $choose, $mark, $colspan);
my ($cur, $orig, $new);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$label = h_abbr(C_("Accounting subject:"));
$choose = h_abbr(C_("Choose"));
$mark = $self->_mark("subj");
$colspan = $self->_colspan;
# A form to create a new item
if ($self->{"type"} eq "new") {
print << "EOT";
<tr>
<th class="th" scope="row"><label for="subj">$mark$label</label></th>
<td$colspan><select id="subj" name="subj">
EOT
print acctsubj_recent_options $form->param("subj");
print << "EOT";
</select>
<input id="selsubj" type="submit" name="selsubj" value="$choose" />
</td>
</tr>
EOT
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
$cur = h(acctsubj_title $current->param("subj"));
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
print << "EOT";
<tr>
<th class="th" rowspan="2" scope="row"><label for="subj">$mark$label</label></th>
<th class="oldnew" scope="row">$orig</th>
<td$colspan>$cur</td>
</tr>
<tr>
<th class="oldnew" scope="row"><label for="subj">$new</label></th>
<td$colspan><select id="subj" name="subj">
EOT
print acctsubj_recent_options $form->param("subj");
print << "EOT";
</select>
<input id="selsubj" type="submit" name="selsubj" value="$choose" />
</td>
</tr>
EOT
# A form to delete a current item
} elsif ($self->{"type"} eq "del") {
$cur = h(acctsubj_title $current->param("subj"));
print << "EOT";
<tr>
<th class="th" scope="row">$mark$label</th>
<td$colspan>$cur</td>
</tr>
EOT
}
return;
}
# _html_col_summary: The summary
sub _html_col_summary : method {
$_[0]->_html_coltmpl_text("summary", h_abbr(C_("Summary:")));
}
# _html_col_amount: The amount
sub _html_col_amount : method {
$_[0]->_html_coltmpl_text("amount", h_abbr(C_("Amount:")));
}
return 1;

View File

@@ -0,0 +1,143 @@
# Selima Website Content Management System
# AcctSubj.pm: The accounting subject form.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-08-23
package Selima::Form::AcctSubj;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::CommText;
use Selima::DataVars qw(:requri :scptconf);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "acctsubj"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this accounting subject")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new accounting subject.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current accounting subject.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete an accounting subject.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(parent code title)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn parent code title ssubs
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add a New Accounting Subject");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Edit a Current Accounting Subject");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete an Accounting Subject");
}
}
$self = $class->SUPER::new($status, $args);
if ($self->{"type"} eq "cur") {
if (defined $self->{"cur"}->param("ssubcount") && $self->{"cur"}->param("ssubcount") > 0) {
$self->{"nodelete"} = 1;
push @{$self->{"prefmsg"}}, C_("This accounting subject has [numerate,_1,an accounting sub-subject,accounting sub-subjects]. It cannot be deleted. To delete the accounting subject, [numerate,_1,its accounting sub-subject,all of its accounting sub-subjects] must first be deleted.", $self->{"cur"}->param("ssubcount"));
}
if (defined $self->{"cur"}->param("reccount") && $self->{"cur"}->param("reccount") > 0) {
$self->{"nodelete"} = 1;
push @{$self->{"prefmsg"}}, C_("This accounting subject has [numerate,_1,an accounting record,accounting records]. It cannot be deleted. To delete the accounting subject, [numerate,_1,its accounting record,all of its accounting records] must first be deleted.", $self->{"cur"}->param("reccount"));
}
}
return $self;
}
# _html_col_code: The code
sub _html_col_code : method {
$_[0]->_html_coltmpl_text("code", h_abbr(C_("Code:")), undef,
${$_[0]->{"maxlens"}}{"code"});
}
# _html_col_parent: The parent
sub _html_col_parent : method {
$_[0]->_html_coltmpl_call_null("parent", h_abbr(C_("Parent subject:")),
"topmost", h_abbr(C_("At the very top")), $MAIN->can($_[0]->{"table"} . "_title"));
}
# _html_col_ssubs: The sub-subjects
sub _html_col_ssubs : method {
local ($_, %_);
my ($self, $form, $current, $label, $url, $mark, $colspan, $thclass, $thcolspan);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("ssubs");
$colspan = $self->_colspan;
$label = h_abbr(C_("[numerate,_1,Sub-subject,Sub-subjects]:", $current->param("ssubcount")));
# A current form span for 2 columns
$thclass = $self->{"type"} ne "cur"? " class=\"th\"": "";
$thcolspan = $self->{"type"} eq "cur"? " colspan=\"2\"": "";
print << "EOT";
<tr>
<th$thclass$thcolspan scope="row">$mark$label</th>
EOT
print " <td$colspan>";
@_ = qw();
for ($_ = 0; $_ < $current->param("ssubcount"); $_++) {
push @_, sprintf(" <li><a href=\"%1\$s\">%2\$s</a></li>\n",
h($REQUEST_FILE . "?form=cur&sn=" . $current->param("ssub$_" . "sn")),
h($current->param("ssub$_" . "title")));
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
}
return 1;

View File

@@ -0,0 +1,819 @@
# Selima Website Content Management System
# AcctTrx.pm: The accounting transaction form.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-20
package Selima::Form::AcctTrx;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::Accounting;
use Selima::AddGet;
use Selima::CommText;
use Selima::DataVars qw($DBH :requri :scptconf);
use Selima::FormFunc;
use Selima::Format;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "accttrx"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this accounting transaction")
if !exists $$args{"deltext"};
# The hidden columns
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new accounting transaction.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current accounting transaction.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete an accounting transaction.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(date ord recs note)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn date ord recs note
created createdby updated updatedby)];
}
}
$self = $class->SUPER::new($status, $args);
${$self->{"maxlens"}}{"ord"} = 2;
# Set the subform type
if ($$args{"type"} eq "new") {
$_ = curform;
$self->{"subtype"} = $_->param("formsub");
} elsif ($$args{"type"} eq "cur") {
$self->{"subtype"} = $self->{"form"}->param("formsub");
} elsif ($$args{"type"} eq "del") {
$self->{"subtype"} = $self->{"cur"}->param("formsub");
}
# Only expense, income or trans are allowed
http_500 "invalid form sub-type: " . $self->{"type"}
if $self->{"subtype"} !~ /^(?:expense|income|trans)$/;
if ($self->{"subtype"} eq "expense" || $self->{"subtype"} eq "income") {
$self->{"colspan"} = 3;
} else {
$self->{"colspan"} = 6;
}
$self->{"hidcols"} = [] if !exists $self->{"hidcols"};
push @{$self->{"hidcols"}}, {
"name" => "formsub",
"value" => $self->{"subtype"},
};
if ($self->{"type"} eq "cur" && $self->{"subtype"} ne "trans") {
$self->{"header_buttons"} = [
{ "name" => undef, "value" => h(C_("Submit")) },
{ "name" => "confirm", "value" => h(C_("Save")) },
{ "name" => "del", "value" => h($self->{"deltext"}) },
{ "name" => "cnvttrans", "value" => h(C_("Convert to a transfer transaction")) }, ];
$self->{"footer_buttons"} = [
{ "name" => undef, "value" => h(C_("Submit")) },
{ "name" => "confirm", "value" => h(C_("Save")) },
{ "name" => "del", "value" => h($self->{"deltext"}) },
{ "name" => "cnvttrans", "value" => h(C_("Convert to a transfer transaction")) }, ];
}
# A form to create a new item
if ($self->{"type"} eq "new") {
if ($self->{"subtype"} eq "expense") {
$self->{"title"} = C_("Add a New Cache Expense Transaction");
} elsif ($self->{"subtype"} eq "income") {
$self->{"title"} = C_("Add a New Cache Income Transaction");
} elsif ($self->{"subtype"} eq "trans") {
$self->{"title"} = C_("Add a New Transfer Transaction");
}
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
if ($self->{"subtype"} eq "expense") {
$self->{"title"} = C_("Edit a Current Cache Expense Transaction");
} elsif ($self->{"subtype"} eq "income") {
$self->{"title"} = C_("Edit a Current Cache Income Transaction");
} elsif ($self->{"subtype"} eq "trans") {
$self->{"title"} = C_("Edit a Current Transfer Transaction");
}
# A form to delete a current item
} elsif ($self->{"type"} eq "del") {
if ($self->{"subtype"} eq "expense") {
$self->{"title"} = C_("Delete a Cache Expense Transaction");
} elsif ($self->{"subtype"} eq "income") {
$self->{"title"} = C_("Delete a Cache Income Transaction");
} elsif ($self->{"subtype"} eq "trans") {
$self->{"title"} = C_("Delete a Transfer Transaction");
}
}
%_ = $DBH->col_lens("acctrec");
${$self->{"maxlens"}}{"recsubj"} = $_{"subj"};
${$self->{"maxlens"}}{"recsummary"} = $_{"summary"};
${$self->{"maxlens"}}{"recamount"} = $_{"amount"};
return $self;
}
# _html_coltmpl_ro_loop_rec: Display a read-only record row column
sub _html_coltmpl_ro_loop_rec : method {
local ($_, %_);
local ($_, %_);
my ($self, $i, $current, $rowhdrs);
my ($curlsubj, $curlsummary, $curlamount);
my ($curbsubj, $curbsummary, $curbamount);
($self, $i) = @_;
$current = $self->{"cur"};
$rowhdrs = "threcs";
$rowhdrs .= " thold" if $self->{"type"} eq "cur";
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
$curlsubj = defined($_ = $current->param("debt$i" . "subj"))?
h_abbr(acctsubj_title $_): "";
$curlsummary = defined($_ = $current->param("debt$i" . "summary"))?
h_abbr($_): "";
$curlamount = defined($_ = $current->param("debt$i" . "amount"))?
h_abbr(fmtntamount $_): "";
print << "EOT";
<tr>
<td headers="$rowhdrs thcdsubj">$curlsubj</td>
<td headers="$rowhdrs thcdsummary">$curlsummary</td>
<td class="amount" headers="$rowhdrs thcdamount">$curlamount</td>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
$curbsubj = defined($_ = $current->param("crdt$i" . "subj"))?
h_abbr(acctsubj_title $_): "";
$curbsummary = defined($_ = $current->param("crdt$i" . "summary"))?
h_abbr($_): "";
$curbamount = defined($_ = $current->param("crdt$i" . "amount"))?
h_abbr(fmtntamount $_): "";
print << "EOT";
<tr>
<td headers="$rowhdrs thccsubj">$curbsubj</td>
<td headers="$rowhdrs thccsummary">$curbsummary</td>
<td class="amount" headers="$rowhdrs thccamount">$curbamount</td>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
$curlsubj = defined($_ = $current->param("debt$i" . "subj"))?
h_abbr(acctsubj_title $_): "";
$curlsummary = defined($_ = $current->param("debt$i" . "summary"))?
h_abbr($_): "";
$curlamount = defined($_ = $current->param("debt$i" . "amount"))?
h_abbr(fmtntamount $_): "";
$curbsubj = defined($_ = $current->param("crdt$i" . "subj"))?
h_abbr(acctsubj_title $_): "";
$curbsummary = defined($_ = $current->param("crdt$i" . "summary"))?
h_abbr($_): "";
$curbamount = defined($_ = $current->param("crdt$i" . "amount"))?
h_abbr(fmtntamount $_): "";
print << "EOT";
<tr>
<td headers="$rowhdrs thcdsubj">$curlsubj</td>
<td headers="$rowhdrs thcdsummary">$curlsummary</td>
<td class="amount" headers="$rowhdrs thcdamount">$curlamount</td>
<td headers="$rowhdrs thccsubj">$curbsubj</td>
<td headers="$rowhdrs thccsummary">$curbsummary</td>
<td class="amount" headers="$rowhdrs thccamount">$curbamount</td>
</tr>
EOT
}
return;
}
# _html_coltmpl_loop_rec: Display a record row column
sub _html_coltmpl_loop_rec : method {
local ($_, %_);
my ($self, $i, $form, $choose, $rowhdrs);
my ($coldsubj, $coldsummary, $valdsummary, $coldamount, $valdamount);
my ($colcsubj, $colcsummary, $valcsummary, $colcamount, $valcamount);
($self, $i) = @_;
$form = $self->{"form"};
$choose = h_abbr(C_("Choose"));
$rowhdrs = "threcs";
$rowhdrs .= " thnew" if $self->{"type"} eq "cur";
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
$coldsubj = "debt$i" . "subj";
$coldsubj = h($coldsubj);
$coldsummary = "debt$i" . "summary";
$valdsummary = $self->_val_text($coldsummary, "recsummary");
$coldsummary = h($coldsummary);
$coldamount = "debt$i" . "amount";
$valdamount = $self->_val_text($coldamount, "recamount");
$coldamount = h($coldamount);
print << "EOT";
<tr>
<td headers="$rowhdrs thdsubj"><select id="$coldsubj" name="$coldsubj" onchange="setAutoSummary(this);">
EOT
print acctsubj_recent_options $form->param($coldsubj);
print << "EOT";
</select>
<input id="sel$coldsubj" type="submit" name="sel$coldsubj" value="$choose" />
</td>
<td headers="$rowhdrs thdsummary"><input id="$coldsummary" type="text" name="$coldsummary" size="15"$valdsummary /></td>
<td headers="$rowhdrs thdamount"><input id="$coldamount" class="amount" type="text" name="$coldamount" size="10"$valdamount onchange="calcTotal(this);" /></td>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
$colcsubj = "crdt$i" . "subj";
$colcsubj = h($colcsubj);
$colcsummary = "crdt$i" . "summary";
$valcsummary = $self->_val_text($colcsummary, "recsummary");
$colcsummary = h($colcsummary);
$colcamount = "crdt$i" . "amount";
$valcamount = $self->_val_text($colcamount, "recamount");
$colcamount = h($colcamount);
print << "EOT";
<tr>
<td headers="$rowhdrs thcsubj"><select id="$colcsubj" name="$colcsubj" onchange="setAutoSummary(this);">
EOT
print acctsubj_recent_options $form->param($colcsubj);
print << "EOT";
</select>
<input id="sel$colcsubj" type="submit" name="sel$colcsubj" value="$choose" />
</td>
<td headers="$rowhdrs thcsummary"><input id="$colcsummary" type="text" name="$colcsummary" size="15"$valcsummary /></td>
<td headers="$rowhdrs thcamount"><input id="$colcamount" class="amount" type="text" name="$colcamount" size="10"$valcamount onchange="calcTotal(this);" /></td>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
$coldsubj = "debt$i" . "subj";
$coldsubj = h($coldsubj);
$coldsummary = "debt$i" . "summary";
$valdsummary = $self->_val_text($coldsummary, "recsummary");
$coldsummary = h($coldsummary);
$coldamount = "debt$i" . "amount";
$valdamount = $self->_val_text($coldamount, "recamount");
$coldamount = h($coldamount);
$colcsubj = "crdt$i" . "subj";
$colcsubj = h($colcsubj);
$colcsummary = "crdt$i" . "summary";
$valcsummary = $self->_val_text($colcsummary, "recsummary");
$colcsummary = h($colcsummary);
$colcamount = "crdt$i" . "amount";
$valcamount = $self->_val_text($colcamount, "recamount");
$colcamount = h($colcamount);
print << "EOT";
<tr>
<td headers="$rowhdrs thdebit thdsubj"><select id="$coldsubj" name="$coldsubj" onchange="setAutoSummary(this);">
EOT
print acctsubj_recent_options $form->param($coldsubj);
print << "EOT";
</select>
<input id="sel$coldsubj" type="submit" name="sel$coldsubj" value="$choose" />
</td>
<td headers="$rowhdrs thdebit thdsummary"><input id="$coldsummary" type="text" name="$coldsummary" size="15"$valdsummary /></td>
<td headers="$rowhdrs thdebit thdamount"><input id="$coldamount" class="amount" type="text" name="$coldamount" size="10"$valdamount onchange="calcTotal(this);" /></td>
<td headers="$rowhdrs thcredit thcsubj"><select id="$colcsubj" name="$colcsubj" onchange="setAutoSummary(this);">
EOT
print acctsubj_recent_options $form->param($colcsubj);
print << "EOT";
</select>
<input id="sel$colcsubj" type="submit" name="sel$colcsubj" value="$choose" />
</td>
<td headers="$rowhdrs thcredit thcsummary"><input id="$colcsummary" type="text" name="$colcsummary" size="15"$valcsummary /></td>
<td headers="$rowhdrs thcredit thcamount"><input id="$colcamount" class="amount" type="text" name="$colcamount" size="10"$valcamount onchange="calcTotal(this);" /></td>
</tr>
EOT
}
return;
}
# _html_col_ord: The order
sub _html_col_ord : method {
local ($_, %_);
my ($self, $form);
$self = $_[0];
$form = $self->{"form"};
# Set the default order to maximum
$form->param("ord", 99)
if $self->{"is_first_form"} && $self->{"type"} eq "new";
$self->_html_coltmpl_text("ord", h_abbr(C_("Order:")), undef,
${$self->{"maxlens"}}{"ord"});
}
# _html_col_recs: The accounting records
sub _html_col_recs : method {
local ($_, %_);
my ($self, $form, $current, $label, $mark, $orig, $new);
my ($labeldebit, $labelcredit, $labelsubj, $labelsummary, $labelamount);
my ($labelsum, $sumdebit, $sumcredit);
my ($count_new, $rows_new, $count_cur, $rows_cur, $rowspan);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("recs");
$labeldebit = h_abbr(C_("Debit"));
$labelcredit = h_abbr(C_("Credit"));
$labelsubj = h_abbr(C_("Accounting subject"));
$labelsummary = h_abbr(C_("Summary"));
$labelamount = h_abbr(C_("Amount"));
$labelsum = h_abbr(C_("Total"));
# A form to create a new item
if ($self->{"type"} eq "new") {
# Find the total number of records
$count_new = 0;
# Find the last-used debit record
for ( $_ = 0;
defined $form->param("debt$_" . "subj")
|| defined $form->param("debt$_" . "summary")
|| defined $form->param("debt$_" . "amount"); $_++) {};
if ($_ > 0) {
for ( $_--;
$_ >= 0
&& $form->param("debt$_" . "subj") eq ""
&& $form->param("debt$_" . "summary") eq ""
&& $form->param("debt$_" . "amount") eq ""; $_--) {};
$_++;
}
$count_new = $_ if $count_new < $_;
# Find the last-used credit record
for ( $_ = 0;
defined $form->param("crdt$_" . "subj")
|| defined $form->param("crdt$_" . "summary")
|| defined $form->param("crdt$_" . "amount"); $_++) {};
if ($_ > 0) {
for ( $_--;
$_ >= 0
&& $form->param("crdt$_" . "subj") eq ""
&& $form->param("crdt$_" . "summary") eq ""
&& $form->param("crdt$_" . "amount") eq ""; $_--) {};
$_++;
}
$count_new = $_ if $count_new < $_;
# We need at least 5 blank records
$count_new += 5;
$_ = $count_new + 2;
$_++ if $self->{"subtype"} eq "trans";
$rows_new = " rowspan=\"" . h($_) . "\"";
$rows_new = "" if $_ == 1;
$label = h_abbr(C_("[numerate,_1,Content]:", 0));
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rows_new scope="row"><label for="debt0subj">$mark$label</label></th>
<th id="thdsubj"><label for="debt0subj">$labelsubj</label></th>
<th id="thdsummary"><label for="debt0summary">$labelsummary</label></th>
<th id="thdamount"><label for="debt0amount">$labelamount</label></th>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rows_new scope="row"><label for="crdt0subj">$mark$label</label></th>
<th id="thcsubj"><label for="crdt0subj">$labelsubj</label></th>
<th id="thcsummary"><label for="crdt0summary">$labelsummary</label></th>
<th id="thcamount"><label for="crdt0amount">$labelamount</label></th>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rows_new scope="row"><label for="debt0subj">$mark$label</label></th>
<th id="thdebit" colspan="3"><label for="debt0subj">$labeldebit</label></th>
<th id="thcredit" colspan="3"><label for="crdt0subj">$labelcredit</label></th>
</tr>
<tr>
<th id="thdsubj"><label for="debt0subj">$labelsubj</label></th>
<th id="thdsummary"><label for="debt0summary">$labelsummary</label></th>
<th id="thdamount"><label for="debt0amount">$labelamount</label></th>
<th id="thcsubj"><label for="crdt0subj">$labelsubj</label></th>
<th id="thcsummary"><label for="crdt0summary">$labelsummary</label></th>
<th id="thcamount"><label for="crdt0amount">$labelamount</label></th>
</tr>
EOT
}
for ($_ = 0, @_ = qw(); $_ < $count_new; $_++) {
$self->_html_coltmpl_loop_rec($_);
}
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
for (my $i = 0, $sumdebit = 0; $i < $count_new; $i++) {
$sumdebit += $_
if defined($_ = $form->param("debt$i" . "amount"))
&& /^\d+$/;
}
print << "EOT";
<tr>
<th id="thdsum" colspan="2"><label for="debttotal">$labelsum</label></th>
<td headers="threcs thdsum"><input id="debttotal" class="amount" type="text" name="debttotal" size="10" value="$sumdebit" disabled="disabled" /></td>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
for (my $i = 0, $sumcredit = 0; $i < $count_new; $i++) {
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"))
&& /^\d+$/;
}
print << "EOT";
<tr>
<th id="thcsum" colspan="2"><label for="crdttotal">$labelsum</label></th>
<td headers="threcs thcsum"><input id="crdttotal" class="amount" type="text" name="crdttotal" size="10" value="$sumcredit" disabled="disabled" /></td>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
for (my $i = 0, $sumdebit = 0, $sumcredit = 0; $i < $count_new; $i++) {
$sumdebit += $_
if defined($_ = $current->param("debt$i" . "amount"))
&& /^\d+$/;
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"))
&& /^\d+$/;
}
print << "EOT";
<tr>
<th id="thdsum" colspan="2"><label for="debttotal">$labelsum</label></th>
<td headers="threcs thdebit thdsum"><input id="debttotal" class="amount" type="text" name="debttotal" size="10" value="$sumdebit" disabled="disabled" /></td>
<th id="thcsum" colspan="2"><label for="crdttotal">$labelsum</label></th>
<td headers="threcs thcredit thcsum"><input id="crdttotal" class="amount" type="text" name="crdttotal" size="10" value="$sumcredit" disabled="disabled" /></td>
</tr>
EOT
}
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
$count_cur = 0;
$count_cur = $current->param("debtcount")
if $count_cur < $current->param("debtcount");
$count_cur = $current->param("crdtcount")
if $count_cur < $current->param("crdtcount");
$_ = $count_cur + 2;
$_++ if $self->{"subtype"} eq "trans";
$rows_cur = " rowspan=\"" . h($_) . "\"";
$rows_cur = "" if $_ == 1;
# Find the total number of records
$count_new = 0;
# Find the last-used debit record
for ( $_ = 0;
defined $form->param("debt$_" . "subj")
|| defined $form->param("debt$_" . "summary")
|| defined $form->param("debt$_" . "amount"); $_++) {};
if ($_ > 0) {
for ( $_--;
$_ > 0
&& $form->param("debt$_" . "subj") eq ""
&& $form->param("debt$_" . "summary") eq ""
&& $form->param("debt$_" . "amount") eq ""; $_--) {};
}
$_++;
$count_new = $_ if $count_new < $_;
# Find the last-used credit record
for ( $_ = 0;
defined $form->param("crdt$_" . "subj")
|| defined $form->param("crdt$_" . "summary")
|| defined $form->param("crdt$_" . "amount"); $_++) {};
if ($_ > 0) {
for ( $_--;
$_ > 0
&& $form->param("crdt$_" . "subj") eq ""
&& $form->param("crdt$_" . "summary") eq ""
&& $form->param("crdt$_" . "amount") eq ""; $_--) {};
}
$_++;
$count_new = $_ if $count_new < $_;
# We need at least 5 blank records
$count_new += 5;
$_ = $count_new + 2;
$_++ if $self->{"subtype"} eq "trans";
$rows_new = " rowspan=\"" . h($_) . "\"";
$rows_new = "" if $_ == 1;
$_ = $count_cur + $count_new + 4;
$_ += 2 if $self->{"subtype"} eq "trans";
$rowspan = " rowspan=\"" . h($_) . "\"";
$rowspan = "" if $_ == 1;
$label = h_abbr(C_("[numerate,_1,Content]:", 0));
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rowspan scope="row">$mark$label</th>
<th id="thold" class="oldnew"$rows_cur>$orig</th>
<th id="thcdsubj">$labelsubj</th>
<th id="thcdsummary">$labelsummary</th>
<th id="thcdamount">$labelamount</th>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rowspan scope="row">$mark$label</th>
<th id="thold" class="oldnew"$rows_cur>$orig</th>
<th id="thccsubj">$labelsubj</th>
<th id="thccsummary">$labelsummary</th>
<th id="thccamount">$labelamount</th>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rowspan scope="row">$mark$label</th>
<th id="thold" class="oldnew"$rows_cur>$orig</th>
<th id="thcdebit" colspan="3">$labeldebit</th>
<th id="thccredit" colspan="3">$labelcredit</th>
</tr>
<tr>
<th id="thcdsubj">$labelsubj</th>
<th id="thcdsummary">$labelsummary</th>
<th id="thcdamount">$labelamount</th>
<th id="thccsubj">$labelsubj</th>
<th id="thccsummary">$labelsummary</th>
<th id="thccamount">$labelamount</th>
</tr>
EOT
}
for ($_ = 0, @_ = qw(); $_ < $count_cur; $_++) {
$self->_html_coltmpl_ro_loop_rec($_);
}
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
for (my $i = 0, $sumdebit = 0; $i < $count_cur; $i++) {
$sumdebit += $_
if defined($_ = $current->param("debt$i" . "amount"));
}
$sumdebit = h_abbr(fmtntamount $sumdebit);
print << "EOT";
<tr>
<th id="thcdsum" colspan="2">$labelsum</th>
<td class="amount" headers="threcs thold thcdsum">$sumdebit</td>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
for (my $i = 0, $sumcredit = 0; $i < $count_cur; $i++) {
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"));
}
$sumcredit = h_abbr(fmtntamount $sumcredit);
print << "EOT";
<tr>
<th id="thccsum" colspan="2">$labelsum</th>
<td class="amount" headers="threcs thold thccsum">$sumcredit</td>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
for (my $i = 0, $sumdebit = 0, $sumcredit = 0; $i < $count_cur; $i++) {
$sumdebit += $_
if defined($_ = $current->param("debt$i" . "amount"));
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"));
}
$sumdebit = h_abbr(fmtntamount $sumdebit);
$sumcredit = h_abbr(fmtntamount $sumcredit);
print << "EOT";
<tr>
<th id="thcdsum" colspan="2">$labelsum</th>
<td class="amount" headers="threcs thold thcdebit thcdsum">$sumdebit</td>
<th id="thccsum" colspan="2">$labelsum</th>
<td class="amount" headers="threcs thold thccredit thccsum">$sumcredit</td>
</tr>
EOT
}
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
print << "EOT";
<tr>
<th id="thnew" class="oldnew"$rows_new>$new</th>
<th id="thdsubj"><label for="debt0subj">$labelsubj</label></th>
<th id="thdsummary"><label for="debt0summary">$labelsummary</label></th>
<th id="thdamount"><label for="debt0amount">$labelamount</label></th>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
print << "EOT";
<tr>
<th id="thnew" class="oldnew"$rows_new>$new</th>
<th id="thcsubj"><label for="crdt0subj">$labelsubj</label></th>
<th id="thcsummary"><label for="crdt0summary">$labelsummary</label></th>
<th id="thcamount"><label for="crdt0amount">$labelamount</label></th>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
print << "EOT";
<tr>
<th id="thnew" class="oldnew"$rows_new>$new</th>
<th id="thdebit" colspan="3"><label for="debt0subj">$labeldebit</label></th>
<th id="thcredit" colspan="3"><label for="crdt0subj">$labelcredit</label></th>
</tr>
<tr>
<th id="thdsubj"><label for="debt0subj">$labelsubj</label></th>
<th id="thdsummary"><label for="debt0summary">$labelsummary</label></th>
<th id="thdamount"><label for="debt0amount">$labelamount</label></th>
<th id="thcsubj"><label for="crdt0subj">$labelsubj</label></th>
<th id="thcsummary"><label for="crdt0summary">$labelsummary</label></th>
<th id="thcamount"><label for="crdt0amount">$labelamount</label></th>
</tr>
EOT
}
for ($_ = 0, @_ = qw(); $_ < $count_new; $_++) {
$self->_html_coltmpl_loop_rec($_);
}
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
for (my $i = 0, $sumdebit = 0; $i < $count_new; $i++) {
$sumdebit += $_
if defined($_ = $form->param("debt$i" . "amount"))
&& /^\d+$/;
}
print << "EOT";
<tr>
<th id="thdsum" colspan="2"><label for="debttotal">$labelsum</label></th>
<td headers="threcs thdsum"><input id="debttotal" class="amount" type="text" name="debttotal" size="10" value="$sumdebit" disabled="disabled" /></td>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
for (my $i = 0, $sumcredit = 0; $i < $count_new; $i++) {
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"))
&& /^\d+$/;
}
print << "EOT";
<tr>
<th id="thcsum" colspan="2"><label for="crdttotal">$labelsum</label></th>
<td headers="threcs thcsum"><input id="crdttotal" class="amount" type="text" name="crdttotal" size="10" value="$sumcredit" disabled="disabled" /></td>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
for (my $i = 0, $sumdebit = 0, $sumcredit = 0; $i < $count_new; $i++) {
$sumdebit += $_
if defined($_ = $current->param("debt$i" . "amount"))
&& /^\d+$/;
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"))
&& /^\d+$/;
}
print << "EOT";
<tr>
<th id="thdsum" colspan="2"><label for="debttotal">$labelsum</label></th>
<td headers="threcs thdebit thdsum"><input id="debttotal" class="amount" type="text" name="debttotal" size="10" value="$sumdebit" disabled="disabled" /></td>
<th id="thcsum" colspan="2"><label for="crdttotal">$labelsum</label></th>
<td headers="threcs thcredit thcsum"><input id="crdttotal" class="amount" type="text" name="crdttotal" size="10" value="$sumcredit" disabled="disabled" /></td>
</tr>
EOT
}
# A form to delete a current item
} elsif ($self->{"type"} eq "del") {
$count_cur = 0;
$count_cur = $current->param("debtcount")
if $count_cur < $current->param("debtcount");
$count_cur = $current->param("crdtcount")
if $count_cur < $current->param("crdtcount");
$_ = $count_cur + 2;
$_++ if $self->{"subtype"} eq "trans";
$rows_cur = " rowspan=\"" . h($_) . "\"";
$rows_cur = "" if $_ == 1;
$label = h_abbr(C_("[numerate,_1,Content]:", $count_cur));
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rows_cur scope="row">$mark$label</th>
<th id="thcdsubj">$labelsubj</th>
<th id="thcdsummary">$labelsummary</th>
<th id="thcdamount">$labelamount</th>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rows_cur scope="row">$mark$label</th>
<th id="thccsubj">$labelsubj</th>
<th id="thccsummary">$labelsummary</th>
<th id="thccamount">$labelamount</th>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
print << "EOT";
<tr>
<th id="threcs" class="th"$rows_cur scope="row">$mark$label</th>
<th id="thcdebit" colspan="3">$labeldebit</th>
<th id="thccredit" colspan="3">$labelcredit</th>
</tr>
<tr>
<th id="thcdsubj">$labelsubj</th>
<th id="thcdsummary">$labelsummary</th>
<th id="thcdamount">$labelamount</th>
<th id="thccsubj">$labelsubj</th>
<th id="thccsummary">$labelsummary</th>
<th id="thccamount">$labelamount</th>
</tr>
EOT
}
for ($_ = 0, @_ = qw(); $_ < $count_cur; $_++) {
$self->_html_coltmpl_ro_loop_rec($_);
}
# A form to fill in a cash expense transaction
if ($self->{"subtype"} eq "expense") {
for (my $i = 0, $sumdebit = 0; $i < $count_cur; $i++) {
$sumdebit += $_
if defined($_ = $current->param("debt$i" . "amount"));
}
$sumdebit = h_abbr(fmtntamount $sumdebit);
print << "EOT";
<tr>
<td id="thcdsum" colspan="2">$labelsum</td>
<td class="amount" headers="threcs thcdsum">$sumdebit</td>
</tr>
EOT
# A form to fill in a cash income transaction
} elsif ($self->{"subtype"} eq "income") {
for (my $i = 0, $sumcredit = 0; $i < $count_cur; $i++) {
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"));
}
$sumcredit = h_abbr(fmtntamount $sumcredit);
print << "EOT";
<tr>
<td id="thccsum" colspan="2">$labelsum</td>
<td class="amount" headers="threcs thccsum">$sumcredit</td>
</tr>
EOT
# A form to fill in a transfer transaction
} elsif ($self->{"subtype"} eq "trans") {
for (my $i = 0, $sumdebit = 0, $sumcredit = 0; $i < $count_cur; $i++) {
$sumdebit += $_
if defined($_ = $current->param("debt$i" . "amount"));
$sumcredit += $_
if defined($_ = $current->param("crdt$i" . "amount"));
}
$sumdebit = h_abbr(fmtntamount $sumdebit);
$sumcredit = h_abbr(fmtntamount $sumcredit);
print << "EOT";
<tr>
<td id="thcdsum" colspan="2">$labelsum</td>
<td class="amount" headers="threcs thcdebit thcdsum">$sumdebit</td>
<td id="thccsum" colspan="2">$labelsum</td>
<td class="amount" headers="threcs thccredit thccsum">$sumcredit</td>
</tr>
EOT
}
}
return;
}
# _html_col_note: The note
sub _html_col_note : method {
$_[0]->_html_coltmpl_textarea("note", h_abbr(C_("Note:")),
h_abbr(C_("Fill in the note here.")), undef, 2);
}
return 1;

View File

@@ -0,0 +1,498 @@
# Selima Website Content Management System
# Group.pm: The account group form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-12
package Selima::Form::Group;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::CommText;
use Selima::ChkPriv;
use Selima::ChkFunc;
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserName;
use Selima::Unicode;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "groups"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this group")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new group.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to update a current group.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a group.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(id dsc subuser subgroup supgroup)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn id dsc subuser subgroup supgroup
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add a New Group");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Update a Current Group");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Group");
}
}
$self = $class->SUPER::new($status, $args);
if ($$args{"type"} eq "cur" && !is_su && $self->{"sn"} == su_group_sn) {
$self->{"nodelete"} = 1;
push @{$self->{"prefmsg"}}, C_("This is a super-user group. You can only change parts of its infomation.");
}
return $self;
}
# _html_col_id: The group ID.
sub _html_col_id : method {
local ($_, %_);
my $self;
$self = $_[0];
# Read-only for a non-super-user editing a super-user
if ($self->{"type"} eq "cur" && !is_su && $self->{"sn"} == su_group_sn) {
$self->_html_coltmpl_ro("id", h_abbr(C_("Group ID.:")));
} else {
$self->_html_coltmpl_text("id", h_abbr(C_("Group ID.:")));
}
}
# _html_col_dsc: The description
sub _html_col_dsc : method {
$_[0]->_html_coltmpl_text("dsc", h_abbr(C_("Description:")));
}
# _html_col_subuser: Its child users
sub _html_col_subuser : method {
local ($_, %_);
my ($self, $form, $current, $label, $orig, $new, $submit, $mark, $colspan, $title);
#my ($col, $val, $colsn, $valsn, $title);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("subuser");
$colspan = $self->_colspan;
$submit = h_abbr(C_("Add a user"));
# A form to create a new item
if ($self->{"type"} eq "new") {
$label = h_abbr(C_("[numerate,_1,User member]:", 0));
print << "EOT";
<tr>
<th class="th" scope="row"><label for="selsubuser">$mark$label</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("subuser$_" . "sn"); $_++) {
if (defined($title = user_opt_label(scalar $form->param("subuser$_" . "sn"), "subuser$_"))) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subuser%1\$dsn\"%2\$s />\n"
. " <input id=\"subuser%1\$d\" type=\"checkbox\" name=\"subuser%1\$d\"%3\$s />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subuser$_" . "sn"),
$self->_val_check("subuser$_"), $title);
} else {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subuser%1\$dsn\"%2\$s />\n"
. " <input type=\"checkbox\"%3\$s disabled=\"disabled\" />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subuser$_" . "sn"),
$self->_val_check("subuser$_"), h_abbr(t_na));
}
}
push @_, " <li><input id=\"selsubuser\" type=\"submit\" name=\"selsubuser\" value=\"$submit\" /></li>\n";
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
# Read-only for a non-super-user editing a super-user group
if (!is_su && $self->{"sn"} == su_group_sn) {
$label = h_abbr(C_("[numerate,_1,User member]:", $_[0]->_delcolcount("subuser")));
print << "EOT";
<tr>
<th colspan="2" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("subusercount"); $_++) {
push @_, " <li>" . $self->_cval_text("subuser$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
} else {
$label = h_abbr(C_("[numerate,_1,User member]:", 0));
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
print << "EOT";
<tr>
<th class="th" rowspan="2" scope="row"><label for="selsubuser">$mark$label</label></th>
<th class="oldnew" scope="row">$orig</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("subusercount"); $_++) {
push @_, " <li>" . $self->_cval_text("subuser$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
<tr>
<th class="oldnew" scope="row"><label for="selsubuser">$new</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("subuser$_" . "sn"); $_++) {
if (defined($title = user_opt_label(scalar $form->param("subuser$_" . "sn"), "subuser$_"))) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subuser%1\$dsn\"%2\$s />\n"
. " <input id=\"subuser%1\$d\" type=\"checkbox\" name=\"subuser%1\$d\"%3\$s />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subuser$_" . "sn"),
$self->_val_check("subuser$_"), $title);
} else {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subuser%1\$dsn\"%2\$s />\n"
. " <input type=\"checkbox\"%3\$s disabled=\"disabled\" />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subuser$_" . "sn"),
$self->_val_check("subuser$_"), h_abbr(t_na));
}
}
push @_, " <li><input id=\"selsubuser\" type=\"submit\" name=\"selsubuser\" value=\"$submit\" /></li>\n";
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
}
# A form to delete a current item
} else {
$label = h_abbr(C_("[numerate,_1,User member]:", $_[0]->_delcolcount("subuser")));
print << "EOT";
<tr>
<th class="th" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("subusercount"); $_++) {
push @_, " <li>" . $self->_cval_text("subuser$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
}
return;
}
# _html_col_subgroup: Its child groups
sub _html_col_subgroup : method {
local ($_, %_);
my ($self, $form, $current, $label, $orig, $new, $submit, $mark, $colspan, $title);
#my ($col, $val, $colsn, $valsn, $title);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("subgroup");
$colspan = $self->_colspan;
$submit = h_abbr(C_("Add a group"));
# A form to create a new item
if ($self->{"type"} eq "new") {
$label = h_abbr(C_("[numerate,_1,Group member]:", 0));
print << "EOT";
<tr>
<th class="th" scope="row"><label for="selsubgroup">$mark$label</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("subgroup$_" . "sn"); $_++) {
if (defined($title = group_opt_label(scalar $form->param("subgroup$_" . "sn"), "subgroup$_"))) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subgroup%1\$dsn\"%2\$s />\n"
. " <input id=\"subgroup%1\$d\" type=\"checkbox\" name=\"subgroup%1\$d\"%3\$s />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subgroup$_" . "sn"),
$self->_val_check("subgroup$_"), $title);
} else {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subgroup%1\$dsn\"%2\$s />\n"
. " <input type=\"checkbox\"%3\$s disabled=\"disabled\" />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subgroup$_" . "sn"),
$self->_val_check("subgroup$_"), h_abbr(t_na));
}
}
push @_, " <li><input id=\"selsubgroup\" type=\"submit\" name=\"selsubgroup\" value=\"$submit\" /></li>\n";
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
# Read-only for a non-super-user editing a super-user group
if (!is_su && $self->{"sn"} == su_group_sn) {
$label = h_abbr(C_("[numerate,_1,Group member]:", $_[0]->_delcolcount("subgroup")));
print << "EOT";
<tr>
<th colspan="2" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("subgroupcount"); $_++) {
push @_, " <li>" . $self->_cval_text("subgroup$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
} else {
$label = h_abbr(C_("[numerate,_1,Group member]:", 0));
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
print << "EOT";
<tr>
<th class="th" rowspan="2" scope="row"><label for="selsubgroup">$mark$label</label></th>
<th class="oldnew" scope="row">$orig</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("subgroupcount"); $_++) {
push @_, " <li>" . $self->_cval_text("subgroup$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
<tr>
<th class="oldnew" scope="row"><label for="selsubgroup">$new</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("subgroup$_" . "sn"); $_++) {
if (defined($title = group_opt_label(scalar $form->param("subgroup$_" . "sn"), "subgroup$_"))) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subgroup%1\$dsn\"%2\$s />\n"
. " <input id=\"subgroup%1\$d\" type=\"checkbox\" name=\"subgroup%1\$d\"%3\$s />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subgroup$_" . "sn"),
$self->_val_check("subgroup$_"), $title);
} else {
push @_, sprintf(" <li><input type=\"hidden\" name=\"subgroup%1\$dsn\"%2\$s />\n"
. " <input type=\"checkbox\"%3\$s disabled=\"disabled\" />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("subgroup$_" . "sn"),
$self->_val_check("subgroup$_"), h_abbr(t_na));
}
}
push @_, " <li><input id=\"selsubgroup\" type=\"submit\" name=\"selsubgroup\" value=\"$submit\" /></li>\n";
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
}
# A form to delete a current item
} else {
$label = h_abbr(C_("[numerate,_1,Group member]:", $_[0]->_delcolcount("subgroup")));
print << "EOT";
<tr>
<th class="th" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("subgroupcount"); $_++) {
push @_, " <li>" . $self->_cval_text("subgroup$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
}
return;
}
# _html_col_supgroup: Its belonging groups
sub _html_col_supgroup : method {
local ($_, %_);
my ($self, $form, $current, $label, $orig, $new, $submit, $mark, $colspan, $title);
#my ($col, $val, $colsn, $valsn, $title);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("supgroup");
$colspan = $self->_colspan;
$label = h_abbr(C_("Belonging to:"));
$submit = h_abbr(C_("Add a group"));
# A form to create a new item
if ($self->{"type"} eq "new") {
print << "EOT";
<tr>
<th class="th" scope="row"><label for="selsupgroup">$mark$label</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("supgroup$_" . "sn"); $_++) {
if (defined($title = group_opt_label(scalar $form->param("supgroup$_" . "sn"), "supgroup$_"))) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"supgroup%1\$dsn\"%2\$s />\n"
. " <input id=\"supgroup%1\$d\" type=\"checkbox\" name=\"supgroup%1\$d\"%3\$s />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("supgroup$_" . "sn"),
$self->_val_check("supgroup$_"), $title);
} else {
push @_, sprintf(" <li><input type=\"hidden\" name=\"supgroup%1\$dsn\"%2\$s />\n"
. " <input type=\"checkbox\"%3\$s disabled=\"disabled\" />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("supgroup$_" . "sn"),
$self->_val_check("supgroup$_"), h_abbr(t_na));
}
}
push @_, " <li><input id=\"selsupgroup\" type=\"submit\" name=\"selsupgroup\" value=\"$submit\" /></li>\n";
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
# Read-only for a non-super-user editing a super-user group
if (!is_su && $self->{"sn"} == su_group_sn) {
print << "EOT";
<tr>
<th colspan="2" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("supgroupcount"); $_++) {
push @_, " <li>" . $self->_cval_text("supgroup$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
} else {
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
print << "EOT";
<tr>
<th class="th" rowspan="2" scope="row"><label for="selsupgroup">$mark$label</label></th>
<th class="oldnew" scope="row">$orig</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("supgroupcount"); $_++) {
push @_, " <li>" . $self->_cval_text("supgroup$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
<tr>
<th class="oldnew" scope="row"><label for="selsupgroup">$new</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("supgroup$_" . "sn"); $_++) {
if (defined($title = group_opt_label(scalar $form->param("supgroup$_" . "sn"), "supgroup$_"))) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"supgroup%1\$dsn\"%2\$s />\n"
. " <input id=\"supgroup%1\$d\" type=\"checkbox\" name=\"supgroup%1\$d\"%3\$s />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("supgroup$_" . "sn"),
$self->_val_check("supgroup$_"), $title);
} else {
push @_, sprintf(" <li><input type=\"hidden\" name=\"supgroup%1\$dsn\"%2\$s />\n"
. " <input type=\"checkbox\"%3\$s disabled=\"disabled\" />\n"
. " %4\$s\n"
. " </li>\n",
h($_), $self->_val_text("supgroup$_" . "sn"),
$self->_val_check("supgroup$_"), h_abbr(t_na));
}
}
push @_, " <li><input id=\"selsupgroup\" type=\"submit\" name=\"selsupgroup\" value=\"$submit\" /></li>\n";
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
}
# A form to delete a current item
} else {
print << "EOT";
<tr>
<th class="th" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("supgroupcount"); $_++) {
push @_, " <li>" . $self->_cval_text("supgroup$_" . "title") . "</li>\n";
}
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
}
return;
}
return 1;

View File

@@ -0,0 +1,93 @@
# Selima Website Content Management System
# GroupMem.pm: The group-to-group membership form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Form::GroupMem;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserName;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "groupmem"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this membership record")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new membership record.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to change a current membership record.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a membership record.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(grp member)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn grp member
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add A New Group Membership Record");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Change a Current Group Membership Record");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Group Membership Record");
}
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_member: The member
sub _html_col_member : method {
$_[0]->_html_coltmpl_call("member", h_abbr(C_("Member:")), \&groupdsc);
}
return 1;

View File

@@ -0,0 +1,116 @@
# Selima Website Content Management System
# Guestbook.pm: The base administrative guestbook form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-16
package Selima::Form::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "guestbook"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this message")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new message.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current message.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a message.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(name identity location email url message hid)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn name identity location email url message hid
ip host ct pageno oldpageno
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Write a New Message");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Edit a Current Message");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Message");
}
}
$self = $class->SUPER::new($status, $args);
${$self->{"maxlens"}}{"message"} = 10240;
return $self;
}
# _html_col_ct: The country
sub _html_col_ct : method {
$_[0]->_html_coltmpl_ro_ct("ct", h_abbr(C_("Country:")));
}
# _html_col_hid: Hide?
sub _html_col_hid : method {
$_[0]->_html_coltmpl_bool("hid", h_abbr(C_("Hide?")),
h_abbr(C_("Hide this message")), h_abbr(C_("Show this message")),
h_abbr(C_("Hide this message currently.")));
}
# _html_col_name: The name
sub _html_col_name : method {
$_[0]->_html_coltmpl_text("name", h_abbr(C_("Signature:")));
}
# _html_col_oldpageno: The old page number
sub _html_col_oldpageno : method {
$_[0]->_html_coltmpl_ro("oldpageno", h_abbr(C_("Old page no.:")));
}
# _html_col_pageno: The page number
sub _html_col_pageno : method {
$_[0]->_html_coltmpl_ro("pageno", h_abbr(C_("Page no.:")));
}
return 1;

View File

@@ -0,0 +1,86 @@
# Selima Website Content Management System
# Public.pm: The base guestbook form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-16
package Selima::Form::Guestbook::Public;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form::Guestbook);
use Selima::DataVars qw(:lninfo);
use Selima::GetLang;
use Selima::HTTP;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
# This should always be always a new form
$$args{"type"} = "new"
if !exists $$args{"type"};
$$args{"type_to_pass"} = undef
if !exists $$args{"type_to_pass"};
$$args{"valid_types"} = [qw(new)]
if !exists $$args{"valid_types"};
$$args{"table"} = "guestbook"
if !exists $$args{"table"};
$$args{"header_buttons"} = []
if !exists $$args{"header_buttons"};
$$args{"footer_buttons"} = [
{ "name" => "submit", "value" => h(C_("Leave a messsage")) } ]
if !exists $$args{"footer_buttons"};
$$args{"summary"} = C_("This table provides you a form to leave a message.")
if !exists $$args{"summary"};
$$args{"onsubmit"} = "return isGuestbookOK(this);"
if !exists $$args{"onsubmit"};
$$args{"cols"} = [qw(message name identity location email captcha url)]
if !exists $$args{"cols"};
$$args{"auto_referer2"} = 0
if !exists $$args{"auto_referer2"};
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_message: The message
sub _html_col_message : method {
local ($_, %_);
my ($self, $val, $label, $default, $colspan, $hdef);
$self = $_[0];
$colspan = $self->_colspan_full;
$default = C_("Fill in your message here.");
$val = $self->_val_textarea("message", $default);
$hdef = h($default);
print << "EOT";
<tr>
<td class="gbmessage"$colspan><textarea id="message" name="message" cols="50" rows="10"
onfocus="if (this.value == &quot;$hdef&quot;) this.value = &quot;&quot;;">$val</textarea></td>
</tr>
EOT
return;
}
return 1;

View File

@@ -0,0 +1,190 @@
# Selima Website Content Management System
# Link.pm: The related-link form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::Form::Link;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::ChkFunc;
use Selima::CommText;
use Selima::FormFunc;
use Selima::HTTP;
use Selima::Links;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "links"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this related link")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new related link.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current related link.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a related link.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(title title_2ln url icon email
addr tel fax dsc hid cats)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn title title_2ln url icon email
addr tel fax dsc hid cats
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add a New Related Link");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Edit a Current Related Link");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Related Link");
}
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_cats: The categories
sub _html_col_cats : method {
$_[0]->_html_coltmpl_select_multi("cat",
h_abbr(C_("[numerate,_1,Category,Categories]:", $_[0]->_delcolcount("cat"))),
\&linkcat_options);
}
# _html_col_hid: Hide?
sub _html_col_hid : method {
$_[0]->_html_coltmpl_bool("hid", h_abbr(C_("Hide?")),
h_abbr(C_("Hide this related link")), h_abbr(C_("Show this related link")),
h_abbr(C_("Hide this related link currently.")));
}
# _html_col_icon: The link icon
sub _html_col_icon : method {
local ($_, %_);
my ($self, $label, $alt, $size, $mark, $colspan);
my ($cur, $preview, $val, $orig, $new);
$self = $_[0];
$mark = $self->_mark("icon");
$colspan = $self->_colspan;
$label = h_abbr(C_("Link icon:"));
$alt = h(C_("Link icon unavailable"));
$size = h($self->{"defsize"});
$self->{"form"}->param("icon", "http://")
if $self->{"is_first_form"} && !defined $self->{"form"}->param("icon");
# A form to create a new item
if ($self->{"type"} eq "new") {
$val = $self->_val_text("icon", "icon");
if (is_url_wellformed $self->{"form"}->param("icon")) {
$preview = "<img src=\"" . h($self->{"form"}->param("icon"))
. "\" alt=\"$alt\" /><br />\n";
} else {
$preview = "";
}
print << "EOT";
<tr>
<th class="th" scope="row"><label for="icon">$mark$label</label></th>
<td$colspan>$preview<input id="icon" class="text" type="text" name="icon" size="$size"$val /></td>
</tr>
EOT
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
$cur = $self->{"cur"}->param("icon");
if (defined $cur) {
$cur = "<img src=\"" . h($cur) . "\" alt=\"$alt\" /><br />\n"
. " " . h($cur);
} else {
$cur = h_abbr(t_none);
}
$val = $self->_val_text("icon", "icon");
if (is_url_wellformed $self->{"form"}->param("icon")) {
$preview = "<img src=\"" . h($self->{"form"}->param("icon"))
. "\" alt=\"$alt\" /><br />\n";
} else {
$preview = "";
}
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
print << "EOT";
<tr>
<th class="th" rowspan="2" scope="row"><label for="icon">$mark$label</label></th>
<th class="oldnew" scope="row">$orig</th>
<td$colspan>$cur</td>
</tr>
<tr>
<th class="oldnew" scope="row"><label for="icon">$new</label></th>
<td$colspan>$preview<input id="icon" class="text" type="text" name="icon" size="$size"$val /></td>
</tr>
EOT
# A form to delete a current item
} elsif ($self->{"type"} eq "del") {
$cur = $self->{"cur"}->param("icon");
if (defined $cur) {
$cur = "<img src=\"" . h($cur) . "\" alt=\"$alt\" /><br />\n"
. " " . h($cur);
} else {
$cur = h_abbr(t_none);
}
print << "EOT";
<tr>
<th class="th" scope="row">$mark$label</th>
<td$colspan>$cur</td>
</tr>
EOT
}
return;
}
# _html_col_title_2ln: Title in the second language
sub _html_col_title_2ln : method {
$_[0]->_html_coltmpl_text("title_2ln", h_abbr(C_("2nd language title:")));
}
return 1;

View File

@@ -0,0 +1,145 @@
# Selima Website Content Management System
# LinkCat.pm: The related-link category form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::Form::LinkCat;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::CommText;
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "linkcat"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this category")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new category.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current category.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a category.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(parent id ord title kw hid)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn parent id ord title kw hid
scats links
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add a New Link Category");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Edit a Current Link Category");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Link Category");
}
}
$self = $class->SUPER::new($status, $args);
${$self->{"maxlens"}}{"ord"} = 2;
if ($self->{"type"} eq "cur") {
if (defined $self->{"cur"}->param("scatcount") && $self->{"cur"}->param("scatcount") > 0) {
$self->{"nodelete"} = 1;
push @{$self->{"prefmsg"}}, C_("This category has [numerate,_1,a subcategory,subcategories]. It cannot be deleted. To delete the category, [numerate,_1,its subcategory,all of its subcategories] must first be deleted.", $self->{"cur"}->param("scatcount"));
}
if (defined $self->{"cur"}->param("linkcount") && $self->{"cur"}->param("linkcount") > 0) {
$self->{"nodelete"} = 1;
push @{$self->{"prefmsg"}}, C_("This category has [numerate,_1,a link,links]. It cannot be deleted. To delete the category, [numerate,_1,its link,all of its links] must first be deleted.", $self->{"cur"}->param("linkcount"));
}
}
return $self;
}
# _html_col_hid: Hide?
sub _html_col_hid : method {
$_[0]->_html_coltmpl_bool("hid", h_abbr(C_("Hide?")),
h_abbr(C_("Hide this category")), h_abbr(C_("Show this category")),
h_abbr(C_("Hide this category currently.")));
}
# _html_col_id: The ID.
sub _html_col_id : method {
$_[0]->_html_coltmpl_text("id", h_abbr(C_("ID.:")), undef, 8);
}
# _html_col_links: The links
sub _html_col_links : method {
my ($self, $form, $current, $label, $mark, $colspan, $thclass, $thcolspan);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("links");
$colspan = $self->_colspan;
$label = h_abbr(C_("[numerate,_1,Link,Links]:", $current->param("linkcount")));
# A current form span for 2 columns
$thclass = $self->{"type"} ne "cur"? " class=\"th\"": "";
$thcolspan = $self->{"type"} eq "cur"? " colspan=\"2\"": "";
print << "EOT";
<tr>
<th$thclass$thcolspan scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("linkcount"); $_++) {
push @_, sprintf(" <li><a href=\"%1\$s\">%2\$s</a>\n"
. " (<a href=\"%3\$s\"><samp>%3\$s</samp></a>)\n"
. " </li>\n",
h("links.cgi?form=cur&sn=" . $current->param("link$_" . "sn")),
h_abbr($current->param("link$_" . "title")),
h($current->param("link$_" . "url")));
}
print @_ > 0? "<ul>\n" . join("", @_) ." </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
}
return 1;

View File

@@ -0,0 +1,98 @@
# Selima Website Content Management System
# LinkCatz.pm: The related-link category membership form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-25
package Selima::Form::LinkCatz;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::Links;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "linkcatz"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this categorization record")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new categorization record.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to change a current categorization record.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a categorization record.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(cat link)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn cat link
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add A New Link Categorization Record");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Change a Current Link Categorization Record");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Link Categorization Record");
}
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_cat: The category
sub _html_col_cat : method {
$_[0]->_html_coltmpl_call("cat", h_abbr(C_("Category:")), \&linkcat_title);
}
# _html_col_link: The link
sub _html_col_link : method {
$_[0]->_html_coltmpl_call("link", h_abbr(C_("Link:")), \&link_title);
}
return 1;

View File

@@ -0,0 +1,100 @@
# Selima Website Content Management System
# Page.pm: The base web page form.
# Copyright (c) 2005-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2005-02-28
package Selima::Form::Page;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "pages"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this page")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to write a new page.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to edit a current page.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a page.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(path ord title body kw html hid)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn path ord title body kw html hid
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Write a New Page");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Edit a Current Page");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Page");
}
}
if (!exists $$args{"preview"}) {
$$args{"preview"} = 1;
}
if ($$args{"preview"} && !exists $$args{"prevmsg"}) {
$$args{"prevmsg"} = C_("Preview this page.");
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_hid: Hide?
sub _html_col_hid : method {
$_[0]->_html_coltmpl_bool("hid", h_abbr(C_("Hide?")),
h_abbr(C_("Hide this page")), h_abbr(C_("Show this page")),
h_abbr(C_("Hide this page currently.")));
}
return 1;

View File

@@ -0,0 +1,70 @@
# Selima Website Content Management System
# Rebuild.pm: The web page rebuild form.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-04-04
package Selima::Form::Rebuild;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::PageFunc;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = "new"
if !exists $$args{"type"};
$$args{"type_to_pass"} = undef
if !exists $$args{"type_to_pass"};
$$args{"valid_types"} = [qw(new)]
if !exists $$args{"valid_types"};
$$args{"cols"} = [qw(type)]
if !exists $$args{"cols"};
$$args{"title"} = C_("Rebuild the Pages")
if !exists $$args{"title"};
$$args{"header_buttons"} = []
if !exists $$args{"header_buttons"};
$$args{"footer_buttons"} = [
{ "name" => "confirm", "value" => h(C_("Confirm")) },
{ "name" => "cancel", "value" => h(C_("Cancel")) } ]
if !exists $$args{"footer_buttons"};
$$args{"auto_referer2"} = 0
if !exists $$args{"auto_referer2"};
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_type: The page type
sub _html_col_type : method {
$_[0]->_html_coltmpl_select("type", h_abbr(C_("Type:")),
\&rebuildtype_options, undef);
}
return 1;

View File

@@ -0,0 +1,93 @@
# Selima Website Content Management System
# ScptPriv.pm: The script privilege form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Form::ScptPriv;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserName;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "scptpriv"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this script privilege record")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new script privilege record.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to change a current script privilege record.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a script privilege record.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(script grp)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn script grp
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add A New Script Privilege Record");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Change a Current Script Privilege Record");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a Script Privilege Record");
}
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_grp: The privileged group
sub _html_col_grp : method {
$_[0]->_html_coltmpl_call("grp", h_abbr(C_("Privilege:")), \&groupdsc);
}
return 1;

View File

@@ -0,0 +1,389 @@
# Selima Website Content Management System
# User.pm: The user account form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-30
package Selima::Form::User;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::ChkPriv;
use Selima::CommText;
use Selima::DataVars qw($DBH :groups :l10n :lninfo);
use Selima::FormFunc;
use Selima::GetLang;
use Selima::HTTP;
use Selima::LnInfo;
use Selima::LogIn;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserName;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "users"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this user account")
if !exists $$args{"deltext"};
$$args{"https"} = ($$args{"type"} ne "del")
if !exists $$args{"https"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new user account.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to update a current user account.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a user account.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(id passwd name disabled supgroup)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn id passwd name disabled supgroup
admin lang visits visited ip host ct
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add a New User Account");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Update a Current User Account");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a User Account");
}
}
$self = $class->SUPER::new($status, $args);
${$self->{"maxlens"}}{"passwd"} = 16;
if ( $$args{"type"} eq "cur" && !is_su
&& ($self->{"cur"}->param("su") || $self->{"sn"} == get_login_sn)) {
$self->{"nodelete"} = 1;
push @{$self->{"prefmsg"}}, C_("This is a super-user. You can only change parts of her/his infomation.")
if $self->{"cur"}->param("su");
}
# Set all the available belonging groups list
$self->_set_supgroup_list();
return $self;
}
# _set_supgroup_list: Set all the available belonging groups list
sub _set_supgroup_list : method {
local ($_, %_);
my ($self, $form, %checked, $sth, $sql, $count);
my ($lndb, $lndbdef, $title);
$self = $_[0];
$form = $self->{"form"};
# Get the list of checked groups
%checked = qw();
for ($_ = 0; defined $form->param("supgroup$_" . "sn"); $_++) {
$checked{$form->param("supgroup$_" . "sn")} = 1
if defined $form->param("supgroup$_");
}
# Remove the old groups list
foreach ($form->param) {
$form->delete($_) if /^supgroup/;
}
# Get the list of all groups
if (@ALL_LINGUAS > 1) {
$lndb = getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
$title = $DBH->strcat("id", "' ('", "dsc_$lndb", "')'");
} else {
$lndbdef = ln $DEFAULT_LANG, LN_DATABASE;
$title = $DBH->strcat("id", "' ('",
"COALESCE(dsc_$lndb, dsc_$lndbdef)", "')'");
}
} else {
$title = $DBH->strcat("id", "' ('", "dsc", "')'");
}
$sql = "SELECT sn AS sn, $title AS title FROM groups"
. " WHERE id!=" . $DBH->quote(SU_GROUP)
. " AND id!=" . $DBH->quote(ADMIN_GROUP)
. " AND id!=" . $DBH->quote(ALLUSERS_GROUP)
. " ORDER BY id;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0; $_ < $count; $_++) {
%_ = %{$sth->fetchrow_hashref};
$form->param("supgroup$_" . "sn", $_{"sn"});
$form->param("supgroup$_" . "title", $_{"title"});
$form->param("supgroup$_", 1) if exists $checked{$_{"sn"}};
}
return;
}
# _html_col_admin: Is this user an administrator?
sub _html_col_admin : method {
$_[0]->_html_coltmpl_ro_bool("admin", h_abbr(C_("Administrator?")),
h_abbr(C_("Administrator")), h_abbr(C_("Non-administrator")));
}
# _html_col_ct: The country
sub _html_col_ct : method {
$_[0]->_html_coltmpl_ro_ct("ct", h_abbr(C_("Country:")));
}
# _html_col_disabled: Disabled?
sub _html_col_disabled : method {
local ($_, %_);
my $self;
$self = $_[0];
# Read-only for a non-super-user editing herself or a super-user
if ( $self->{"type"} eq "cur" && !is_su
&& ($self->{"cur"}->param("su") || $self->{"sn"} == get_login_sn)) {
$self->_html_coltmpl_ro_bool("disabled", h_abbr(C_("Disabled?")),
h_abbr(C_("Disabled")), h_abbr(C_("Enabled")));
} else {
$self->_html_coltmpl_bool("disabled", h_abbr(C_("Disabled?")),
h_abbr(C_("Disabled")), h_abbr(C_("Enabled")),
h_abbr(C_("Disable this user account.")));
}
}
# _html_col_id: The user ID.
sub _html_col_id : method {
local ($_, %_);
my $self;
$self = $_[0];
# Read-only for a non-super-user editing a super-user
if ($self->{"type"} eq "cur" && !is_su && $self->{"cur"}->param("su")) {
$self->_html_coltmpl_ro("id", h_abbr(C_("User ID.:")));
} else {
$self->_html_coltmpl_text("id", h_abbr(C_("User ID.:")));
}
}
# _html_col_lang: The preferred language
sub _html_col_lang : method {
$_[0]->_html_coltmpl_ro_lang("lang", h_abbr(C_("Pref. language:")));
}
# _html_col_name: The name
sub _html_col_name : method {
$_[0]->_html_coltmpl_text("name", h_abbr(C_("Full name:")));
}
# _html_col_passwd: The password
sub _html_col_passwd : method {
local ($_, %_);
my ($self, $label, $dummy, $mark, $colspan);
$self = $_[0];
# Read-only for a non-super-user editing a super-user
if ($self->{"type"} eq "cur" && !is_su && $self->{"cur"}->param("su")) {
$mark = $self->_mark("passwd");
$colspan = $self->_colspan;
$label = h_abbr(C_("Password:"));
$dummy = h("*" x ${$self->{"maxlens"}}{"passwd"});
print << "EOT";
<tr>
<th colspan="2" scope="row">$mark$label</th>
<td$colspan>$dummy</td>
</tr>
EOT
} else {
$self->SUPER::_html_col_passwd();
}
}
# _html_col_supgroup: Its belonging groups
sub _html_col_supgroup : method {
local ($_, %_);
my ($self, $form, $current, $label, $orig, $new, $mark, $colspan);
$self = $_[0];
$form = $self->{"form"};
$current = $self->{"cur"};
$mark = $self->_mark("supgroup");
$colspan = $self->_colspan;
$label = h_abbr(C_("Belonging to:"));
# A form to create a new item
if ($self->{"type"} eq "new") {
print << "EOT";
<tr>
<th class="th" scope="row"><label for="supgroup0">$mark$label</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("supgroup$_" . "sn"); $_++) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"supgroup%1\$dsn\"%2\$s />\n"
. " <input id=\"supgroup%1\$d\" type=\"checkbox\" name=\"supgroup%1\$d\"%3\$s />\n"
. " <label for=\"supgroup%1\$d\">%4\$s</label>\n"
. " </li>\n",
h($_), $self->_val_text("supgroup$_" . "sn"),
$self->_val_check("supgroup$_"),
h_abbr($form->param("supgroup$_" . "title")));
}
# Only super users can set the super-user group
if (su_group_sn != 0) {
if (is_su) {
push @_, sprintf(" <li><input id=\"su\" type=\"checkbox\" name=\"su\"%s />\n"
. " %s\n"
. " </li>\n",
$self->_val_check("su"),
group_opt_label(su_group_sn, "su"));
} else {
push @_, sprintf(" <li><input id=\"su\" type=\"checkbox\" disabled=\"disabled\" />\n"
. " %s\n"
. " </li>\n",
group_opt_label(su_group_sn));
}
}
# Attach the all-users group in any case
push @_, sprintf(" <li><input id=\"su\" type=\"checkbox\" checked=\"checked\" disabled=\"disabled\" />\n"
. " %s\n"
. " </li>\n",
group_opt_label(groupsn(ALLUSERS_GROUP)))
if defined groupsn(ALLUSERS_GROUP);
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
# A form to edit a current item
} elsif ($self->{"type"} eq "cur") {
# Read-only for a non-super-user editing herself or a super-user
if (!is_su && $self->{"sn"} == get_login_sn) {
print << "EOT";
<tr>
<th colspan="2" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("supgroupcount"); $_++) {
push @_, " <li>" . h_abbr($current->param("supgroup$_" . "title")) . "</li>\n";
}
push @_, " <li>" . group_opt_label(su_group_sn) . "</li>\n"
if $current->param("su");
push @_, " <li>" . group_opt_label(groupsn(ALLUSERS_GROUP)) . "</li>\n"
if defined groupsn(ALLUSERS_GROUP);
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
} else {
$orig = h_abbr(C_("Original:"));
$new = h_abbr(C_("New:"));
print << "EOT";
<tr>
<th class="th" rowspan="2" scope="row"><label for="supgroup0">$mark$label</label></th>
<th class="oldnew" scope="row">$orig</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("supgroupcount"); $_++) {
push @_, " <li>" . h_abbr($current->param("supgroup$_" . "title")) . "</li>\n";
}
push @_, " <li>" . group_opt_label(su_group_sn) . "</li>\n"
if $current->param("su");
push @_, " <li>" . group_opt_label(groupsn(ALLUSERS_GROUP)) . "</li>\n"
if defined groupsn(ALLUSERS_GROUP);
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
<tr>
<th class="oldnew" scope="row"><label for="supgroup0">$new</label></th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); defined $form->param("supgroup$_" . "sn"); $_++) {
push @_, sprintf(" <li><input type=\"hidden\" name=\"supgroup%1\$dsn\"%2\$s />\n"
. " <input id=\"supgroup%1\$d\" type=\"checkbox\" name=\"supgroup%1\$d\"%3\$s />\n"
. " <label for=\"supgroup%1\$d\">%4\$s</label>\n"
. " </li>\n",
h($_), $self->_val_text("supgroup$_" . "sn"),
$self->_val_check("supgroup$_"),
h_abbr($form->param("supgroup$_" . "title")));
}
# Only super users can set the super-user group
if (su_group_sn != 0) {
if (is_su) {
push @_, sprintf(" <li><input id=\"su\" type=\"checkbox\" name=\"su\"%s />\n"
. " %s\n"
. " </li>\n",
$self->_val_check("su"),
group_opt_label(su_group_sn, "su"));
} else {
push @_, sprintf(" <li><input id=\"su\" type=\"checkbox\"%s disabled=\"disabled\" />\n"
. " %s\n"
. " </li>\n",
$current->param("su")? " checked=\"checked\"": "",
group_opt_label(su_group_sn));
}
}
# Attach the all-users group in any case
push @_, sprintf(" <li><input id=\"su\" type=\"checkbox\" checked=\"checked\" disabled=\"disabled\" />\n"
. " %s\n"
. " </li>\n",
group_opt_label(groupsn(ALLUSERS_GROUP)))
if defined groupsn(ALLUSERS_GROUP);
print "<ul>\n" . join("", @_) . " </ul>\n ";
print << "EOT";
</td>
</tr>
EOT
}
# A form to delete a current item
} else {
print << "EOT";
<tr>
<th class="th" scope="row">$mark$label</th>
EOT
print " <td$colspan>";
for ($_ = 0, @_ = qw(); $_ < $current->param("supgroupcount"); $_++) {
push @_, " <li>" . h_abbr($current->param("supgroup$_" . "title")) . "</li>\n";
}
push @_, " <li>" . group_opt_label(su_group_sn) . "</li>\n"
if $current->param("su");
push @_, " <li>" . group_opt_label(groupsn(ALLUSERS_GROUP)) . "</li>\n"
if defined groupsn(ALLUSERS_GROUP);
print @_ > 0? "<ul>\n" . join("", @_) . " </ul>\n ": h_abbr(t_none);
print << "EOT";
</td>
</tr>
EOT
}
return;
}
return 1;

View File

@@ -0,0 +1,93 @@
# Selima Website Content Management System
# UserMem.pm: The user-to-group membership form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Form::UserMem;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserName;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "usermem"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this membership record")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new membership record.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to change a current membership record.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a membership record.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(grp member)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn grp member
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add A New User Membership Record");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Change a Current User Membership Record");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a User Membership Record");
}
}
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_member: The member
sub _html_col_member : method {
$_[0]->_html_coltmpl_call("member", h_abbr(C_("Member:")), \&username);
}
return 1;

View File

@@ -0,0 +1,111 @@
# Selima Website Content Management System
# UserPref.pm: The user preference form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::Form::UserPref;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form);
use Selima::DataVars qw(:scptconf);
use Selima::FormFunc;
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::Unicode;
use Selima::UserName;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"type"} = form_type
if !exists $$args{"type"};
$$args{"table"} = "usermem"
if !exists $$args{"table"};
$$args{"deltext"} = C_("Delete this user preference")
if !exists $$args{"deltext"};
if (!exists $$args{"summary"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"summary"} = C_("This table provides you a form to add a new user preference.");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"summary"} = C_("This table provides you a form to modify a current user preference.");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"summary"} = C_("This table provides you a form to delete a user preference.");
}
}
if (!exists $$args{"cols"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"cols"} = [qw(usr domain name value)];
# A form to edit a current item
# A form to delete a current item
} elsif ($$args{"type"} eq "cur" || $$args{"type"} eq "del") {
$$args{"cols"} = [qw(sn usr domain name value
created createdby updated updatedby)];
}
}
if (!exists $$args{"title"}) {
# A form to create a new item
if ($$args{"type"} eq "new") {
$$args{"title"} = C_("Add A New User Preference");
# A form to edit a current item
} elsif ($$args{"type"} eq "cur") {
$$args{"title"} = C_("Modify a Current User Preference");
# A form to delete a current item
} elsif ($$args{"type"} eq "del") {
$$args{"title"} = C_("Delete a User Preference");
}
}
$self = $class->SUPER::new($status, $args);
$self->{"form"}->delete("usr")
if defined $self->{"form"}->param("usr")
&& (!defined $self->{"form"}->param("usr")
|| $self->{"form"}->param("usr") eq "");
return $self;
}
# _html_col_domain: The preference domain
sub _html_col_domain : method {
$_[0]->_html_coltmpl_text_null("domain", h_abbr(C_("Domain:")),
"everywhere", h_abbr(C_("Everywhere")));
}
# _html_col_usr: The user
sub _html_col_usr : method {
$_[0]->_html_coltmpl_call_null("usr", h_abbr(C_("User:")),
"everyone", h_abbr(C_("Everyone")), \&username);
}
# _html_col_value: The preference value
sub _html_col_value : method {
$_[0]->_html_coltmpl_text("value", h_abbr(C_("Value:")));
}
return 1;

View File

@@ -0,0 +1,115 @@
# Selima Website Content Management System
# FormFunc.pm: The form-related functions.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-24
package Selima::FormFunc;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(get_or_post curform is_form form_type);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub get_or_post();
sub curform();
sub is_form(;$);
sub form_type();
}
use Selima::Cache qw(:formfunc);
use Selima::CallForm;
use Selima::DataVars qw(:env :input);
# get_or_post: whether we should look for form data from $GET or $POST
sub get_or_post() {
local ($_, %_);
return $FormFunc_get_or_post if defined $FormFunc_get_or_post;
$_ = $IS_MODPERL? ($IS_MP2?
Apache2::RequestUtil->request->method: Apache->request->method):
$ENV{"REQUEST_METHOD"};
return ($FormFunc_get_or_post = $_ eq "POST"? $POST: $GET);
}
# curform: Obtain the current form, either from the sent form or
# from the suspended form
sub curform() {
local ($_, %_);
return $FormFunc_curform if defined $FormFunc_curform;
$FormFunc_curform = get_or_post;
$FormFunc_curform = retrieve_form
if defined $FormFunc_curform->param("formid");
return $FormFunc_curform;
}
# is_form: whether this is a form
sub is_form(;$) {
local ($_, %_);
my ($isform, $FORM);
$isform = $_[0];
# Use "isform" to alter the cache
if (defined $isform) {
# A status is provided
if (ref $isform eq "HASH") {
$FormFunc_isform = $$isform{"isform"}? 1: 0
if exists $$isform{"isform"};
# A scalar value
} else {
$FormFunc_isform = $isform? 1: 0;
}
}
# Return the cache
return $FormFunc_isform if defined $FormFunc_isform;
# Obtain the current form
$FORM = curform;
# No valid form infomation
return ($FormFunc_isform = 0) if !defined $FORM->param("form");
# "isform" was specified
if ( defined $FORM->param("status")
&& exists ${$FORM->param("status")}{"isform"}) {
return ($FormFunc_isform = ${$FORM->param("status")}{"isform"}? 1: 0);
}
return ($FormFunc_isform = 1);
}
# form_type: Return the form name (new, cur, del, listpref... etc)
sub form_type() {
local ($_, %_);
# Return the cache
return $FormFunc_formtype if defined $FormFunc_formtype;
# Obtain the current form
$_ = curform;
# Form type specified in arguments
return ($FormFunc_formtype = $_->param("form"))
if defined $_->param("form");
# No form source is found
return ($FormFunc_formtype = -1);
}
return 1;

197
lib/perl5/Selima/Format.pm Normal file
View File

@@ -0,0 +1,197 @@
# Selima Website Content Management System
# Format.pm: The data formatters.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-23
package Selima::Format;
use 5.008;
use utf8;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(fmtno fmtsize fmtdate fmttime rdtime);
push @EXPORT, qw(myfmtdate myfmttime fmtntamount);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub fmtno($);
sub fmtsize($);
sub fmtdate(;$);
sub fmttime(;$);
sub rdtime($);
sub myfmtdate(;$);
sub myfmttime(;$);
sub fmtntamount($);
}
use Time::Local qw(timelocal);
use Selima::ShortCut;
# fmtno: Format the number
sub fmtno($) {
local ($_, %_);
$_ = $_[0];
1 while s/^(\d+)(\d{3})/$1,$2/;
return $_;
}
# fmtsize: Format the size
sub fmtsize($) {
local ($_, %_);
my ($report, $size, $kb, $mb, $gb, $tb, $digits, $rounded);
$size = $_[0];
# Get the size
$report = C_("[#,_1] bytes", $size);
# Try to use KB as the unit
$kb = $size / 1024;
# Bounce if there are fewer than 3 digits in the rounded result
return $report if sprintf("%0.0f", $kb * 100) < 100;
# Check the rounded result for each digit
for ($_ = 2; $_ >= 0; $_--) {
$digits = 10 ** $_;
$rounded = sprintf "%0.0f", $kb * $digits;
# There are 3 significient digits in the rounded result
return sprintf "%s (%." . $_ . "f KB)", $report, $rounded / $digits
if $rounded < 1000;
}
# Try to use MB as the unit
$mb = $kb / 1024;
# Check each digit
for ($_ = 2; $_ >= 0; $_--) {
$digits = 10 ** $_;
$rounded = sprintf "%0.0f", $mb * $digits;
# There are 3 significient digits in the rounded result
return sprintf "%s (%." . $_ . "f MB)", $report, $rounded / $digits
if $rounded < 1000;
}
# Try to use GB as the unit
$gb = $mb / 1024;
# Check each digit
for ($_ = 2; $_ >= 0; $_--) {
$digits = 10 ** $_;
$rounded = sprintf "%0.0f", $gb * $digits;
# There are 3 significient digits in the rounded result
return sprintf "%s (%." . $_ . "f GB)", $report, $rounded / $digits
if $rounded < 1000;
}
# Try to use TB as the unit
$tb = $gb / 1024;
# Check each digit
for ($_ = 2; $_ >= 0; $_--) {
$digits = 10 ** $_;
$rounded = sprintf "%0.0f", $tb * $digits;
# There are 3 significient digits in the rounded result
return sprintf "%s (%." . $_ . "f TB)", $report, $rounded / $digits
if $rounded < 1000;
}
# More than TB
return sprintf "%s (%0.0f TB)", $report, fmtno $tb;
}
# fmtdate: Format the date with the standard ISO format YYYY-MM-DD
sub fmtdate(;$) {
@_ = defined $_[0]? localtime $_[0]: localtime;
return sprintf "%04d-%02d-%02d",
$_[5]+1900, $_[4]+1, $_[3];
}
# fmttime: Format the time with the standard ISO format YYYY-MM-DD HH:MM:SS
sub fmttime(;$) {
local ($_, %_);
if (defined $_[0]) {
$_ = $_[0];
@_ = localtime $_;
$_ = $_ - int $_;
} else {
@_ = localtime;
$_ = 0;
}
$_[5] += 1900;
$_[4]++;
return sprintf "%04d-%02d-%02d %02d:%02d:%02d", @_[5,4,3,2,1,0]
if $_ == 0;
return sprintf "%04d-%02d-%02d %02d:%02d:%02d.%06d",
@_[5,4,3,2,1,0], $_ * 1000000;
}
# rdtime: Read the time with the standard ISO format YYYY-MM-DD HH:MM:SS
sub rdtime($) {
# Not in a correct format
return 0 if $_[0] !~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
return timelocal($6, $5, $4, $3, $2-1, $1-1900);
}
# myfmtdate: Format the date with my personal format
sub myfmtdate(;$) {
local ($_, %_);
if (defined $_[0]) {
# TODO:
# Actually the timestamp column should be changed to
# timestamp with time zone. But that requires a lot of
# changes, and I have no time for that now.
# imacat <imacat@mail.imacat.idv.tw> 2013-06-27
#@_ = localtime $_[0];
@_ = gmtime $_[0];
} else {
@_ = localtime;
}
$_[5] = ($_[5]+1900) % 100;
$_[4]++;
return sprintf "%d.%d.%02d.", $_[4], $_[3], $_[5];
}
# myfmttime: Format the time with my personal format
sub myfmttime(;$) {
local ($_, %_);
if (defined $_[0]) {
# TODO:
# Actually the timestamp column should be changed to
# timestamp with time zone. But that requires a lot of
# changes, and I have no time for that now.
# imacat <imacat@mail.imacat.idv.tw> 2013-06-27
#@_ = localtime $_[0];
@_ = gmtime $_[0];
} else {
@_ = localtime;
}
$_[5] = ($_[5]+1900) % 100;
$_[4]++;
$_[0] = ($_[2] < 12)? "am": "pm";
$_[2] = ($_[2] > 12)? $_[2]-12: $_[2];
return sprintf "%d.%d.%02d. %d:%02d%s.",
$_[4], $_[3], $_[5], $_[2], $_[1], $_[0];
}
# fmtntamount: Format an amount of money in NTD format
sub fmtntamount($) {
local ($_, %_);
$_ = $_[0];
1 while s/^(\d+)(\d{3})/$1,$2/;
return "NT\$ $_.00";
}
no utf8;
return 1;

73
lib/perl5/Selima/GeoIP.pm Normal file
View File

@@ -0,0 +1,73 @@
# Selima Website Content Management System
# GeoIP.pm: The GeoIP-related subroutines.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-16
package Selima::GeoIP;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(country_lookup);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub country_lookup(;$);
}
use Geo::IP qw(GEOIP_MEMORY_CACHE);
use Net::CIDR::Lite qw();
BEGIN {
if (exists $ENV{"MOD_PERL_API_VERSION"} && $ENV{"MOD_PERL_API_VERSION"} >= 2) {
require Apache2::Connection;
}
}
use Selima::DataVars qw(:env);
use vars qw($PRIVATE_NETWORKS);
$PRIVATE_NETWORKS = Net::CIDR::Lite->new(
qw(10.0.0.0/8 172.16.0.0/12 192.168.0.0/16));
use constant CT_PRIVATE => "AA";
use constant CT_UNKNOWN => "ZZ";
# country_lookup: Look-up country from IP
sub country_lookup(;$) {
local ($_, %_);
my ($GI, $ip, $ct);
$ip = $_[0];
# Default to look up the client
$ip = $IS_MODPERL? ($IS_MP2?
Apache2::RequestUtil->request->connection->remote_ip:
Apache->request->connection->remote_ip):
$ENV{"REMOTE_ADDR"}
if !defined $ip;
# Start a new GeoIP handler. It rarely needs to be cached
$GI = Geo::IP->new(GEOIP_MEMORY_CACHE);
# Look up in the GeoIP database
return uc $_ if defined($_ = $GI->country_code_by_addr($ip));
# Look up private IP
return CT_PRIVATE if $PRIVATE_NETWORKS->find($ip);
# Not known
return CT_UNKNOWN;
}
return 1;

303
lib/perl5/Selima/GetLang.pm Normal file
View File

@@ -0,0 +1,303 @@
# Selima Website Content Management System
# GetLang.pm: The subroutine to match the user preferred languages with our available languages.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-23
package Selima::GetLang;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(getlang getcharset);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub getlang(;$);
sub getcharset();
sub getlang_real();
sub getlang_filename();
sub getlang_env();
sub getlang_accept();
sub getlang_setenv($);
sub getcharset_accept();
sub all_charsets();
}
use CGI::Cookie qw();
use Selima::Cache qw(:getlang);
use Selima::DataVars qw(:input :l10n :lninfo :output :requri);
use Selima::LnInfo;
# getlang: Get the appropriate language from the user-agent
sub getlang(;$) {
local ($_, %_);
$_ = $_[0];
# Return the proper data type
return defined $_? ln(getlang_real, $_): getlang_real;
}
# getcharset: Get the appropriate character set from the user-agent.
sub getcharset() {
local ($_, %_);
my $default;
# Obtained before
return $GetLang_charset if defined $GetLang_charset;
$default = getlang(LN_CHARSET);
# We have no choice
return ($GetLang_charset = $default) if all_charsets < 2;
# Parse the character set by the Accept-Charset header
return $GetLang_charset if defined($GetLang_charset = getcharset_accept);
# Cannot parse -- return the default
return ($GetLang_charset = $default);
}
# getlang_real: The real subroutine
sub getlang_real() {
local ($_, %_);
# Obtained before
return $GetLang_lang if defined $GetLang_lang;
# Uni-lingual
return ($GetLang_lang = $DEFAULT_LANG) if @ALL_LINGUAS == 1;
# Check the file name for specified language
# No setting environment in this case
return $GetLang_lang if defined($GetLang_lang = getlang_filename);
# Methods below should set the language in the environment
# Check the environment for specified language
if (defined($GetLang_lang = getlang_env)) {
getlang_setenv $GetLang_lang;
return $GetLang_lang;
}
# Parse the language by the Accept-Language header
if (defined($GetLang_lang = getlang_accept)) {
getlang_setenv $GetLang_lang;
return $GetLang_lang;
}
# Cannot parse -- return the default
$GetLang_lang = $DEFAULT_LANG;
getlang_setenv $GetLang_lang;
return $GetLang_lang;
}
# getlang_filename: Check the file name for specified language
sub getlang_filename() {
local ($_, %_);
my $langfile;
# Check the file name format
return undef unless defined $REQUEST_PATH && $REQUEST_PATH =~ /\.([^\.\/]+)\.[^\.\/]+$/;
$langfile = $1;
# Check each language for its file name format
@_ = grep $langfile = $_, map ln($_, LN_FILENAME), @ALL_LINGUAS;
return $_[0] if @_ > 0;
# Not found
return undef;
}
# getlang_env: Check the environment for specified language
sub getlang_env() {
local ($_, %_);
%_ = map { $_ => 1 } @ALL_LINGUAS;
# Check the query string
return $_ if defined $GET
&& defined($_ = $GET->param("lang")) && exists $_{$_};
# Check the POSTed form
return $_ if defined $POST
&& defined($_ = $POST->param("lang")) && exists $_{$_};
# Check the cookies
return $_ if exists $COOKIES{"lang"}
&& exists $_{$_ = $COOKIES{"lang"}->value};
# Not set
return undef;
}
# getlang_accept: Parse the language by the Accept-Language header
# Refer to HTTP/1.1 section 14.4 for this algorism
sub getlang_accept() {
local ($_, %_);
my (@rngs, %rngqf, $defqf, $ln, @attrs, %tagqf, $tag, $match);
# Accept-Language not set
return undef if !exists $ENV{"HTTP_ACCEPT_LANGUAGE"};
# Split into language ranges
$_ = $ENV{"HTTP_ACCEPT_LANGUAGE"};
s/^\s*(.*?)\s*$/$1/;
@rngs = split /\s*,\s*/, $_;
%rngqf = qw();
foreach my $range (@rngs) {
# Split into attributes
$range =~ s/^\s*(.*?)\s*$/$1/;
@attrs = split /\s*;\s*/, $range;
# First piece is the language range
$ln = shift @attrs;
# Lower-case it
$ln = lc $ln;
# Find the quality factor
foreach my $attr (@attrs) {
# A numeric quality factor found
$rngqf{$ln} = $1+0 if $attr =~ /^q=([01](?:\.\d{1,3})?)$/;
}
# Default quality factor to 1
$rngqf{$ln} = 1 if !exists $rngqf{$ln};
}
# The default quality factor
if (exists $rngqf{"*"}) {
$defqf = $rngqf{"*"};
delete $rngqf{"*"};
} else {
$defqf = 0;
}
# Language tags (what we have)
%tagqf = qw(); # Calculated quality factor
foreach my $ln (@ALL_LINGUAS) {
# Language tag, as specified in ISO
$tag = ln $ln, LN_NAME;
# Matched range of the quality factor
undef $match if defined $match;
# Language ranges (what the user sent to match us)
foreach my $range (keys %rngqf) {
# Exactly match or match a prefix
if ($tag eq $range || $tag =~ /^\Q$range\E-/) {
# Not matched yet
if (!defined $match) {
$tagqf{$ln} = $rngqf{$range}; # Quality Factor
$match = $range; # Record the matched range
# A longer match range
} elsif (length $range > length $match) {
$tagqf{$ln} = $rngqf{$range}; # Quality Factor
$match = $range; # Record the matched range
}
}
}
# Not matched -- apply a default quality factor
$tagqf{$ln} = $defqf if !exists $tagqf{$ln};
}
# Drop unacceptable languages
foreach my $ln (keys %tagqf) {
delete $tagqf{$ln} unless $tagqf{$ln} > 0;
}
# Nothing acceptable
return undef if scalar(keys %tagqf) == 0;
# Sort by the quality factor
@_ = sort { $tagqf{$b} <=> $tagqf{$a}
|| ($a eq $DEFAULT_LANG? -1: 0)
|| ($b eq $DEFAULT_LANG? 1: 0) } keys %tagqf;
# A preferred match
return $_[0];
}
# getlang_setenv: Check the environment for specified language
sub getlang_setenv($) {
local ($_, %_);
$_ = $_[0];
# Set the cookie to keep the result
$NEWCOOKIES{"lang"} = new CGI::Cookie(-name=>"lang", -value=>$_)
if !exists $COOKIES{"lang"} || $COOKIES{"lang"}->value ne $_;
return;
}
# getcharset_accept: Parse the character set by the Accept-Charset header
# Refer to HTTP/1.1 section 14.2 for this algorism
sub getcharset_accept() {
local ($_, %_);
my (@rngs, %rngqf, $defqf, $cs, @attrs, %tagqf, $tag, $default);
# Accept-Charset not set
return undef if !exists $ENV{"HTTP_ACCEPT_CHARSET"};
$default = getlang(LN_CHARSET);
# Split into character set ranges
$_ = $ENV{"HTTP_ACCEPT_CHARSET"};
s/^\s*(.*?)\s*$/$1/;
@rngs = split /\s*,\s*/, $_;
%rngqf = qw();
foreach my $range (@rngs) {
# Split into attributes
$range =~ s/^\s*(.*?)\s*$/$1/;
@attrs = split /\s*;\s*/, $range;
# First piece is the character set range
$cs = shift @attrs;
# Lower-case it
$cs = lc $cs;
# Find the quality factor
foreach my $attr (@attrs) {
# A numeric quality factor found
$rngqf{$cs} = $1+0 if $attr =~ /^q=([01](?:\.\d{1,3})?)$/;
}
# Default quality factor to 1
$rngqf{$cs} = 1 if !exists $rngqf{$cs};
}
# The default quality factor
if (exists $rngqf{"*"}) {
$defqf = $rngqf{"*"};
delete $rngqf{"*"};
} else {
# Default ISO-8859-1 to 1
$rngqf{"iso-8859-1"} = 1 if !exists $rngqf{"iso-8859-1"};
$defqf = 0;
}
# Character set tags (what we have)
%tagqf = qw(); # Calculated quality factor
foreach my $cs (all_charsets) {
# Character set tag, as specified in ISO
$tag = lc $cs;
# Character set ranges (what the user sent to match us)
foreach my $range (keys %rngqf) {
# Matched
$tagqf{$cs} = $rngqf{$range} # Quality Factor
if $tag eq $range;
}
# Not matched -- apply a default quality factor
$tagqf{$cs} = $defqf if !exists $tagqf{$cs};
}
# Drop unacceptable character sets
foreach my $cs (keys %tagqf) {
delete $tagqf{$cs} unless $tagqf{$cs} > 0;
}
# Nothing acceptable
return undef if scalar(keys %tagqf) == 0;
# Sort by the quality factor
@_ = sort { $tagqf{$b} <=> $tagqf{$a}
|| ($a eq $default? -1: 0)
|| ($b eq $default? 1: 0) } keys %tagqf;
# A preferred match
return $_[0];
}
# all_charsets: Obtain all the available character sets
# Available character sets are the default character set of this
# language, and UTF-8
sub all_charsets() {
local ($_, %_);
return @GetLang_all_charsets if @GetLang_all_charsets > 0;
%_ = qw();
$_ = getlang(LN_CHARSET);
$_{$_} = 1;
$_{"UTF-8"} = 1;
@GetLang_all_charsets = keys %_;
return @GetLang_all_charsets;
}
return 1;

83
lib/perl5/Selima/Guest.pm Normal file
View File

@@ -0,0 +1,83 @@
# Selima Website Content Management System
# Guest.pm: The subroutines for anonymouse/guest operations.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-26
package Selima::Guest;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(is_guest gactlog goutpage grmoldpage grmoldfile);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub is_guest(;$);
sub gactlog($;$);
sub goutpage($$;$);
sub grmoldpage($;$);
sub grmoldfile($;$);
}
use Selima::Array;
use Selima::Cache qw(:guest);
use Selima::ChkPriv;
use Selima::DataVars qw(:input :groups);
use Selima::Logging;
use Selima::LogIn;
use Selima::MkAllDir;
use Selima::PageFunc;
use Selima::UserName;
use Selima::XFileIO;
# is_guest: If the user is a guest (by the user id)
sub is_guest(;$) {
local ($_, %_);
$_ = $_[0];
# Default to the current logged-in user
return !is_su && in_array(GUEST_GROUP, get_login_groups)
if !defined $_ || (defined get_login_sn && $_ == get_login_sn);
# Super user is never a guest
return ($Guest_is_guest{$_} = 0) if is_su($_);
# Obtain the groups
return ($Guest_is_guest{$_} = in_array(GUEST_GROUP,
user_parent_groups($_)));
}
# gactlog: Add an activity log record if the user is not a guest
sub gactlog($;$) {
actlog $_[0], $_[1] unless is_guest;
}
# goutpage: outpage() if the user is not a guest
sub goutpage($$;$) {
outpage $_[0], $_[1], $_[2] unless is_guest;
}
# grmoldpage: rmoldpage() if the user is not a guest
sub grmoldpage($;$) {
rmoldpage $_[0], $_[1] unless is_guest;
}
# grmoldfile: rmoldfile() if the user is not a guest
sub grmoldfile($;$) {
rmoldfile $_[0], $_[1] unless is_guest;
}
return 1;

View File

@@ -0,0 +1,206 @@
# Selima Website Content Management System
# Guestbook.pm: The guestbook-related subroutines.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-23
package Selima::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(update_pageno);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub update_pageno($$$;$);
sub split_page(\@\%$;$);
sub update_old_gbpageno($$;$);
}
use Selima::DataVars qw($DBH);
use Selima::Format;
# update_pageno: Update the guestbook page number
sub update_pageno($$$;$) {
local ($_, %_);
my ($table, $page_size, $cols, $from, $sql, $sth, $count, $row, $len, $where);
my (@ents, %sizes, %orig, %pagenos, $startno, $startat, $commit);
($table, $page_size, $cols, $from) = @_;
# If we should begin and commit here
$commit = $DBH->{"AutoCommit"};
$DBH->begin_work if $commit;
# Update the current page number
# Check the page to start from
undef $startno;
undef $startat;
$from = fmttime $from if defined $from;
if (defined $from) {
$sql = "SELECT pageno FROM $table WHERE NOT hid AND created<'$from'"
. " ORDER BY created DESC LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
undef $from if $sth->rows != 1;
}
if (defined $from) {
$startno = ${$sth->fetch}[0];
undef $sth;
$sql = "SELECT created FROM $table WHERE NOT hid AND pageno=$startno"
. " ORDER BY created LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$startat = ${$sth->fetch}[0];
}
@_ = qw();
push @_, "created>='" . fmttime($startat) . "'" if defined $startat;
$where = (@_ > 0)? " WHERE " . join(" AND ", @_): "";
# Ge the size of everyhing
$len = join " + ", map "CASE WHEN $_ IS NULL THEN 0 ELSE char_length($_) END", @$cols;
$len = "CASE WHEN hid THEN 0 ELSE $len END AS len";
$sql = "SELECT sn, $len, pageno FROM $table $where ORDER BY created;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, @ents = qw(), %sizes = qw(), %orig = qw(); $_ < $count; $_++) {
$row = $sth->fetchrow_hashref;
push @ents, $$row{"sn"};
$sizes{$$row{"sn"}} = $$row{"len"};
$orig{$$row{"sn"}} = $$row{"pageno"};
}
# Split page
%pagenos = split_page @ents, %sizes, $page_size, $startno;
# Update it
foreach (@ents) {
next if $orig{$_} == $pagenos{$_};
$sql = "UPDATE $table SET pageno=$pagenos{$_} WHERE sn=$_;\n";
$DBH->gdo($sql);
}
# Update the old page number
# No need to update it anymore. Let it go.
$DBH->commit if $commit;
return;
}
# split_page: Split page according to the entry sizes
sub split_page(\@\%$;$) {
local ($_, %_);
my ($ents, $sizes, $page_size, $startno, $cursize, %pagenos);
($ents, $sizes, $page_size, $startno) = @_;
$startno = 1 if !defined $startno;
# Bounce for nothing
return if scalar @$ents == 0;
# Split pages
%pagenos = qw();
$pagenos{$$ents[0]} = $startno;
$cursize = $$sizes{$$ents[0]};
for ($_ = 1; $_ < scalar(@$ents); $_++) {
my ($hi_gap, $lo_gap);
# We need at least one record
if ($cursize == 0) {
$pagenos{$$ents[$_]} = $pagenos{$$ents[$_-1]};
$cursize += $$sizes{$$ents[$_]};
next;
# Not oversized yet
} elsif ($cursize + $$sizes{$$ents[$_]} < $page_size) {
$pagenos{$$ents[$_]} = $pagenos{$$ents[$_-1]};
$cursize += $$sizes{$$ents[$_]};
next;
}
$hi_gap = $cursize + $$sizes{$$ents[$_]} - $page_size;
$lo_gap = $page_size - $cursize;
# The upper boundary is closer, and the page is not too oversized
if ($hi_gap < $lo_gap && $hi_gap <= $page_size / 4) {
$pagenos{$$ents[$_]} = $pagenos{$$ents[$_-1]};
$cursize += $$sizes{$$ents[$_]};
# Or, we prefer the lower, since the page is not oversized
} else {
$pagenos{$$ents[$_]} = $pagenos{$$ents[$_-1]} + 1;
$cursize = $$sizes{$$ents[$_]};
}
}
return %pagenos;
}
# update_old_gbpageno: Update the old guestbook page number
sub update_old_gbpageno($$;$) {
local ($_, %_);
my ($table, $page_size, $from, $sql, $sth, $count, $row, $len, $cond);
my (@ents, %sizes, %orig, %pagenos, $startno, $startat, $commit);
($table, $page_size, $from) = @_;
# If we should begin and commit here
$commit = $DBH->{"AutoCommit"};
$DBH->begin_work if $commit;
# Update the old page number
undef $startno;
undef $startat;
if (defined $from) {
$sql = "SELECT oldpageno FROM $table WHERE NOT hid AND created<'$from'"
. " ORDER BY created DESC LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$startno = ${$sth->fetch}[0];
undef $sth;
$sql = "SELECT created FROM $table WHERE NOT hid AND oldpageno=$startno"
. " ORDER BY created LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$startat = ${$sth->fetch}[0];
}
@_ = qw();
push @_, "NOT hid";
push @_, "created>='$startat'" if defined $startat;
$cond = join " AND ", @_;
# Ge the size of everyhing
$len = "${table}_oldlen(created, ip, host, name, identity, location, email, url, message, updated, updatedby) AS len";
# Tavern Backalley has a special table structure
$len = "garbage_oldlen(created, ip, host, message, updated, updatedby) AS len"
if $table eq "garbage";
$sql = "SELECT sn, $len, oldpageno FROM $table WHERE $cond ORDER BY created;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, @ents = qw(), %sizes = qw(), %orig = qw(); $_ < $count; $_++) {
$row = $sth->fetchrow_hashref;
push @ents, $$row{"sn"};
$sizes{$$row{"sn"}} = $$row{"len"};
$orig{$$row{"sn"}} = $$row{"oldpageno"};
}
# Split page
# Old page size is always 10240
%pagenos = split_page @ents, %sizes, 10240, $startno;
# Update it
foreach (@ents) {
next if $orig{$_} == $pagenos{$_};
$sql = "UPDATE $table SET oldpageno=$pagenos{$_} WHERE sn=$_;\n";
$DBH->gdo($sql);
}
$DBH->commit if $commit;
return;
}
return 1;

1627
lib/perl5/Selima/HTTP.pm Normal file

File diff suppressed because it is too large Load Diff

94
lib/perl5/Selima/HTTPS.pm Normal file
View File

@@ -0,0 +1,94 @@
# Selima Website Content Management System
# HTTPS.pm: The HTTPS SSL subroutines.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-09-12
package Selima::HTTPS;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(https_process https_host fqdn is_https);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub https_process(;$);
sub https_host();
sub fqdn();
sub is_https();
}
use Socket qw(inet_aton inet_ntoa AF_INET);
use Selima::Cache qw(:https);
use Selima::DataVars qw(:hostconf);
use Selima::Server;
# https_process: Use HTTPs to process the request
sub https_process(;$) {
local ($_, %_);
my $https;
$https = $_[0];
# Set the answer
$HTTPS_https_process = 1 if defined $https;
# Return the answer
return $HTTPS_https_process;
}
# https_host: The default HTTPs host name
sub https_host() {
local ($_, %_);
# Respect the pre-defined setting
return $HTTPS_HOST if defined $HTTPS_HOST;
# Use the fully-qualified domain name (FQDN)
return ($HTTPS_HOST = fqdn);
}
# fqdn: The fully qualified domain name
sub fqdn() {
local ($_, %_);
# Return the cache
return $HTTPS_fqdn if defined $HTTPS_fqdn;
# Use DNS look-up for the current host name
# Apache implementation
$_ = is_apache? $ENV{"SERVER_ADDR"}:
# Microsoft IIS implementation
is_iis? $ENV{"LOCAL_ADDR"}:
# Else, do DNS query
inet_ntoa(scalar gethostbyname $ENV{"SERVER_NAME"});
# Reverse-DNS query for a fully-qualified domain name (FQDN)
$HTTPS_fqdn = gethostbyaddr inet_aton($_), AF_INET;
return $HTTPS_fqdn;
}
# is_https: Check if current scheme is HTTPS
sub is_https() {
local ($_, %_);
# Apache implementation
return exists $ENV{"HTTPS"} if is_apache;
# Microsoft IIS implementation
return exists $ENV{"SERVER_PORT_SECURE"} if is_iis;
# Well, set port 443 to https and others to http.
# This is a bad approach. Avoid it whenever possible.
return ($ENV{"SERVER_PORT"} == 443);
}
return 1;

351
lib/perl5/Selima/Init.pm Normal file
View File

@@ -0,0 +1,351 @@
# Selima Website Content Management System
# Init.pm: The script initializer.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-23
package Selima::Init;
use 5.008;
use utf8;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(initvars initenv);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub initvars($);
sub initenv(%);
sub check_spambots();
sub block_spam($);
}
use Fcntl qw(:flock);
use File::Basename qw(basename);
use File::Spec::Functions qw(splitpath splitdir catpath catdir catfile);
use IO::NestedCapture qw(CAPTURE_STDOUT);
use POSIX qw(setlocale LC_ALL);
use Sys::Hostname qw(hostname);
use Time::HiRes qw();
use URI qw();
use URI::Escape qw(uri_unescape);
BEGIN {
if (exists $ENV{"MOD_PERL_API_VERSION"} && $ENV{"MOD_PERL_API_VERSION"} >= 2) {
require Apache2::RequestRec;
}
}
use Selima::Cache qw();
use Selima::CallForm;
use Selima::DataVars qw(:all);
use Selima::DBI;
use Selima::DecForm;
use Selima::FormFunc;
use Selima::HTTP;
use Selima::LastModf;
use Selima::ListPref;
use Selima::LogIn;
use Selima::Logging;
use Selima::ReqURI;
use Selima::ScptPriv;
use Selima::SetL10N;
use Selima::ShortCut;
use Selima::Session;
use Selima::Unauth;
# initvars: Initialize the data variables
sub initvars($) {
local ($_, %_);
my ($pkg, $r);
$pkg = $_[0];
# Only run once for mod_perl
if ($IS_MODPERL) {
$r = $IS_MP2? Apache2::RequestUtil->request:
Apache->request;
# Bounce if already initialized under mod_perl
return if defined $r->headers_in->get("X-Selima-Initialized");
$r->headers_in->set("X-Selima-Initialized", "yes");
# Clean-up before initialization, only for mod_perl
Selima::DataVars::clear;
Selima::Cache::clear;
# Cear the site data variables
if (defined $pkg) {
$_ = "Selima::" . $pkg . "::DataVars";
&$_ if defined($_ = $_->can("clear"));
}
}
# Set the default values of some variables
# By default we use PostgreSQL, unless changed by site configuration
$DBI_TYPE = DBI_POSTGRESQL;
# The script path
%SCRIPTS = (
FORM_USERS() => "/magicat/cgi-bin/users.cgi",
FORM_GROUPS() => "/magicat/cgi-bin/groups.cgi",
FORM_USERMEM() => "/magicat/cgi-bin/usermem.cgi",
FORM_GROUPMEM() => "/magicat/cgi-bin/groupmem.cgi",
FORM_USERPREF() => "/magicat/cgi-bin/userpref.cgi",
FORM_SCPTPRIV() => "/magicat/cgi-bin/scpptpriv.cgi",
FORM_PIC() => "/magicat/cgi-bin/pic.cgi",
FORM_PAGES() => "/magicat/cgi-bin/pages.cgi",
FORM_NEWS() => "/magicat/cgi-bin/news.cgi",
FORM_LINKCAT() => "/magicat/cgi-bin/linkcat.cgi",
FORM_LINKS() => "/magicat/cgi-bin/links.cgi",
FORM_ACCTSUBJ() => "/magicat/cgi-bin/acctsubj.cgi",
FORM_ACCTTRX() => "/magicat/cgi-bin/accttrx.cgi",
);
$NOLOGIN = 0;
$DEFAULT_LANG = "zh-tw";
$PAGEBAR_RANGE = 2;
IO::NestedCapture->start(CAPTURE_STDOUT);
binmode IO::NestedCapture->instance->{"STDOUT_current"}[-1], ":utf8";
$NO_AUTO_OUTPUT = 0;
$LOGTIME = 0;
$T_START = Time::HiRes::time;
# Load the site and host configuration
if (defined $pkg) {
# Load the site configuration
$_ = "Selima::" . $pkg . "::Config";
&$_ if defined($_ = $_->can("siteconf"));
# Load the host-specific configuration
$_ = "Selima::" . $pkg . "::HostConf";
&$_ if defined($_ = $_->can("hostconf"));
# Look for siteconf() and hostconf() imported to the caller
} else {
(caller 1)[0]->siteconf if (caller 1)[0]->can("siteconf");
(caller 1)[0]->hostconf if (caller 1)[0]->can("hostconf");
}
# Set $0 of the non-CGI scripts
if (!$IS_CGI) {
# Deal with the relative path
require FindBin;
if ($FindBin::Script ne "-" && $FindBin::Script ne "-e") {
@_ = splitpath($FindBin::Bin);
$_[1] = catdir(splitdir($_[1]));
$_ = catpath(@_);
$0 = catfile($_, $FindBin::Script);
}
}
# Emulate the CGI environment, if not
if (!$IS_CGI) {
$ENV{"GATEWAY_INTERFACE"} = "";
$ENV{"QUERY_STRING"} = "" if !exists $ENV{"QUERY_STRING"};
$ENV{"REMOTE_ADDR"} = "127.0.0.1" if !exists $ENV{"REMOTE_ADDR"};
$ENV{"REMOTE_HOST"} = "localhost" if !exists $ENV{"REMOTE_HOST"};
$ENV{"REQUEST_METHOD"} = "GET" if !exists $ENV{"REQUEST_METHOD"};
$ENV{"SCRIPT_NAME"} = $0 if !exists $ENV{"SCRIPT_NAME"};
$ENV{"SERVER_NAME"} = hostname if !exists $ENV{"SERVER_NAME"};
$ENV{"SERVER_PORT"} = 80 if !exists $ENV{"SERVER_PORT"};
$ENV{"SERVER_SOFTWARE"} = $^O if !exists $ENV{"SERVER_SOFTWARE"};
}
# Try to obtain the request information
init_request_uri;
# Scan the parameters
%COOKIES = fetch CGI::Cookie;
init_forms;
# Initialize the localization framework (gettext/Maketext)
# This runs gettext implicitly
set_l10n;
decode_forms;
# Set the path of the this processing form
$SCRIPTS{FORM_THIS()} = form_this;
return;
}
# initenv: Initialize the script environment
sub initenv(%) {
local ($_, %_);
my (%param);
my ($dbi, $session, $restricted, $lastmod);
%param = @_;
# Initialize the data variables and cache
$MAIN = (caller)[0];
&$_ if defined($_ = $MAIN->can("siteconf"));
initvars $PACKAGE;
decode_forms_delay;
# Load the script configuration
$THIS_FILE = basename($0);
# $MAIN was cleaned-up in initvars(), so we need to obtain it again
$MAIN = (caller)[0];
&$_ if defined($_ = $MAIN->can("scptconf"));
# Parse the arguments
$dbi = exists $param{"-dbi"}? $param{"-dbi"}:
defined $DBI_TYPE? $DBI_TYPE: DBI_NONE;
$session = exists $param{"-session"}? $param{"-session"}: 1;
$restricted = exists $param{"-restricted"}? $param{"-restricted"}: 0;
$lastmod = exists $param{"-lastmod"}? $param{"-lastmod"}: 0;
# Tag if we should log the processing time
$LOGTIME = $param{"-logtime"} if exists $param{"-logtime"};
if (exists $param{"-page_param"}) {
$PAGE_PARAM = $param{"-page_param"};
# Maketext now, since we have already set_l10n() in initvars()
$$PAGE_PARAM{"keywords"} = __($$PAGE_PARAM{"keywords"})
if exists $$PAGE_PARAM{"keywords"};
}
# Block FunWebProduct
# See http://www.networkworld.com/newsletters/web/2003/1208web2.html
http_403(N_("Sorry, browsers with FunWebProduct plugin (Smiley, PopSwatter, Spin4Dough, My Mail Signature, My Mail Stationery, My Mail Stamp, Cursor Mania, etc.) are are not welcome. It duplicates your request and produces high load and even crashes to our server. Please remove it first before you visit us."))
if exists $ENV{"HTTP_USER_AGENT"}
&& $ENV{"HTTP_USER_AGENT"} =~ /FunWebProduct/;
# Block bad-behaved e-mail crawlers
# Some bad-behaved e-mail crawlers cannot deal with the parent
# directory "/.." and ampersands, and attach them to the URI infinitely
http_400(0) if $REQUEST_PATH =~ /\/\.\./ || $REQUEST_URI =~ /&amp;/;
# Check the request method
$_ = exists $param{"-allowed"}? $param{"-allowed"}: [qw(GET HEAD POST)];
if (defined $_) {
%_ = map { $_ => 1 } @$_;
http_405 @$_ if !exists $_{$ENV{"REQUEST_METHOD"}};
}
# Check and block the spambots
check_spambots;
# Start the session
$SESSION = Selima::Session->init if $session;
# If client has not logged in on restricted area, we can
# bypass SQL connection to save our work
if ($IS_CGI && $restricted) {
if (exists $INC{"Apache/AuthDigest/API.pm"}) {
unauth if !defined $AUTHINFO;
} else {
unauth if !exists $ENV{"REMOTE_USER"};
}
}
# Initialize the database connection
if ($dbi) {
$DBH = Selima::DBI->new($dbi) ;
# Set the current table
$THIS_TABLE = $param{"-this_table"} if exists $param{"-this_table"};
}
# Prepare the SQL tables to lock
if ($dbi && exists $param{"-dbi_lock"}) {
# Read-only on non-POSTed forms
if ($ENV{"REQUEST_METHOD"} ne "POST") {
${$param{"-dbi_lock"}}{$_} = LOCK_SH
foreach keys %{$param{"-dbi_lock"}};
}
# Supply the default locks
if (use_users) {
${$param{"-dbi_lock"}}{$_} = LOCK_SH
foreach grep !exists ${$param{"-dbi_lock"}}{$_},
(qw(users groups scptpriv userpref),
"users AS createdby", "users AS updatedby");
}
}
# Check the last modified
if ($lastmod) {
my (@tables, @files);
# Set the database tables to check
@tables = qw();
push @tables, @{$param{"-lmtables"}} if exists $param{"-lmtables"};
# Add the locked tables automatically
push @tables, keys %{$param{"-dbi_lock"}} if exists $param{"-dbi_lock"};
# Set the files to check
@files = qw();
push @files, @{$param{"-lmfiles"}} if exists $param{"-lmfiles"};
http_304 if not_modified @tables, @files;
}
# Lock the SQL tables
$DBH->lock(%{$param{"-dbi_lock"}})
if $dbi && exists $param{"-dbi_lock"};
# Only available on systems with membership turned on
if ($dbi && use_users && $session) {
# Update the log-in information
if (exists $INC{"Apache/AuthDigest/API.pm"}) {
upd_login_info if defined $AUTHINFO;
} else {
upd_login_info if exists $ENV{"REMOTE_USER"};
upd_login_info if !$IS_CGI;
}
# Check the client permission
unauth if $restricted && !is_script_permitted;
}
# Process the list preference form
if (form_type eq "listpref") {
my $domain;
if ( defined($domain = $POST->param("domain"))
&& $domain->can("new")) {
$_ = $domain->new;
$_->set_listpref;
} else {
$_ = new Selima::ListPref($POST);
$_->main;
}
}
return;
}
# check_spambots: Check and block spam bots
# This starts at an earlier phrase before the database initialization,
# to decrease the server load.
sub check_spambots() {
local ($_, %_);
my ($r, $method, $col);
if ($IS_MODPERL) {
$r = $IS_MP2? Apache2::RequestUtil->request:
Apache->request;
$method = $r->method;
} else {
$method = $ENV{"REQUEST_METHOD"};
}
$col = FORM_CAPTCHA;
# Block the spam for POST forms
if ($method eq "POST" && defined $POST->param($col)) {
block_spam "check_spambots: captcha column \"$col\" should be empty but got \""
. $POST->param($col) . "\"."
if $POST->param($col) ne "";
}
}
# block_spam: Block the spam message
sub block_spam($) {
local ($_, %_);
$_ = $_[0];
spamlog $_;
# Delay the spammer
sleep 300;
http_403(0);
# No return
}
no utf8;
return 1;

106
lib/perl5/Selima/L10N.pm Normal file
View File

@@ -0,0 +1,106 @@
# Selima Website Content Management System
# L10N.pm: The core localization class.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-21
package Selima::L10N;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;
# The core Chinese (Taiwan) localized messages.
package Selima::L10N::zh_tw;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
sub numerate : method { $_[2] }
return 1;
# The core Chinese (China) localized messages.
package Selima::L10N::zh_cn;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
sub numerate : method { $_[2] }
return 1;
# The core Chinese localized messages.
package Selima::L10N::zh;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
sub numerate : method { $_[2] }
return 1;
# The core English localized messages.
package Selima::L10N::en;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;
# The core English (United States) localized messages.
package Selima::L10N::en_us;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;
# The core default-language localized messages.
package Selima::L10N::i_default;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;
# The core automatic localized messages.
package Selima::_AUTO::L10N;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;
# The core automatic default-language localized messages.
package Selima::_AUTO::L10N::i_default;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;

View File

@@ -0,0 +1,173 @@
# Selima Website Content Management System
# LastModf.pm: The web site last-modified time calculator.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-03-23
package Selima::LastModf;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(not_modified);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub not_modified(\@\@);
sub find_last_modified(\@\@);
sub check_inc();
sub updmtime_tables(\@);
sub updmtime_file($);
}
use File::Spec::Functions qw(catfile);
use HTTP::Date qw(str2time);
use Selima::CallForm;
use Selima::DataVars qw($DBH :input :l10n :lninfo :lastmod :libdir :requri :siteconf);
use Selima::GetLang;
use Selima::HTTP;
use Selima::Session;
# not_modified: Check if we should send "HTTP/1.1 304 Not Modified"
sub not_modified(\@\@) {
local ($_, %_);
my ($tables, $files);
($tables, $files) = @_;
# HTTP/1.1 304 only works for GET or HEAD
return 0 unless $ENV{"REQUEST_METHOD"} eq "GET"
|| $ENV{"REQUEST_METHOD"} eq "HEAD";
# Find the last-modified time
find_last_modified @$tables, @$files;
# If-Modified-Since not supplied. The client had not cached yet.
return 0 if !exists $ENV{"HTTP_IF_MODIFIED_SINCE"};
# Malformed If-Modified-Since value
return 0 if !defined($_ = str2time($ENV{"HTTP_IF_MODIFIED_SINCE"}));
# We are newer than the cache
return 0 if $LAST_MODIFIED > $_;
# Yes, use the cache
return 1;
}
# find_last_modified: Find the last-modified time
sub find_last_modified(\@\@) {
local ($_, %_);
my ($tables, $files);
($tables, $files) = @_;
# Checked before
return if defined $LAST_MODIFIED;
# Remove duplicates
%_ = map { $_ => 1 } @$tables;
@$tables = keys %_;
%_ = map { $_ => 1 } @$files;
@$files = keys %_;
# Start with EPOCH 1970-01-01 00:00:00
$LAST_MODIFIED = 0;
# Check myself
updmtime_file $0;
# Check mtime from the included modules and gettext MO files
check_inc;
# Check the supplied data files
updmtime_file $_ foreach grep -f $_, @$files;
# Check the supplied data tables
updmtime_tables @$tables;
# Check the saved form and status
if (defined $GET->param("formid")) {
$_ = catfile($Selima::Session::DIR, "saveform_" . $GET->param("formid"));
updmtime_file $_ if -f $_;
}
if (defined $GET->param("statid")) {
$_ = catfile($Selima::Session::DIR, "savestat_" . $GET->param("statid"));
updmtime_file $_ if -f $_;
}
return;
}
# check_inc: Check mtime from the included modules and gettext MO files
sub check_inc() {
local ($_, %_);
my ($t, $DH);
# Check the included modules
updmtime_file $_ foreach grep /^\Q$SITE_LIBDIR\E\b/, values %INC;
$t = COMMON_LIBDIR;
updmtime_file $_ foreach grep /^$t\b/, values %INC;
# Check the header and footer
$_ = $DOC_ROOT . "/magicat/include";
@_ = qw();
if (@ALL_LINGUAS > 1) {
push @_, $_ . "/header." . getlang(LN_FILENAME) . ".html";
push @_, $_ . "/footer." . getlang(LN_FILENAME) . ".html";
} else {
push @_, $_ . "/header.html";
push @_, $_ . "/footer.html";
}
foreach (@_) {
updmtime_file $_ if -e $_;
}
# Check the gettext mo files
if (-d $LOCALEDIR) {
opendir $DH, $LOCALEDIR or http_500 "$LOCALEDIR: $!";
@_ = readdir $DH or http_500 "$LOCALEDIR: $!";
closedir $DH or http_500 "$LOCALEDIR: $!";
foreach (@_) {
$_ = "$LOCALEDIR/$_/LC_MESSAGES/$PACKAGE.mo";
updmtime_file $_ if -f $_;
}
}
opendir $DH, COMMON_LOCALEDIR or http_500 COMMON_LOCALEDIR . ": $!";
@_ = readdir $DH or http_500 COMMON_LOCALEDIR . ": $!";
closedir $DH or http_500 COMMON_LOCALEDIR . ": $!";
foreach (@_) {
$_ = COMMON_LOCALEDIR . "/$_/LC_MESSAGES/" . COMMON_DOMAIN . ".mo";
updmtime_file $_ if -f $_;
}
return;
}
# updmtime_tables: Update the $LAST_MODIFIED with the mtime of tables
sub updmtime_tables(\@) {
local ($_, %_);
my ($tables, $sql, $sth);
$tables = $_[0];
# Only work when using database
return if !defined $DBH;
return unless defined($_ = $DBH->lastupd(@$tables));
$_ = int $_;
$LAST_MODIFIED = $_ if defined $_ && $LAST_MODIFIED < $_;
return;
}
# updmtime_file: Update the $LAST_MODIFIED with the mtime of a file
sub updmtime_file($) {
local ($_, %_);
$_ = (stat $_[0])[9];
$LAST_MODIFIED = $_ if $LAST_MODIFIED < $_;
return;
}
return 1;

364
lib/perl5/Selima/Links.pm Normal file
View File

@@ -0,0 +1,364 @@
# Selima Website Content Management System
# Links.pm: The related-link related subroutines.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::Links;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(linkcat_title linkcat_options link_title);
push @EXPORT, qw(links_shown_parts link_tree link_tree_full);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub linkcat_title($);
sub linkcat_options($);
sub link_title($);
sub links_shown_parts();
sub link_tree($$;$);
sub link_tree_full($;$);
sub link_subtree($$$;$);
}
use Encode qw(decode);
use Selima::Cache qw(:links);
use Selima::ChkFunc;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo);
use Selima::EchoForm;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::PageFunc;
use Selima::ShortCut;
# linkcat_title: Obtain a link category title
sub linkcat_title($) {
local ($_, %_);
my ($sn, $sql, $sth, $col, $thiscol, $lang, $defcol);
$sn = $_[0];
# Bounce if there is any problem with $sn
return t_notset if !defined $sn;
# Return the cache
return $Links_linkcat_title{$sn} if exists $Links_linkcat_title{$sn};
# Check the serial number first
return ($Links_linkcat_title{$sn} = t_na)
if !check_sn $sn;
# Query
# Unilingual
if (@ALL_LINGUAS == 1) {
$col = "linkcat_fulltitle(parent, title) AS title";
# Multilingual
} else {
$thiscol = "title_" . getlang LN_DATABASE;
$lang = getlang;
# Default language
if ($lang eq $DEFAULT_LANG) {
$col = "linkcat_fulltitle('$lang', parent, $thiscol) AS title";
# Fall back to the default language
} else {
$defcol = "title_" . ln($DEFAULT_LANG, LN_DATABASE);
$col = "linkcat_fulltitle('$lang', parent, COALESCE($thiscol, $defcol)) AS title";
}
}
$sql = "SELECT $col FROM linkcat WHERE sn=$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Links_linkcat_title{$sn} = t_na)
unless $sth->rows == 1;
# Found
return ($Links_linkcat_title{$sn} = ${$sth->fetch}[0]);
}
# linkcat_options: Obtain a link category options list
sub linkcat_options($) {
local ($_, %_);
my ($value, $sql, $thiscol, $defcol, $lang, $content);
$value = $_[0];
# Unilingual
if (@ALL_LINGUAS == 1) {
$content = "linkcat_fulltitle(parent, title) AS content";
# Multilingual
} else {
$thiscol = "title_" . getlang(LN_DATABASE);
$lang = getlang;
# Default language
if ($lang eq $DEFAULT_LANG) {
$content = "linkcat_fulltitle('$lang', parent, $thiscol) AS content";
# Fall back to the default language
} else {
$defcol = "title_" . ln($DEFAULT_LANG, LN_DATABASE);
$content = "linkcat_fulltitle('$lang', parent, COALESCE($thiscol, $defcol)) AS content";
}
}
$sql = "SELECT sn AS value, $content FROM linkcat"
. " ORDER BY linkcat_fullord(parent, ord);\n";
return opt_list $sql, $value;
}
# link_title: Obtain a link title
sub link_title($) {
local ($_, %_);
my ($sn, $sql, $sth, $thiscol, $defcol);
$sn = $_[0];
# Bounce if there is any problem with $sn
return t_notset if !defined $sn;
# Return the cache
return $Links_link_title{$sn} if exists $Links_link_title{$sn};
# Check the serial number first
return ($Links_link_title{$sn} = t_na)
if !check_sn $sn;
# Query
$sql = "SELECT title FROM links WHERE sn=$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Links_link_title{$sn} = t_na)
unless $sth->rows == 1;
# Found
return ($Links_link_title{$sn} = ${$sth->fetch}[0]);
}
# link_url: Obtain a link URL
sub link_url($) {
local ($_, %_);
my ($sn, $sql, $sth, $thiscol, $defcol);
$sn = $_[0];
# Bounce if there is any problem with $sn
return t_notset if !defined $sn;
# Return the cache
return $Links_link_title{$sn} if exists $Links_link_title{$sn};
# Check the serial number first
return ($Links_link_title{$sn} = t_na)
if !check_sn $sn;
# Query
$sql = "SELECT url FROM links WHERE sn=$sn;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# Not found
return ($Links_link_title{$sn} = t_na)
unless $sth->rows == 1;
# Found
return ($Links_link_title{$sn} = ${$sth->fetch}[0]);
}
# links_shown_parts: Obtain the shown links parts
sub links_shown_parts() {
local ($_, %_);
my ($sql, $sth, $count, $row, $path);
%_ = (
"cats" => [],
"catspath" => [],
);
# Obtain the shown categories
$path = $DBH->strcat("'/links'", "linkcat_path(sn, id, parent)")
. " AS path";
$sql = "SELECT sn, $path FROM linkcat"
. " WHERE linkcat_isshown(sn, hid, parent)"
. " ORDER BY linkcat_fullord(parent, ord);\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0; $_ < $count; $_++) {
$row = $sth->fetchrow_hashref;
push @{$_{"cats"}}, $$row{"sn"};
push @{$_{"catspath"}}, $$row{"path"};
}
@{$_{"cats"}} = sort @{$_{"cats"}};
@{$_{"catspath"}} = sort @{$_{"catspath"}};
return \%_;
}
####################
# Subroutines about link categories
####################
# link_tree: Get the page tree of the links
sub link_tree($$;$) {
local ($_, %_);
my ($path, $lang, $preview, $dir, $tree);
($path, $lang, $preview) = @_;
# Obtain the directory
$dir = $path;
$dir =~ s/[^\/]+\/?$//;
# Initialize the directory array
$Links_link_tree{$lang} = {}
if !exists $Links_link_tree{$lang};
# Return the cache
return ${$Links_link_tree{$lang}}{$dir}
if exists ${$Links_link_tree{$lang}}{$dir};
# Get the full page tree of the links
$tree = link_tree_full $lang, $preview;
# Make a hash of the page tree
$Links_link_tree{$lang} = {hash_tree $tree};
# Not found
return undef if !exists ${$Links_link_tree{$lang}}{$dir};
return ${$Links_link_tree{$lang}}{$dir};
}
# link_tree_full: Get the page tree of the links
sub link_tree_full($;$) {
local ($_, %_);
my ($lang, $preview, $charset, $tree, $pages);
($lang, $preview) = @_;
# Return the cache
return $Links_link_tree_full{$lang}
if exists $Links_link_tree_full{$lang};
# Set the language
$charset = ln($lang, LN_CHARSET);
# Initialize the result
$tree = qw();
# Set the index page
$$tree{"index"} = {
"path" => "/links/",
"title" => C_("Related Links"),
};
# Get the link categories
$pages = link_subtree "/links", undef, $lang, $preview;
$$tree{"pages"} = $pages if defined $pages;
return $tree;
}
# link_subtree: Get the page subtree of the links
sub link_subtree($$$;$) {
local ($_, %_);
my ($path, $parent, $lang, $preview);
my ($lndb, $lndbdef, $has_links, $pages);
my ($sql, $sth, $count, @cols, @conds);
($path, $parent, $lang, $preview) = @_;
# Check if there is any link below this category
$has_links = 0;
if (defined $parent) {
$sql = "SELECT links.sn FROM links"
. " INNER JOIN linkcatz ON linkcatz.link=links.sn"
. " WHERE linkcatz.cat=" . $parent
. " AND NOT links.hid;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$has_links = ($sth->rows > 0);
# Check the preview
$has_links = in_array($parent, @{$$preview{"cats"}})
if !$has_links && defined $preview;
}
# Obtain the subcategories
@cols = qw();
push @cols, "sn AS sn";
push @cols, "id AS id";
# Unilingual
if (@ALL_LINGUAS == 1) {
push @cols, "title AS title";
# Multilingual
} else {
# Set the language
$lndb = ln $lang, LN_DATABASE;
if ($lang eq $DEFAULT_LANG) {
push @cols, "title_$lndb AS title";
} else {
$lndbdef = ln $DEFAULT_LANG, LN_DATABASE;
push @cols, "COALESCE(title_$lndb, title_$lndbdef)"
. " AS title";
}
}
push @cols, "ord AS ord";
@conds = qw();
if (!defined $parent) {
push @conds, "parent IS NULL";
} else {
push @conds, "parent=$parent";
}
push @conds, "NOT hid";
push @conds, "sn!=" . $$preview{"sn"}
if defined $preview && exists $$preview{"sn"};
$sql = "SELECT " . join(", ", @cols) . " FROM linkcat"
. " WHERE " . join(" AND ", @conds)
. " ORDER BY ord;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, $pages = []; $_ < $count; $_++) {
my ($row, $subpages, $subpath);
$row = $sth->fetchrow_hashref;
$subpages = link_subtree($path . "/" . $$row{"id"},
$$row{"sn"}, $lang, $preview);
# Only create subtree that has some content
if (defined $subpages) {
# No subcategories -- create it as a ".html" page
if (@$subpages == 0) {
$subpath = $path . "/" . $$row{"id"} . ".html";
push @$pages, {
"path" => $subpath,
"title" => $$row{"title"},
"ord" => $$row{"ord"},
};
# There are subcatgories -- create it as a directory
} else {
$subpath = $path . "/" . $$row{"id"} . "/";
push @$pages, {
"path" => $subpath,
"title" => $$row{"title"},
"ord" => $$row{"ord"},
"sub" => {
"index" => {
"path" => $subpath,
"title" => $$row{"title"},
},
"pages" => $subpages,
},
};
}
}
}
# No content below
return undef if !$has_links && @$pages == 0;
return $pages;
}
return 1;

1619
lib/perl5/Selima/List.pm Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
# Selima Website Content Management System
# Records.pm: The accounting record list.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-23
package Selima::List::Accounting::Records;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::CommText;
use Selima::Format;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "acctrecs" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
C_("Select an Accounting Record"):
C_("Manage Accounting Records");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "trx";
# Column labels
$self->col_labels(
"trx" => C_("Accounting transaction"),
"credit" => C_("Debit/credit"),
"subj" => C_("Accounting subject"),
"summary" => C_("Summary"),
"amount" => C_("Amount"),
);
return $self;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
local ($_, %_);
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Add a new accounting record."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for an accounting record:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting record].", $self->{"total"});
# List result
} else {
return C_("[*,_1,accounting record].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,accounting record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The balance
if ($col eq "amount") {
return "<div class=\"amount\">"
. h_abbr(fmtntamount $row{$col}) . "</div>";
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
return 1;

View File

@@ -0,0 +1,667 @@
# Selima Website Content Management System
# Reports.pm: The base accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-24
package Selima::List::Accounting::Reports;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Time::Local qw(timelocal);
use Selima::AddGet;
use Selima::CommText;
use Selima::ChkFunc;
use Selima::DataVars qw($DBH :input :requri);
use Selima::Format;
use Selima::LogIn;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class, $sql, $sth);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = C_("View the Accounting Reports");
# Known columns that should not be displayed (has a special purpose)
push @{$self->{"COLS_NO_DISPLAY"}}, qw(_subj _date _trx);
# Known columns that should not be sorted with
# List sorting is disabled here at all
push @{$self->{"COLS_NO_SORT_BY"}}, qw(date month trxno subj summary
income expense debit credit balance note);
# The list type
$self->{"type"} = $self->{"FORM"}->param("list");
# The date range
$self->{"range"} = $self->{"FORM"}->param("r");
# The onload event handler
$self->{"onload"} = "acctRepQueryDisableNoUseRanges();";
# Should we return the data as CSV
$self->{"iscsv"} = 0;
$self->{"iscsv"} = 1
if defined($_ = $self->{"FORM"}->param("format")) && $_ eq "csv";
# If the database is empty
$sql = "SELECT sn FROM accttrx LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"nodata"} = ($sth->rows == 0);
# The full period - used in all reports
if (!$self->{"nodata"}) {
# The earliest start date
$sql = "SELECT date FROM accttrx ORDER BY date LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
@_ = localtime ${$sth->fetchrow_hashref}{"date"};
$self->{"startdate"} = sprintf "%04d-%02d-%02d",
$_[5] + 1900, $_[4] + 1, $_[3];
# The latest end date
$sql = "SELECT date FROM accttrx ORDER BY date DESC LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
@_ = localtime ${$sth->fetchrow_hashref}{"date"};
$self->{"enddate"} = sprintf "%04d-%02d-%02d",
$_[5] + 1900, $_[4] + 1, $_[3];
}
# Column labels
$self->col_labels(
"month" => C_("Month"),
"subj" => C_("Accounting subject"),
"summary" => C_("Summary"),
"debit" => C_("Debit"),
"credit" => C_("Credit"),
"income" => C_("Income"),
"expense" => C_("Expense"),
"balance" => C_("Balance"),
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
return if !defined $_[0]->{"type"};
# Run the parent method
return $_[0]->SUPER::fetch;
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The debit and the credit
if ( $col eq "debit" || $col eq "credit"
|| $col eq "income" || $col eq "expense") {
return "" if $row{$col} == 0;
return "<div class=\"amount\">"
. h_abbr(fmtntamount $row{$col}) . "</div>";
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# pre_filter: Set the pre-defined filter
sub pre_filter : method {
local ($_, %_);
my ($self, $form, @conds, $year, $month, $day, $from, $to);
my ($sql, $sth, $startdate);
$self = $_[0];
$form = $self->{"FORM"};
# No need to run if there is no data at all
return undef if $self->{"nodata"};
@conds = qw();
# The active range that is affecting this list
$self->{"actrange"} = undef;
# Range specified
if (defined $self->{"range"}) {
# By month
if ($self->{"range"} eq "m") {
# Trim the value
if (defined($_ = $form->param("m"))) {
s/^\s+//;
s/\s+$//;
$form->param("m", $_);
}
if (!defined $form->param("m") || $form->param("m") eq "") {
$self->{"error"} = {"msg"=>N_("Please specify a month.")}
if !defined $self->{"error"};
} elsif ( $form->param("m") !~ /^(\d{4})-(\d{2})$/
|| !check_date($year = $1, $month = $2, 1)) {
$self->{"error"} = {"msg"=>N_("Please specify a valid month in YYYY-MM format.")}
if !defined $self->{"error"};
} else {
$from = sprintf "%04d-%02d-%02d", $year, $month, 1;
$self->{"startdate"} = $from
if ($self->{"startdate"} cmp $from) < 0;
# The next month
$month++;
if ($month > 12) {
$year++;
$month = 1;
}
# The previous day before the first day of next month
# - The last day of this month
$_ = timelocal(0, 0, 0, 1, $month - 1, $year - 1900);
$_ -= 86400;
@_ = localtime $_;
$to = sprintf "%04d-%02d-%02d",
$_[5] + 1900, $_[4] + 1, $_[3];
$self->{"enddate"} = $to
if ($self->{"enddate"} cmp $to) > 0;
push @conds, "date>=" . $DBH->quote($self->{"startdate"});
push @conds, "date<=" . $DBH->quote($self->{"enddate"});
$self->{"actrange"} = "r=m&m=" . $form->param("m");
}
# By year
} elsif ($self->{"range"} eq "y") {
# Trim the value
if (defined($_ = $form->param("y"))) {
s/^\s+//;
s/\s+$//;
$form->param("y", $_);
}
if (!defined $form->param("y") || $form->param("y") eq "") {
$self->{"error"} = {"msg"=>N_("Please specify a year.")}
if !defined $self->{"error"};
} elsif ( ($_ = $form->param("y")) !~ /^\d{4}$/
|| !check_date($_, 1, 1)) {
$self->{"error"} = {"msg"=>N_("Please specify a valid year in YYYY format.")}
if !defined $self->{"error"};
} else {
$from = sprintf "%04d-%02d-%02d", $_, 1, 1;
$self->{"startdate"} = $from
if ($self->{"startdate"} cmp $from) < 0;
$to = sprintf "%04d-%02d-%02d", $_, 12, 31;
$self->{"enddate"} = $to
if ($self->{"enddate"} cmp $to) > 0;
push @conds, "date>=" . $DBH->quote($self->{"startdate"});
push @conds, "date<=" . $DBH->quote($self->{"enddate"});
$self->{"actrange"} = "r=y&y=" . $form->param("y");
}
# Specified reange
} elsif ($self->{"range"} eq "s") {
my @actrange;
@actrange = qw();
# Trim the value
if (defined($_ = $form->param("f"))) {
s/^\s+//;
s/\s+$//;
$form->param("f", $_);
}
if (defined($_ = $form->param("t"))) {
s/^\s+//;
s/\s+$//;
$form->param("t", $_);
}
# The start day
if (!defined $form->param("f") || $form->param("f") eq "") {
$self->{"error"} = {"msg"=>N_("Please specify the start date.")}
if !defined $self->{"error"};
} elsif ( $form->param("f") !~ /^(\d{4})-(\d{2})-(\d{2})$/
|| !check_date($year = $1, $month = $2, $day =$3)) {
$self->{"error"} = {"msg"=>N_("Please specify a valid start date in YYYY-MM-DD format.")}
if !defined $self->{"error"};
} else {
$from = sprintf "%04d-%02d-%02d", $year, $month, $day;
$self->{"startdate"} = $from
if ($self->{"startdate"} cmp $from) < 0;
push @conds, "date>=" . $DBH->quote($self->{"startdate"});
push @actrange, "f=" . $form->param("f");
}
# The end day
if (!defined $form->param("t") || $form->param("t") eq "") {
$self->{"error"} = {"msg"=>N_("Please specify the end date.")};
} elsif ( $form->param("t") !~ /^(\d{4})-(\d{2})-(\d{2})$/
|| !check_date($year = $1, $month = $2, $day =$3)) {
$self->{"error"} = {"msg"=>N_("Please specify a valid end date in YYYY-MM-DD format.")}
if !defined $self->{"error"};
} else {
$to = sprintf "%04d-%02d-%02d", $year, $month, $day;
$self->{"enddate"} = $to
if ($self->{"enddate"} cmp $to) > 0;
push @conds, "date<=" . $DBH->quote($self->{"enddate"});
push @actrange, "t=" . $form->param("t");
}
if (@actrange > 0) {
unshift @actrange, "r=s";
$self->{"actrange"} = join "&", @actrange;
}
# All
} elsif ($self->{"range"} eq "a") {
# No condition is applied here
$self->{"actrange"} = "r=a";
# Else
} else {
$self->{"error"} = {"msg"=>N_("This option is invalid. Please select a proper date range.")}
if !defined $self->{"error"};
}
}
# Range not set - default to the current month
if (!defined $self->{"actrange"}) {
($year, $month) = (localtime)[5,4];
$year += 1900;
$month++;
$from = sprintf "%04d-%02d-%02d", $year, $month, 1;
$self->{"startdate"} = $from
if ($self->{"startdate"} cmp $from) < 0;
$self->{"actrange"} = "r=m&m=" . sprintf("%04d-%02d", $year, $month);
$month++;
if ($month > 12) {
$year++;
$month = 1;
}
# The previous day before the first day of next month
# - The last day of this month
$_ = timelocal(0, 0, 0, 1, $month - 1, $year - 1900);
$_ -= 86400;
@_ = localtime $_;
$to = sprintf "%04d-%02d-%02d",
$_[5] + 1900, $_[4] + 1, $_[3];
$self->{"enddate"} = $to
if ($self->{"enddate"} cmp $to) > 0;
push @conds, "date>=" . $DBH->quote($self->{"startdate"});
push @conds, "date<=" . $DBH->quote($self->{"enddate"});
}
# Th end date should not be before the first date
$self->{"enddate"} = $self->{"startdate"}
if exists $self->{"startdate"} && exists $self->{"enddate"}
&& ($self->{"startdate"} cmp $self->{"enddate"}) > 0;
return undef if @conds == 0;
return join " AND ", @conds;
}
# html: Output the list
sub html : method {
local ($_, %_);
my $self;
$self = $_[0];
# Fetch the current list if not fetched yet
$self->fetch if !$self->{"fetched"};
# Download the CSV
return $self->html_csv if $self->{"iscsv"} && $self->can("html_csv");
# Run the parent method
return $self->SUPER::html;
}
# set_listpref: Set the list preference
sub set_listpref : method {
local ($_, %_);
my $self;
$self = $_[0];
$_ = new Selima::ListPref::AcctReps($self->{"FORM"});
$_->main;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
local ($_, %_);
my ($self, $urle, $urli, $urlt, $prompt);
$self = $_[0];
$urle = "accttrx.cgi?form=new&formsub=expense";
$urli = "accttrx.cgi?form=new&formsub=income";
$urlt = "accttrx.cgi?form=new&formsub=trans";
$prompt = C_("<a href=\"[_1]\">Add a new cash expense transaction</a>, <a href=\"[_2]\">add a new cash income transaction</a> or <a href=\"[_3]\">add a new transfer transaction</a>.",
h($urle), h($urli), h($urlt));
print << "EOT";
<p>$prompt</p>
EOT
return;
}
# html_lists_switch: Display the switch for different lists
sub html_lists_switch : method {
local ($_, %_);
my ($self, $labellist, @lists);
$self = $_[0];
# No need to run if there is no data at all
return if $self->{"nodata"};
# Switch for different lists
$labellist = h_abbr(C_("Report type:"));
@lists = qw();
push @lists, {
"type" => "cash",
"title" => C_("Cash book"),
};
push @lists, {
"type" => "cashsum",
"title" => C_("Cash book summary"),
};
push @lists, {
"type" => "ldgr",
"title" => C_("Ledger"),
};
push @lists, {
"type" => "ldgrsum",
"title" => C_("Ledger summary"),
};
push @lists, {
"type" => "journal",
"title" => C_("Journal"),
};
push @lists, {
"type" => "tb",
"title" => C_("Trial balance"),
};
push @lists, {
"type" => "incmstat",
"title" => C_("Income statement"),
};
push @lists, {
"type" => "blncshet",
"title" => C_("Balance sheet"),
};
# Display the form
print << "EOT";
<p>$labellist
EOT
@_ = qw();
foreach (@lists) {
if (defined $self->{"type"} && $self->{"type"} eq $$_{"type"}) {
push @_, h($$_{"title"});
} else {
push @_, sprintf("<a href=\"%s\">%s</a>",
h($REQUEST_FILE . "?list=" . $$_{"type"}),
h($$_{"title"}));
}
}
print join(" |\n", @_) . "\n";
print << "EOT";
</p>
EOT
return;
}
# html_search: Display the search box
sub html_search : method {
local ($_, %_);
my ($self, $prompt, $label, $query, $request_file);
($self, $prompt) = @_;
# No need to run if there is no data at all
return if $self->{"nodata"};
# Display the report query box
$self->html_report_query;
$prompt = C_("Search the accounting records:") if !defined $prompt;
$prompt = h($prompt);
$request_file = h($REQUEST_FILE);
$query = defined $self->{"query"}? h($self->{"query"}): "";
$label = h(C_("Search"));
print << "EOT";
<form action="$request_file" method="get" accept-charset="<!--selima:charset-->">
<div class="searchbox">
<input type="hidden" name="list" value="search" />
<label for="query">$prompt</label>
<input id="query" type="text" name="query" value="$query" /><input
type="hidden" name="charset" value="<!--selima:charset-->" /><input
type="submit" value="$label" />
</div>
</form>
EOT
return;
}
# html_report_query: Display the report query box
sub html_report_query : method {
local ($_, %_);
my ($self, $form, $request_file, $label, $curlist);
my ($labelmonth, $labelyear, $labelspecified, $labelall);
my ($labelfrom, $labelto, $labelrange);
my ($valrm, $valry, $valrs, $valra);
my ($valm, $valy, $valf, $valt);
my ($sql, $sth, $count, $row);
$self = $_[0];
$form = $self->{"FORM"};
$request_file = h($REQUEST_FILE);
$label = h_abbr(C_("Query"));
$labelrange = h_abbr(C_("Date range:"));
$labelmonth = h_abbr(C_("By month:"));
$labelyear = h_abbr(C_("By year:"));
$labelspecified = h_abbr(C_("Specified date range:"));
$labelall = h_abbr(C_("All"));
$labelfrom = h_abbr(C_("From"));
$labelto = h_abbr(C_("to"));
# Whether each radio button is checked
($valrm, $valry, $valrs, $valra) = ("", "", "", "");
if (defined $self->{"range"}) {
$valrm = " checked=\"checked\""
if $self->{"range"} eq "m";
$valry = " checked=\"checked\""
if $self->{"range"} eq "y";
$valrs = " checked=\"checked\""
if $self->{"range"} eq "s";
$valra = " checked=\"checked\""
if $self->{"range"} eq "a";
# Default to this month
} else {
$valrm = " checked=\"checked\"";
}
# The value of each range
@_ = localtime;
$_[5] += 1900;
$_[4]++;
$valm = defined $form->param("m")? h($form->param("m")):
sprintf("%04d-%02d", @_[5,4]);
$valy = defined $form->param("y")? h($form->param("y")): $_[5];
$valf = defined $form->param("f")? h($form->param("f")):
sprintf("%04d-%02d-%02d", @_[5,4,3]);
$valt = defined $form->param("t")? h($form->param("t")):
sprintf("%04d-%02d-%02d", @_[5,4,3]);
$curlist = h($self->{"type"});
print << "EOT";
<form id="acctrepquery" action="$request_file" method="get" accept-charset="<!--selima:charset-->">
<div class="acctrepquery">
<div><input type="hidden" name="list" value="$curlist" /></div>
EOT
# Display the subject selection if available
$self->html_select_subject if $self->can("html_select_subject");
print << "EOT";
<p>$labelrange
<input id="rangemonth" type="radio" name="r" value="m"$valrm onchange="acctRepQueryDisableNoUseRanges();" /><label
for="rangemonth">$labelmonth</label><select
name="m">
EOT
$sql = "SELECT extract(year FROM date) AS year, extract(month FROM date) AS month FROM accttrx"
. " GROUP BY extract(year FROM date), extract(month FROM date)"
. " ORDER BY extract(year FROM date) DESC, extract(month FROM date) DESC;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @_ = qw(); $i < $count; $i++) {
my ($val, $selected);
$row = $sth->fetchrow_hashref;
$_ = sprintf "%04d-%02d", $$row{"year"}, $$row{"month"};
$val = h($_);
$selected = $valm eq $_? " selected=\"selected\"": "";
print << "EOT";
<option value="$val"$selected>$_</option>
EOT
}
print << "EOT";
</select>
<input id="rangeyear" type="radio" name="r" value="y"$valry onchange="acctRepQueryDisableNoUseRanges();" /><label
for="rangeyear">$labelyear</label><select
name="y">
EOT
$sql = "SELECT extract(year FROM date) AS year FROM accttrx"
. " GROUP BY extract(year FROM date)"
. " ORDER BY extract(year FROM date) DESC;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @_ = qw(); $i < $count; $i++) {
my ($val, $selected);
$row = $sth->fetchrow_hashref;
$_ = $$row{"year"};
$val = h($_);
$selected = $valy eq $_? " selected=\"selected\"": "";
print << "EOT";
<option value="$val"$selected>$_</option>
EOT
}
print << "EOT";
</select>
<input id="rangespecified" type="radio" name="r" value="s"$valrs onchange="acctRepQueryDisableNoUseRanges();" /><label
for="rangespecified">$labelspecified</label><label
for="rangestart">$labelfrom</label><input
id="rangestart" type="text" name="f" value="$valf" size="10" /><label
for="rangeend">$labelto</label><input
id="rangeend" type="text" name="t" value="$valt" size="10" />
<input id="rangeall" type="radio" name="r" value="a"$valra onchange="acctRepQueryDisableNoUseRanges();" /><label
for="rangeall">$labelall</label>
</p>
<p><input type="hidden" name="charset" value="<!--selima:charset-->" /><input
type="submit" value="$label" />
</p>
</div>
</form>
EOT
return;
}
# html_liststat: Display the list statistics
sub html_liststat : method {
local ($_, %_);
my $self;
$self = $_[0];
# Print the current time period
if (!$self->{"nodata"}) {
my ($message, $from, $to);
($from, $to) = ($self->{"startdate"}, $self->{"enddate"});
$message = h(C_("From [_1] to [_2].", $from, $to));
print << "EOT";
<p>$message</p>
EOT
}
# Run the parent method
return $self->SUPER::html_liststat;
}
# html_listprefform: Display a form to change the list preference
sub html_listprefform : method {
local ($_, %_);
my ($self, $submit, $referer, $request_file_h, $domain);
my ($label, $pagesize);
$self = $_[0];
# Do not show the list
return if !defined $self->{"total"};
# No record to be listed
return if $self->{"total"} == 0;
# Return if users preferences are not available
return if !use_users || !defined $SESSION;
$submit = C_("Set");
# The referer
$referer = h(rem_get_arg $REQUEST_FULLURI, "statid");
$request_file_h = h($REQUEST_FILE);
# The domain -- my class name
$domain = h(ref($self));
print << "EOT";
<form action="$request_file_h" method="post" accept-charset="<!--selima:charset-->">
<div><input type="hidden" name="charset" value="<!--selima:charset-->" /><input
type="hidden" name="form" value="listpref" /><input
type="hidden" name="referer" value="$referer" /><input
type="hidden" name="domain" value="$domain" />
EOT
# The number of rows per page
$label = h_abbr(C_("Rows per page:"));
$pagesize = h($self->{"pagesize"});
print << "EOT";
<label for="listsize">$label</label><input
id="listsize" type="text" name="listsize" size="5" maxlength="5" value="$pagesize" />
EOT
print << "EOT";
<input type="submit" name="confirm" value="$submit" /></div>
</form>
EOT
return;
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting record].", $self->{"total"});
# List result
} else {
return C_("[*,_1,accounting record].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,accounting record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,372 @@
# Selima Website Content Management System
# BlncShet.pm: The balance sheet accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-10-03
package Selima::List::Accounting::Reports::BlncShet;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo :output :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = C_("Balance Sheet");
# The list type
$self->{"type"} = "blncshet";
# The default number of rows per page
$self->{"DEFAULT_LIST_SIZE"} = undef;
# Known columns that should not be sorted with
# List sorting is disabled here at all
push @{$self->{"COLS_NO_SORT_BY"}}, qw(subjd amountd subjc amountc);
$self->{"noselect"} = 1;
# Column labels
$self->col_labels(
"subjd" => C_("Assets accounting subject"),
"amountd" => C_("Assets amount"),
"subjc" => C_("Liabilities accounting subject"),
"amountc" => C_("Liabilities amount"),
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, @cols, $title, $sth, $sql, $error);
my (@subjs, %recs, @debits, @credits, $sumdebit, $sumcredit);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if (exists $self->{"nodata"} && $self->{"nodata"}) {
$self->{"total"} = 0;
return;
}
# Construct the SQL query statement
# Obtain the period once
$self->sql_filter;
@cols = qw();
if (@ALL_LINGUAS == 1) {
$title = "acctsubj.title";
} else {
my ($lndb, $lndbdef);
$lndb = getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
$title = "acctsubj.title_$lndb";
} else {
$lndbdef = ln $DEFAULT_LANG, LN_DATABASE;
$title = "COALESCE(acctsubj.title_$lndb, acctsubj.title_$lndbdef)";
}
}
push @cols, $DBH->strcat("acctsubj.code", "' '", $title) . " AS subj";
push @cols, "sum(CASE WHEN acctrecs.credit THEN acctrecs.amount ELSE -acctrecs.amount END)"
. " AS balance";
$sql = "SELECT " . join(", ", @cols)
. " FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE date<=" . $DBH->quote($self->{"enddate"})
. " AND (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " GROUP BY acctsubj.code, $title"
. " ORDER BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
@subjs = qw();
push @subjs, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
@subjs = grep $$_{"balance"} != 0, @subjs;
# Obtain the carry-over record of assets/liabilities
$_ = "sum(CASE WHEN acctrecs.credit THEN -acctrecs.amount ELSE acctrecs.amount END)"
. " AS balance";
$sql = "SELECT $_ FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " AND accttrx.date<" . $DBH->quote($self->{"startdate"}) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$_ = $sth->fetchrow_hashref;
undef $sth;
push @subjs, {
"subj" => acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_ACUM)),
"balance" => $$_{"balance"},
} if defined $$_{"balance"};
# Obtain the net income or loss for current period
$self->sql_filter;
$_ = "sum(CASE WHEN acctrecs.credit THEN acctrecs.amount ELSE -acctrecs.amount END)"
. " AS balance";
$sql = "SELECT $_ FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE date>=" . $DBH->quote($self->{"startdate"})
. " AND date<=" . $DBH->quote($self->{"enddate"})
. " AND NOT (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%');\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$_ = $sth->fetchrow_hashref;
undef $sth;
push @subjs, {
"subj" => acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_CUR)),
"balance" => $$_{"balance"},
} if defined $$_{"balance"};
# Add each major category
%recs = qw();
($sumdebit, $sumcredit) = (0, 0);
foreach my $majsubj ((1, 2, 3)) {
my $sum;
$recs{$majsubj} = [];
push @{$recs{$majsubj}}, {
"subj" => acctsubj_title(acctsubj_sn($majsubj)),
"amount" => "",
};
$sum = 0;
%_ = map { substr($$_{"subj"}, 0, 2) => 1 }
grep $$_{"subj"} =~ /^$majsubj/, @subjs;
foreach my $minsubj (sort keys %_) {
push @{$recs{$majsubj}}, {
"subj" => acctsubj_title(acctsubj_sn($minsubj)),
"amount" => "",
} if $minsubj !~ /^(?:12|15|22)$/;
foreach (sort { $$a{"subj"} cmp $$b{"subj"} } grep $$_{"subj"} =~ /^$minsubj/, @subjs) {
push @{$recs{$majsubj}}, {
"subj" => $$_{"subj"},
"amount" => $$_{"balance"},
};
$sum += $$_{"balance"};
}
}
push @{$recs{$majsubj}}, {
"subj" => C_("Total"),
"amount" => $sum,
};
if ($majsubj == 1) {
$sumdebit += $sum;
} else {
$sumcredit += $sum;
}
}
@debits = @{$recs{1}};
@credits = (@{$recs{2}}, { "subj" => "", "amount" => "" }, @{$recs{3}});
# Supply blank records
while (@debits != @credits) {
if (@debits < @credits) {
push @debits, { "subj" => "", "amount" => "" };
} else {
push @credits, { "subj" => "", "amount" => "" };
}
}
# Supply the total record
push @debits, {
"subj" => C_("Total"),
"amount" => $sumdebit,
};
push @credits, {
"subj" => C_("Total"),
"amount" => $sumcredit,
};
# Invert the amount of the debit records
foreach (@debits) {
$$_{"amount"} *= -1 if $$_{"amount"} ne "";
}
# Join the debit and credit records into the balance sheet
$self->{"current"} = [];
for ($_ = 0; $_ < @debits; $_++) {
push @{$self->{"current"}}, {
"subjd" => ${$debits[$_]}{"subj"},
"amountd" => ${$debits[$_]}{"amount"},
"subjc" => ${$credits[$_]}{"subj"},
"amountc" => ${$credits[$_]}{"amount"},
};
}
# The number of rows per page
$self->{"pagesize"} = $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Set the columns to be displayed
$self->{"cols"} = [qw(subjd amountd subjc amountc)];
$self->{"listcols"} = [qw(subjd amountd subjc amountc)];
# Done
return $self->{"error"};
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The debit and the credit
if ($col eq "subjd" || $col eq "subjc") {
if ($row{$col} =~ /^\d\d /) {
return "<div class=\"subjlv2\">"
. h_abbr($row{$col}) . "</div>";
} elsif ($row{$col} =~ /^\d\d\d/) {
return "<div class=\"subjlastlv\">"
. h_abbr($row{$col}) . "</div>";
} else {
return h_abbr($row{$col});
}
}
# The amount
if ($col eq "amountd" || $col eq "amountc") {
return "" if $row{$col} eq "";
if ($row{$col} < 0) {
return "<div class=\"amount\"><span class=\"neg\">("
. h_abbr(fmtntamount -$row{$col}) . ")</span></div>";
} else {
return "<div class=\"amount\">"
. h_abbr(fmtntamount $row{$col}) . "</div>";
}
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=blncshet";
push @_, $self->{"actrange"};
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=balance_sheet.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
@_ = map $$current{$_}, @{$self->{"listcols"}};
foreach (@_) {
$_ = "" if !defined $_;
s/"/""/g; # "
}
print join(",", map "\"$_\"", @_) . "\n";
}
return;
}
# html_liststat: Display the list statistics
sub html_liststat : method {
local ($_, %_);
my $self;
$self = $_[0];
# Print the current time period
if (!$self->{"nodata"}) {
my ($message, $from, $to);
($from, $to) = ($self->{"startdate"}, $self->{"enddate"});
$message = h(C_("From [_1] to [_2].", $from, $to));
print << "EOT";
<p>$message</p>
EOT
}
return;
}
# html_listprefform: Display a form to change the list preference
# Make it a null function
sub html_listprefform : method {}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my ($self, $total);
$self = $_[0];
$total = $self->{"total"};
# Inherit the empty list statistics message
return $self->SUPER::liststat_message if $self->{"total"} == 0;
return C_("[*,_1,accounting subject].", $self->{"total"} - 1);
}
return 1;

View File

@@ -0,0 +1,440 @@
# Selima Website Content Management System
# Cash.pm: The cash accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-24
package Selima::List::Accounting::Reports::Cash;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use POSIX qw(floor);
use Text::Capitalize qw(capitalize_title);
use URI::Escape qw(uri_escape);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo :output :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserPref;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The list type
$self->{"type"} = "cash";
if (@ALL_LINGUAS == 1) {
$self->{"view"} = "acctrep_cash_list";
} else {
$self->{"view"} = "acctrep_cash_list_" . getlang(LN_DATABASE);
}
# The subject
$self->{"subj"} = $self->{"FORM"}->param("subj");
$self->{"subj"} = "" if !defined $self->{"subj"};
# The page title
$_ = $self->{"subj"} eq ""?
C_("current assets and liabilities"):
acctsubj_title(acctsubj_sn($self->{"subj"}));
s/^\d+ //;
$self->{"title"} = capitalize_title(C_("Cash book - [_1]", $_));
$self->{"title"} =~ s/ - /\x{2014}/;
# Column labels
$self->col_labels(
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $cols, $table, $where, $orderby, $limit, $sth, $sql, $error);
my ($balance, $brought, $sumincome, $sumexpense);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if ($self->{"nodata"}) {
$self->{"total"} = 0;
return $self->{"error"};
}
# See if we need to use views or not.
# Views make things much faster and easier, but some DBMS has no views.
# *MySQL has no views*
if ($self->{"useview"}) {
($cols, $table, $where, $orderby, $limit) = $self->select_with_view;
} else {
($cols, $table, $where, $orderby, $limit) = $self->select_without_view;
}
# Find the balance before our date range
$_ = "sum(CASE WHEN acctrecs.credit THEN -amount ELSE amount END) AS sum";
if ($self->{"subj"} eq "") {
$sql = "SELECT $_ FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE (acctsubj.code LIKE '11%'"
. " OR acctsubj.code LIKE '12%'"
. " OR acctsubj.code LIKE '21%'"
. " OR acctsubj.code LIKE '22%')"
. " AND accttrx.date<" . $DBH->quote($self->{"startdate"}) . ";\n";
} else {
$sql = "SELECT $_ FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE acctsubj.code LIKE '" . $self->{"subj"} . "%'"
. " AND accttrx.date<" . $DBH->quote($self->{"startdate"}) . ";\n";
}
$sth = $DBH->prepare($sql);
$sth->execute;
$balance = ${$sth->fetch}[0];
# Fetch everything
$self->{"select"} = sprintf "SELECT %s FROM %s%s%s%s;\n",
$cols, $table, $where, $orderby, $limit;
$sql = $self->{"select"};
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"current"} = [];
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Brought-forward record
undef $brought;
$brought = {
"_sel" => 0,
"_selurl" => undef,
"date" => $self->{"startdate"},
"subj" => acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_ACUM)),
"summary" => undef,
"income" => $balance > 0? $balance: 0,
"expense" => $balance < 0? -$balance: 0,
"balance" => $balance,
} if defined $balance;
# Do calculation on each record
$balance = 0 if !defined $balance;
($sumincome, $sumexpense) = (0, 0);
for (my $i = 0; $i < @{$self->{"current"}}; $i++) {
$_ = ${$self->{"current"}}[$i];
$balance = $balance + $$_{"income"} - $$_{"expense"};
$sumincome += $$_{"income"};
$sumexpense += $$_{"expense"};
$$_{"balance"} = $balance;
}
# Prepend the brought-forward record
unshift @{$self->{"current"}}, $brought if defined $brought;
# Append the total record
push @{$self->{"current"}}, {
"_sel" => 0,
"_selurl" => undef,
"date" => $self->{"enddate"},
"subj" => $self->{"subj"} eq ""?
C_("current assets and liabilities"):
acctsubj_title(acctsubj_sn($self->{"subj"})),
"summary" => C_("Total"),
"income" => $sumincome,
"expense" => $sumexpense,
"balance" => $balance,
};
# The number of rows per page
$_ = userpref("listsize", ref $self);
$self->{"pagesize"} = defined $_? $_: $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
if (!defined $self->{"pagesize"}) {
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Done
return $self->{"error"};
}
# Obtain the total number
$self->{"total"} = scalar @{$self->{"current"}};
$self->{"lastpage"} = floor(($self->{"total"} - 1) / $self->{"pagesize"}) + 1;
# If last page is 0 (when total is 0), set to page 1
$self->{"lastpage"} = 1 if $self->{"lastpage"} < 1;
# Check the page number
# Show the last page by default, but not reverse the number colume
$_ = $self->{"reverse"};
$self->{"reverse"} = 1;
$error = $self->check_pageno;
$self->{"reverse"} = $_;
$self->{"error"} = $error if defined $error && !defined $self->{"error"};
# Calculate the start and end record number
$self->{"startno"} = ($self->{"pageno"} - 1) * $self->{"pagesize"};
$self->{"endno"} = $self->{"pageno"} * $self->{"pagesize"} - 1;
# If there is not enough remaining records, set to the last one
$self->{"endno"} = $self->{"total"} - 1
if $self->{"endno"} > $self->{"total"} - 1;
# If the last record is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Obtain everything in this page
$self->{"current"} = [ @{$self->{"current"}}[$self->{"startno"}...$self->{"endno"}] ]
if !$self->{"iscsv"};
# Set the columns to be displayed
$self->{"listcols"} = [qw(date subj summary income expense balance)];
# Done
return $self->{"error"};
}
# page_param: Obtain page parameters
sub page_param : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::page_param;
$self->{"reverse"} = $rev;
return $r;
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Display nothing instead of "not set" for empty summaries
return "" if $col =~ /^(?:date|summary)$/ && !defined $row{$col};
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The balance
if ($col eq "balance") {
return "" if $row{$col} eq "";
if ($row{$col} > 0) {
return "<div class=\"amount\">"
. h_abbr(fmtntamount $row{$col}) . "</div>";
} elsif ($row{$col} < 0) {
return "<div class=\"amount\"><span class=\"neg\">-"
. h_abbr(fmtntamount -$row{$col}) . "</span></div>";
} else {
return "<div class=\"amount\">-</div>";
}
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# pre_filter: Set the pre-defined filter
sub pre_filter : method {
local ($_, %_);
my ($self, @conds);
$self = $_[0];
@conds = qw();
if ($self->{"subj"} eq "") {
push @conds, "_subj NOT LIKE '11%'";
push @conds, "_subj NOT LIKE '12%'";
push @conds, "_subj NOT LIKE '21%'";
push @conds, "_subj NOT LIKE '22%'";
@_ = qw();
push @_, "accttrx_has_subj(_trx, '11')";
push @_, "accttrx_has_subj(_trx, '12')";
push @_, "accttrx_has_subj(_trx, '21')";
push @_, "accttrx_has_subj(_trx, '22')";
push @conds, "(" . join(" OR ", @_) . ")";
} else {
push @conds, "_subj NOT LIKE '" . $self->{"subj"} . "%'";
push @conds, "accttrx_has_subj(_trx, '" . $self->{"subj"} . "')";
}
push @conds, $_ if defined($_ = $self->SUPER::pre_filter);
return undef if @conds == 0;
return join " AND ", @conds;
}
# html_select_subject: Display the subject search box
sub html_select_subject : method {
local ($_, %_);
my ($self, $label, @subjs);
my ($sql, $sth, $count, $row, $thiscol, $defcol);
$self = $_[0];
# The subject
$label = h_abbr(C_("Accounting subject:"));
# Obtain all the cash subjects
$sql = "SELECT acctsubj.code FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " WHERE acctsubj.code LIKE '11%'"
. " OR acctsubj.code LIKE '12%'"
. " OR acctsubj.code LIKE '21%'"
. " OR acctsubj.code LIKE '22%'"
. " GROUP BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @_ = qw(); $i < $count; $i++) {
$row = $sth->fetchrow_hashref;
push @_, $$row{"code"};
}
undef $sth;
%_ = map { $_ => 1 } @_;
# Add all of their parents
foreach (keys %_) {
$_{$_} = 1 while s/.$//;
}
delete $_{""};
delete $_{"1"};
delete $_{"11"};
delete $_{"12"};
delete $_{"2"};
delete $_{"21"};
delete $_{"22"};
@_ = qw();
push @_, "code AS value";
if (@ALL_LINGUAS == 1) {
push @_, $DBH->strcat("code", "' '", "title")
. " AS content";
} else {
$thiscol = "title_" . getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
push @_, $DBH->strcat("code", "' '", $thiscol)
. " AS content";
} else {
$defcol = "title_" . ln($DEFAULT_LANG, LN_DATABASE);
push @_, $DBH->strcat("code", "' '", "COALESCE($thiscol, $defcol)")
. " AS content";
}
}
$sql = "SELECT " . join(", ", @_) . " FROM acctsubj"
. " WHERE " . join(" OR ", map "code=" . $DBH->quote($_), sort keys %_)
. " ORDER BY code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @subjs = qw(); $i < $count; $i++) {
push @subjs, {%{$sth->fetchrow_hashref}};
}
undef $sth;
unshift @subjs, { "value" => "", "content" => C_("current assets and liabilities"), };
# Display the form
print << "EOT";
<p>$label
<select name="subj">
EOT
foreach (@subjs) {
my ($val, $content, $selected);
$val = h($$_{"value"});
$content = h($$_{"content"});
$selected = defined $self->{"subj"} && $self->{"subj"} eq $$_{"value"}?
" selected=\"selected\"": "";
print << "EOT";
<option value="$val"$selected>$content</option>
EOT
}
print << "EOT";
</select>
</p>
EOT
return;
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $form, $url, $title);
$self = $_[0];
$form = $self->{"FORM"};
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=cash";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, $self->{"actrange"};
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=cash_details.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
@_ = map $$current{$_}, @{$self->{"listcols"}};
foreach (@_) {
$_ = "" if !defined $_;
s/"/""/g; # "
}
print join(",", map "\"$_\"", @_) . "\n";
}
return;
}
# html_pagebar: Display a page navigation bar
sub html_pagebar : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::html_pagebar;
$self->{"reverse"} = $rev;
return $r;
}
return 1;

View File

@@ -0,0 +1,477 @@
# Selima Website Content Management System
# Summary.pm: The cash summary accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-27
package Selima::List::Accounting::Reports::Cash::Summary;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use POSIX qw(floor);
use Text::Capitalize qw(capitalize_title);
use URI::Escape qw(uri_escape);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :env :l10n :lninfo :output :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserPref;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class, @cols, $sql);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The list type
$self->{"type"} = "cashsum";
# The subject
$self->{"subj"} = $self->{"FORM"}->param("subj");
$self->{"subj"} = "" if !defined $self->{"subj"};
# The page title
$_ = $self->{"subj"} eq ""?
C_("current assets and liabilities"):
acctsubj_title(acctsubj_sn($self->{"subj"}));
s/^\d+ //;
$self->{"title"} = capitalize_title(C_("Cash Book Summary - [_1]", $_));
$self->{"title"} =~ s/ - /\x{2014}/;
# Construct the view
$self->{"view"} = "acctrep_cash_summary_list";
$self->{"noselect"} = 1;
@_ = qw();
push @_, "list=cash";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, "r=m";
push @_, "m=";
$_ = $REQUEST_FILE . "?" . join "&", @_;
if ($self->{"subj"} eq "") {
@cols = qw();
push @cols, $DBH->strcat($DBH->quote($_),
"lpad(cast(extract(year FROM date) AS text), 4, '0')",
"'-'", "lpad(cast(extract(month FROM date) AS text), 2, '0')") . " AS _viewurl";
push @cols, $DBH->strcat("lpad(cast(extract(year FROM date) AS text), 4, '0')",
"'-'", "lpad(cast(extract(month FROM date) AS text), 2, '0')") . " AS month";
@_ = qw();
push @_, "acctsum_debit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '11')";
push @_, "acctsum_debit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '12')";
push @_, "acctsum_debit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '21')";
push @_, "acctsum_debit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '22')";
push @cols, join(" + ", @_) . " AS income";
@_ = qw();
push @_, "acctsum_credit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '11')";
push @_, "acctsum_credit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '12')";
push @_, "acctsum_credit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '21')";
push @_, "acctsum_credit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '22')";
push @cols, join(" + ", @_) . " AS expense";
@_ = qw();
push @_, "acctsum_balance(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '11')";
push @_, "acctsum_balance(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '12')";
push @_, "acctsum_balance(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '21')";
push @_, "acctsum_balance(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), '22')";
push @cols, join(" + ", @_) . " AS balance";
$sql = "CREATE TEMPORARY VIEW " . $self->{"view"} . " AS"
. " SELECT " . join(", ", @cols)
. " FROM accttrx"
. " GROUP BY extract(year FROM date), extract(month FROM date)"
. " ORDER BY extract(year FROM date), extract(month FROM date);\n";
} else {
@cols = qw();
push @cols, $DBH->strcat($DBH->quote($_),
"lpad(cast(extract(year FROM date) AS text), 4, '0')",
"'-'", "lpad(extract(cast(month FROM date) AS text), 2, '0')") . " AS _viewurl";
push @cols, $DBH->strcat("lpad(cast(extract(year FROM date) AS text), 4, '0')",
"'-'", "lpad(cast(extract(month FROM date) AS text), 2, '0')") . " AS month";
push @cols, "acctsum_debit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), "
. " " . $DBH->quote($self->{"subj"}) . ") AS income";
push @cols, "acctsum_credit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer),"
. " " . $DBH->quote($self->{"subj"}) . ") AS expense";
push @cols, "acctsum_balance(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), "
. " " . $DBH->quote($self->{"subj"}) . ") AS balance";
$sql = "CREATE TEMPORARY VIEW " . $self->{"view"} . " AS"
. " SELECT " . join(", ", @cols)
. " FROM accttrx"
. " GROUP BY extract(year FROM date), extract(month FROM date)"
. " ORDER BY extract(year FROM date), extract(month FROM date);\n";
}
$DBH->do($sql);
# Column labels
$self->col_labels(
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $cols, $table, $where, $orderby, $limit, $sth, $sql, $error);
my ($sumincome, $sumexpense);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if (exists $self->{"nodata"} && $self->{"nodata"}) {
$self->{"total"} = 0;
return;
}
# See if we need to use views or not.
# Views make things much faster and easier, but some DBMS has no views.
# *MySQL has no views*
if ($self->{"useview"}) {
($cols, $table, $where, $orderby, $limit) = $self->select_with_view;
} else {
($cols, $table, $where, $orderby, $limit) = $self->select_without_view;
}
# Fetch everything
$self->{"select"} = sprintf "SELECT %s FROM %s%s%s%s;\n",
$cols, $table, $where, $orderby, $limit;
$sql = $self->{"select"};
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"current"} = [];
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Do calculation on each record
($sumincome, $sumexpense) = (0, 0);
foreach (@{$self->{"current"}}) {
$sumincome += $$_{"income"};
$sumexpense += $$_{"expense"};
}
# Remove the starting and ending empty records
@_ = @{$self->{"current"}};
shift @_ while @_ > 0 && ${$_[0]}{"income"} == 0 && ${$_[0]}{"expense"} == 0;
pop @_ while @_ > 0 && ${$_[$#_]}{"income"} == 0 && ${$_[$#_]}{"expense"} == 0;
$self->{"current"} = [@_];
# Append the total record
if (@{$self->{"current"}} > 0) {
@_ = qw();
push @_, "list=cash";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, "r=a";
push @{$self->{"current"}}, {
"_viewurl" => $REQUEST_FILE . "?" . join("&", @_),
"month" => C_("Total"),
"income" => $sumincome,
"expense" => $sumexpense,
"balance" => ${(reverse @{$self->{"current"}})[0]}{"balance"},
};
}
# The number of rows per page
$_ = userpref("listsize", ref $self);
$self->{"pagesize"} = defined $_? $_: $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
if (!defined $self->{"pagesize"}) {
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Done
return $self->{"error"};
}
# Obtain the total number
$self->{"total"} = scalar @{$self->{"current"}};
$self->{"lastpage"} = floor(($self->{"total"} - 1) / $self->{"pagesize"}) + 1;
# If last page is 0 (when total is 0), set to page 1
$self->{"lastpage"} = 1 if $self->{"lastpage"} < 1;
# Check the page number
# Show the last page by default, but not reverse the number colume
$_ = $self->{"reverse"};
$self->{"reverse"} = 1;
$error = $self->check_pageno;
$self->{"reverse"} = $_;
$self->{"error"} = $error if defined $error && !defined $self->{"error"};
# Calculate the start and end record number
$self->{"startno"} = ($self->{"pageno"} - 1) * $self->{"pagesize"};
$self->{"endno"} = $self->{"pageno"} * $self->{"pagesize"} - 1;
# If there is not enough remaining records, set to the last one
$self->{"endno"} = $self->{"total"} - 1
if $self->{"endno"} > $self->{"total"} - 1;
# If the last record is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Obtain everything in this page
$self->{"current"} = [ @{$self->{"current"}}[$self->{"startno"}...$self->{"endno"}] ]
if !$self->{"iscsv"};
# Set the columns to be displayed
$self->{"cols"} = [qw(_viewurl month income expense balance)];
$self->{"listcols"} = [qw(month income expense balance)];
# Done
return $self->{"error"};
}
# page_param: Obtain page parameters
sub page_param : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::page_param;
$self->{"reverse"} = $rev;
return $r;
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The balance
if ($col eq "balance") {
if ($row{$col} > 0) {
return "<div class=\"amount\">"
. h_abbr(fmtntamount $row{$col}) . "</div>";
} elsif ($row{$col} < 0) {
return "<div class=\"amount\"><span class=\"neg\">-"
. h_abbr(fmtntamount -$row{$col}) . "</span></div>";
} else {
return "<div class=\"amount\">-</div>";
}
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# pre_filter: Set the pre-defined filter
# Make it a null function
sub pre_filter : method { }
# html_report_query: Display the report query box
sub html_report_query : method {
local ($_, %_);
my ($self, $form, $request_file, $label, $curlist);
$self = $_[0];
$form = $self->{"FORM"};
$request_file = h($REQUEST_FILE);
$label = h_abbr(C_("Query"));
$curlist = h($self->{"type"});
print << "EOT";
<form action="$request_file" method="get" accept-charset="<!--selima:charset-->">
<div class="acctrepquery">
<div><input type="hidden" name="list" value="$curlist" /></div>
EOT
# Display the subject selection if available
$self->html_select_subject if $self->can("html_select_subject");
print << "EOT";
<p><input type="hidden" name="charset" value="<!--selima:charset-->" /><input
type="submit" value="$label" />
</p>
</div>
</form>
EOT
return;
}
# html_select_subject: Display the subject search box
sub html_select_subject : method {
local ($_, %_);
my ($self, $label, @subjs);
my ($sql, $sth, $count, $row, $thiscol, $defcol);
$self = $_[0];
# The subject
$label = h_abbr(C_("Accounting subject:"));
# Obtain all the cash subjects
$sql = "SELECT acctsubj.code FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " WHERE acctsubj.code LIKE '11%'"
. " OR acctsubj.code LIKE '12%'"
. " OR acctsubj.code LIKE '21%'"
. " OR acctsubj.code LIKE '22%'"
. " GROUP BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @_ = qw(); $i < $count; $i++) {
$row = $sth->fetchrow_hashref;
push @_, $$row{"code"};
}
undef $sth;
%_ = map { $_ => 1 } @_;
# Add all of their parents
foreach (keys %_) {
$_{$_} = 1 while s/.$//;
}
delete $_{""};
delete $_{"1"};
delete $_{"11"};
delete $_{"12"};
delete $_{"2"};
delete $_{"21"};
delete $_{"22"};
@_ = qw();
push @_, "code AS value";
if (@ALL_LINGUAS == 1) {
push @_, $DBH->strcat("code", "' '", "title")
. " AS content";
} else {
$thiscol = "title_" . getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
push @_, $DBH->strcat("code", "' '", $thiscol)
. " AS content";
} else {
$defcol = "title_" . ln($DEFAULT_LANG, LN_DATABASE);
push @_, $DBH->strcat("code", "' '", "COALESCE($thiscol, $defcol)")
. " AS content";
}
}
$sql = "SELECT " . join(", ", @_) . " FROM acctsubj"
. " WHERE " . join(" OR ", map "code=" . $DBH->quote($_), sort keys %_)
. " ORDER BY code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @subjs = qw(); $i < $count; $i++) {
push @subjs, {%{$sth->fetchrow_hashref}};
}
undef $sth;
unshift @subjs, { "value" => "", "content" => C_("current assets and liabilities"), };
# Display the form
print << "EOT";
<p>$label
<select name="subj">
EOT
foreach (@subjs) {
my ($val, $content, $selected);
$val = h($$_{"value"});
$content = h($$_{"content"});
$selected = defined $self->{"subj"} && $self->{"subj"} eq $$_{"value"}?
" selected=\"selected\"": "";
print << "EOT";
<option value="$val"$selected>$content</option>
EOT
}
print << "EOT";
</select>
</p>
EOT
return;
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=cashsum";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=cash_summary.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
print join(",", map $$current{$_}, @{$self->{"listcols"}}) . "\n";
}
return;
}
# html_pagebar: Display a page navigation bar
sub html_pagebar : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::html_pagebar;
$self->{"reverse"} = $rev;
return $r;
}
return 1;

View File

@@ -0,0 +1,366 @@
# Selima Website Content Management System
# IncmStat.pm: The income statement accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-10-03
package Selima::List::Accounting::Reports::IncmStat;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo :output :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = C_("Income Statement");
# The list type
$self->{"type"} = "incmstat";
# The default number of rows per page
$self->{"DEFAULT_LIST_SIZE"} = undef;
# Known columns that should not be sorted with
# List sorting is disabled here at all
push @{$self->{"COLS_NO_SORT_BY"}}, qw(amount1 amount2);
$self->{"noselect"} = 1;
# Column labels
$self->col_labels(
"amount1" => C_("Amount"),
"amount2" => C_("Amount"),
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, @cols, $title, $sth, $sql, $error);
my (@subjs, $sum, $balance);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if (exists $self->{"nodata"} && $self->{"nodata"}) {
$self->{"total"} = 0;
return;
}
# Construct the SQL query statement
# Obtain the period once
$self->sql_filter;
@cols = qw();
if (@ALL_LINGUAS == 1) {
$title = "acctsubj.title";
} else {
my ($lndb, $lndbdef);
$lndb = getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
$title = "acctsubj.title_$lndb";
} else {
$lndbdef = ln $DEFAULT_LANG, LN_DATABASE;
$title = "COALESCE(acctsubj.title_$lndb, acctsubj.title_$lndbdef)";
}
}
push @cols, $DBH->strcat("acctsubj.code", "' '", $title) . " AS subj";
push @cols, "sum(CASE WHEN acctrecs.credit THEN acctrecs.amount ELSE -acctrecs.amount END)"
. " AS balance";
$sql = "SELECT " . join(", ", @cols)
. " FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE date>=" . $DBH->quote($self->{"startdate"})
. " AND date<=" . $DBH->quote($self->{"enddate"})
. " AND NOT (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " GROUP BY acctsubj.code, $title"
. " ORDER BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
@subjs = qw();
push @subjs, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
$self->{"current"} = [];
$balance = 0;
foreach my $majsubj ((4, 5, 6, 7, 8, 9)) {
push @{$self->{"current"}}, {
"subj" => acctsubj_title(acctsubj_sn($majsubj)),
"amount1" => "",
"amount2" => "",
};
# non-operating revenue and expenses, other income (expense)
if ($majsubj == 7) {
# non-operating revenue
$sum = 0;
push @{$self->{"current"}}, {
"subj" => acctsubj_title(acctsubj_sn(71)),
"amount1" => "",
"amount2" => "",
};
foreach (grep $$_{"subj"} =~ /^7[1234]/, @subjs) {
push @{$self->{"current"}}, {
"subj" => $$_{"subj"},
"amount1" => $$_{"balance"},
"amount2" => "",
};
$sum += $$_{"balance"};
}
push @{$self->{"current"}}, {
"subj" => C_("Total"),
"amount1" => "",
"amount2" => $sum,
};
$balance += $sum;
# non-operating expenses
$sum = 0;
push @{$self->{"current"}}, {
"subj" => acctsubj_title(acctsubj_sn(75)),
"amount1" => "",
"amount2" => "",
};
foreach (grep $$_{"subj"} =~ /^7[5678]/, @subjs) {
push @{$self->{"current"}}, {
"subj" => $$_{"subj"},
"amount1" => $$_{"balance"},
"amount2" => "",
};
$sum += $$_{"balance"};
}
push @{$self->{"current"}}, {
"subj" => C_("Total"),
"amount1" => "",
"amount2" => $sum,
};
$balance += $sum;
# Other categories
} else {
$sum = 0;
%_ = map { substr($$_{"subj"}, 0, 2) => 1 }
grep $$_{"subj"} =~ /^$majsubj/, @subjs;
foreach my $minsubj (sort keys %_) {
push @{$self->{"current"}}, {
"subj" => acctsubj_title(acctsubj_sn($minsubj)),
"amount1" => "",
"amount2" => "",
};
foreach (grep $$_{"subj"} =~ /^$minsubj/, @subjs) {
push @{$self->{"current"}}, {
"subj" => $$_{"subj"},
"amount1" => $$_{"balance"},
"amount2" => "",
};
$sum += $$_{"balance"};
}
}
push @{$self->{"current"}}, {
"subj" => C_("Total"),
"amount1" => "",
"amount2" => $sum,
};
$balance += $sum;
}
# 4 operating revenue
# No balance after operating revenue
if ($majsubj != 4) {
# 5 operating costs
$_ = C_("Gross income") if $majsubj == 5;
# 6 operating expenses
$_ = C_("Operating income") if $majsubj == 6;
# 7 non-operating revenue and expenses, other income (expense)
$_ = C_("Before tax income") if $majsubj == 7;
# 8 income tax expense (or benefit)
$_ = C_("After tax income") if $majsubj == 8;
# 9 nonrecurring gain or loss
$_ = acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_CUR))
if $majsubj == 9;
push @{$self->{"current"}}, {
"subj" => $_,
"amount1" => "",
"amount2" => $balance,
};
}
# Put a blank separator record
if ($majsubj != 9) {
push @{$self->{"current"}}, {
"subj" => "",
"amount1" => "",
"amount2" => "",
};
}
}
# The number of rows per page
$self->{"pagesize"} = $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Set the columns to be displayed
$self->{"cols"} = [qw(subj amount1 amount2)];
$self->{"listcols"} = [qw(subj amount1 amount2)];
# Done
return $self->{"error"};
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The debit and the credit
if ($col eq "subj") {
if ($row{$col} =~ /^\d\d /) {
return "<div class=\"subjlv2\">"
. h_abbr($row{$col}) . "</div>";
} elsif ( $row{$col} =~ /^\d\d\d/
&& substr($row{$col}, 0, 4) ne ACCTSUBJ_INCOME_CUR) {
return "<div class=\"subjlastlv\">"
. h_abbr($row{$col}) . "</div>";
} else {
return h_abbr($row{$col});
}
}
# The amount
if ($col eq "amount1" || $col eq "amount2") {
return "" if $row{$col} eq "";
if ($row{$col} < 0) {
return "<div class=\"amount\"><span class=\"neg\">("
. h_abbr(fmtntamount -$row{$col}) . ")</span></div>";
} else {
return "<div class=\"amount\">"
. h_abbr(fmtntamount $row{$col}) . "</div>";
}
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=incmstat";
push @_, $self->{"actrange"};
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=income_statement.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
@_ = map $$current{$_}, @{$self->{"listcols"}};
foreach (@_) {
$_ = "" if !defined $_;
s/"/""/g; # "
}
print join(",", map "\"$_\"", @_) . "\n";
}
return;
}
# html_liststat: Display the list statistics
sub html_liststat : method {
local ($_, %_);
my $self;
$self = $_[0];
# Print the current time period
if (!$self->{"nodata"}) {
my ($message, $from, $to);
($from, $to) = ($self->{"startdate"}, $self->{"enddate"});
$message = h(C_("From [_1] to [_2].", $from, $to));
print << "EOT";
<p>$message</p>
EOT
}
return;
}
# html_listprefform: Display a form to change the list preference
# Make it a null function
sub html_listprefform : method {}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my ($self, $total);
$self = $_[0];
$total = $self->{"total"};
# Inherit the empty list statistics message
return $self->SUPER::liststat_message if $self->{"total"} == 0;
return C_("[*,_1,accounting subject].", $self->{"total"} - 1);
}
return 1;

View File

@@ -0,0 +1,322 @@
# Selima Website Content Management System
# Journal.pm: The journal accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-29
package Selima::List::Accounting::Reports::Journal;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use POSIX qw(floor);
use URI::Escape qw(uri_escape);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo :output :requri);
use Selima::GetLang;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserPref;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = C_("Journal");
# The list type
$self->{"type"} = "journal";
if (@ALL_LINGUAS == 1) {
$self->{"view"} = "acctrep_search_list";
} else {
$self->{"view"} = "acctrep_search_list_" . getlang(LN_DATABASE);
}
# Columns should be displayed in a reversed order
$self->{"reverse"} = 1;
# Column labels
$self->col_labels(
"trxno" => C_("Transaction Number"),
"note" => C_("Note"),
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $cols, $table, $where, $orderby, $limit, $sth, $sql, $error);
my (@debits, @credits);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if (exists $self->{"nodata"} && $self->{"nodata"}) {
$self->{"total"} = 0;
return;
}
# See if we need to use views or not.
# Views make things much faster and easier, but some DBMS has no views.
# *MySQL has no views*
if ($self->{"useview"}) {
($cols, $table, $where, $orderby, $limit) = $self->select_with_view;
} else {
($cols, $table, $where, $orderby, $limit) = $self->select_without_view;
}
# Fetch everything
$self->{"select"} = sprintf "SELECT %s FROM %s%s%s%s;\n",
$cols, $table, $where, $orderby, $limit;
$sql = $self->{"select"};
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"current"} = [];
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Set the selection column
unshift @{$self->{"cols"}}, "_sel";
$$_{"_sel"} = 1 foreach @{$self->{"current"}};
# Find the carry-over balance
@_ = qw();
push @_, "acctsubj.code AS code";
push @_, "sum(CASE WHEN acctrecs.credit THEN -amount ELSE amount END) AS sum";
$sql = "SELECT " . join(", ", @_) . " FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " AND acctsubj.code != " . $DBH->quote(ACCTSUBJ_INCOME_ACUM)
. " AND accttrx.date<" . $DBH->quote($self->{"startdate"})
. " GROUP BY acctsubj.code"
. " ORDER BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
for ($_ = 0, @_ = qw(); $_ < $sth->rows; $_++) {
push @_, {%{$sth->fetchrow_hashref}};
}
undef $sth;
# Create the carry-over transaction
@debits = qw();
@credits = qw();
foreach (@_) {
# Positive balance - from carry-over to account
if ($$_{"sum"} > 0) {
push @debits, {
"_sel" => 0,
"_selurl" => undef,
"date" => $self->{"startdate"},
"trxno" => "",
"subj" => acctsubj_title(acctsubj_sn($$_{"code"})),
"summary" => "",
"debit" => $$_{"sum"},
"credit" => 0,
"note" => C_("Brought forward"),
};
push @credits, {
"_sel" => 0,
"_selurl" => undef,
"date" => $self->{"startdate"},
"trxno" => "",
"subj" => acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_ACUM)),
"summary" => acctsubj_title(acctsubj_sn($$_{"code"})),
"debit" => 0,
"credit" => $$_{"sum"},
"note" => C_("Brought forward"),
};
# Negative balance - from account to carry-over
} elsif ($$_{"sum"} < 0) {
push @debits, {
"_sel" => 0,
"_selurl" => undef,
"date" => $self->{"startdate"},
"trxno" => "",
"subj" => acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_ACUM)),
"summary" => acctsubj_title(acctsubj_sn($$_{"code"})),
"debit" => -$$_{"sum"},
"credit" => 0,
"note" => C_("Brought forward"),
};
push @credits, {
"_sel" => 0,
"_selurl" => undef,
"date" => $self->{"startdate"},
"trxno" => "",
"subj" => acctsubj_title(acctsubj_sn($$_{"code"})),
"summary" => "",
"debit" => 0,
"credit" => -$$_{"sum"},
"note" => C_("Brought forward"),
};
}
# Skip subjects with zero balances
}
$self->{"current"} = [@debits, @credits, @{$self->{"current"}}];
# The number of rows per page
$_ = userpref("listsize", ref $self);
$self->{"pagesize"} = defined $_? $_: $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
if (!defined $self->{"pagesize"}) {
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Done
return $self->{"error"};
}
# Obtain the total number
$self->{"total"} = scalar @{$self->{"current"}};
$self->{"lastpage"} = floor(($self->{"total"} - 1) / $self->{"pagesize"}) + 1;
# If last page is 0 (when total is 0), set to page 1
$self->{"lastpage"} = 1 if $self->{"lastpage"} < 1;
# Check the page number
# Show the last page by default, but not reverse the number colume
$_ = $self->{"reverse"};
$self->{"reverse"} = 1;
$error = $self->check_pageno;
$self->{"reverse"} = $_;
$self->{"error"} = $error if defined $error && !defined $self->{"error"};
# Calculate the start and end record number
$self->{"startno"} = ($self->{"pageno"} - 1) * $self->{"pagesize"};
$self->{"endno"} = $self->{"pageno"} * $self->{"pagesize"} - 1;
# If there is not enough remaining records, set to the last one
$self->{"endno"} = $self->{"total"} - 1
if $self->{"endno"} > $self->{"total"} - 1;
# If the last record is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Obtain everything in this page
$self->{"current"} = [ @{$self->{"current"}}[$self->{"startno"}...$self->{"endno"}] ]
if !$self->{"iscsv"};
# Set the columns to be displayed
$self->{"listcols"} = [qw(date trxno subj summary debit credit note)];
# Done
return $self->{"error"};
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# The subject
if ($col eq "subj") {
if ($row{"credit"} > 0) {
return "<div class=\"crdtsubj\">"
. h_abbr($row{$col}) . "</div>";
} else {
return h_abbr($row{$col});
}
}
# The summary
if ($col eq "summary") {
return "" if !defined $row{$col};
return h_abbr($row{$col});
}
# Null/no value
return h(t_notset()) if !defined $row{$col};
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=journal";
push @_, $self->{"actrange"};
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=journal.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
@_ = map $$current{$_}, @{$self->{"listcols"}};
foreach (@_) {
$_ = "" if !defined $_;
s/"/""/g; # "
}
print join(",", map "\"$_\"", @_) . "\n";
}
return;
}
# html_list: List the items
sub html_list : method {
local ($_, %_);
my $self;
$self = $_[0];
$self->{"reverse"} = 0;
# Run the parent method
$self->SUPER::html_list;
$self->{"reverse"} = 1;
return;
}
return 1;

View File

@@ -0,0 +1,412 @@
# Selima Website Content Management System
# Ledger.pm: The ledger accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-24
package Selima::List::Accounting::Reports::Ledger;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use POSIX qw(floor);
use Text::Capitalize qw(capitalize_title);
use URI::Escape qw(uri_escape);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo :output :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserPref;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The list type
$self->{"type"} = "ldgr";
if (@ALL_LINGUAS == 1) {
$self->{"view"} = "acctrep_ledger_list";
} else {
$self->{"view"} = "acctrep_ledger_list_" . getlang(LN_DATABASE);
}
# The subject
$self->{"subj"} = $self->{"FORM"}->param("subj");
$self->{"subj"} = ACCTSUBJ_CASH if !defined $self->{"subj"};
# The page title
$_ = acctsubj_title(acctsubj_sn($self->{"subj"}));
s/^\d+ //;
$self->{"title"} = capitalize_title(C_("Ledger - [_1]", $_));
$self->{"title"} =~ s/ - /\x{2014}/;
# Column labels
$self->col_labels(
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $cols, $table, $where, $orderby, $limit, $sth, $sql, $error);
my ($balance, @carryover);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if (exists $self->{"nodata"} && $self->{"nodata"}) {
$self->{"total"} = 0;
return;
}
# See if we need to use views or not.
# Views make things much faster and easier, but some DBMS has no views.
# *MySQL has no views*
if ($self->{"useview"}) {
($cols, $table, $where, $orderby, $limit) = $self->select_with_view;
} else {
($cols, $table, $where, $orderby, $limit) = $self->select_without_view;
}
# Fetch everything
$self->{"select"} = sprintf "SELECT %s FROM %s%s%s%s;\n",
$cols, $table, $where, $orderby, $limit;
$sql = $self->{"select"};
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"current"} = [];
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Find the carry-over records
if ($self->{"subj"} =~ /^[123]/) {
@_ = qw();
push @_, "acctsubj.code AS code";
push @_, "sum(CASE WHEN acctrecs.credit THEN -amount ELSE amount END) AS sum";
$sql = "SELECT " . join(", ", @_) . " FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE acctsubj.code LIKE " . $DBH->strcat($DBH->quote($self->{"subj"}), "'%'")
. " AND accttrx.date<" . $DBH->quote($self->{"startdate"})
. " GROUP BY acctsubj.code"
. " ORDER BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
for ($_ = 0, @_ = qw(); $_ < $sth->rows; $_++) {
push @_, {%{$sth->fetchrow_hashref}};
}
undef $sth;
# Create the carry over transaction
@carryover = qw();
foreach (@_) {
# Positive balance - from carry-over to account
if ($$_{"sum"} > 0) {
push @carryover, {
"_sel" => 0,
"_selurl" => undef,
"_subj" => $self->{"subj"},
"_date" => $self->{"startdate"},
"date" => $self->{"startdate"},
"subj" => acctsubj_title(acctsubj_sn($$_{"code"})),
"summary" => C_("Brought forward"),
"debit" => $$_{"sum"},
"credit" => 0,
"balance" => 0,
};
# Negative balance - from account to carry-over
} elsif ($$_{"sum"} < 0) {
push @carryover, {
"_sel" => 0,
"_selurl" => undef,
"_subj" => $self->{"subj"},
"_date" => $self->{"startdate"},
"date" => $self->{"startdate"},
"subj" => acctsubj_title(acctsubj_sn($$_{"code"})),
"summary" => C_("Brought forward"),
"debit" => 0,
"credit" => -$$_{"sum"},
"balance" => 0,
};
}
# Skip subjects with zero balances
}
$self->{"current"} = [@carryover, @{$self->{"current"}}];
}
# Calculate the balance
$balance = 0;
foreach (@{$self->{"current"}}) {
$balance = $balance + $$_{"debit"} - $$_{"credit"};
$$_{"balance"} = $balance;
}
# The number of rows per page
$_ = userpref("listsize", ref $self);
$self->{"pagesize"} = defined $_? $_: $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
if (!defined $self->{"pagesize"}) {
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Done
return $self->{"error"};
}
# Obtain the total number
$self->{"total"} = scalar @{$self->{"current"}};
$self->{"lastpage"} = floor(($self->{"total"} - 1) / $self->{"pagesize"}) + 1;
# If last page is 0 (when total is 0), set to page 1
$self->{"lastpage"} = 1 if $self->{"lastpage"} < 1;
# Check the page number
# Show the last page by default, but not reverse the number colume
$_ = $self->{"reverse"};
$self->{"reverse"} = 1;
$error = $self->check_pageno;
$self->{"reverse"} = $_;
$self->{"error"} = $error if defined $error && !defined $self->{"error"};
# Calculate the start and end record number
$self->{"startno"} = ($self->{"pageno"} - 1) * $self->{"pagesize"};
$self->{"endno"} = $self->{"pageno"} * $self->{"pagesize"} - 1;
# If there is not enough remaining records, set to the last one
$self->{"endno"} = $self->{"total"} - 1
if $self->{"endno"} > $self->{"total"} - 1;
# If the last record is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Obtain everything in this page
$self->{"current"} = [ @{$self->{"current"}}[$self->{"startno"}...$self->{"endno"}] ]
if !$self->{"iscsv"};
# Set the columns to be displayed
$self->{"listcols"} = [qw(date subj summary debit credit balance)];
# Done
return $self->{"error"};
}
# page_param: Obtain page parameters
sub page_param : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::page_param;
$self->{"reverse"} = $rev;
return $r;
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The balance
if ($col eq "balance") {
return "" if $row{$col} eq "";
if ($row{$col} > 0) {
return "<div class=\"amount\">" . h_abbr(C_("Debit")) . " "
. h_abbr(fmtntamount $row{$col}) . "</div>";
} elsif ($row{$col} < 0) {
return "<div class=\"amount\"><span class=\"neg\">"
. h_abbr(C_("Credit")) . " "
. h_abbr(fmtntamount -$row{$col}) . "</span></div>";
} else {
return "<div class=\"amount\">-</div>";
}
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# pre_filter: Set the pre-defined filter
sub pre_filter : method {
local ($_, %_);
my $self;
$self = $_[0];
@_ = qw();
push @_, "_subj LIKE '" . $self->{"subj"} . "%'";
push @_, $_ if defined($_ = $self->SUPER::pre_filter);
return undef if @_ == 0;
return join " AND ", @_;
}
# html_select_subject: Display the subject search box
sub html_select_subject : method {
local ($_, %_);
my ($self, $label, @subjs);
my ($sql, $sth, $count, $row, $thiscol, $defcol);
$self = $_[0];
# The subject
$label = h_abbr(C_("Accounting subject:"));
# Obtain all the using subjects
$sql = "SELECT acctsubj.code FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " GROUP BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @_ = qw(); $i < $count; $i++) {
$row = $sth->fetchrow_hashref;
push @_, $$row{"code"};
}
undef $sth;
%_ = map { $_ => 1 } @_;
# Add all of their parents
foreach (keys %_) {
$_{$_} = 1 while s/.$//;
}
delete $_{""};
@_ = qw();
push @_, "code AS value";
if (@ALL_LINGUAS == 1) {
push @_, $DBH->strcat("code", "' '", "title")
. " AS content";
} else {
$thiscol = "title_" . getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
push @_, $DBH->strcat("code", "' '", $thiscol)
. " AS content";
} else {
$defcol = "title_" . ln($DEFAULT_LANG, LN_DATABASE);
push @_, $DBH->strcat("code", "' '", "COALESCE($thiscol, $defcol)")
. " AS content";
}
}
$sql = "SELECT " . join(", ", @_) . " FROM acctsubj"
. " WHERE " . join(" OR ", map "code=" . $DBH->quote($_), sort keys %_)
. " ORDER BY code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @subjs = qw(); $i < $count; $i++) {
push @subjs, {%{$sth->fetchrow_hashref}};
}
undef $sth;
# Display the form
print << "EOT";
<p>$label
<select name="subj">
EOT
foreach (@subjs) {
my ($val, $content, $selected);
$val = h($$_{"value"});
$content = h($$_{"content"});
$selected = defined $self->{"subj"} && $self->{"subj"} eq $$_{"value"}?
" selected=\"selected\"": "";
print << "EOT";
<option value="$val"$selected>$content</option>
EOT
}
print << "EOT";
</select>
</p>
EOT
return;
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=ldgr";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, $self->{"actrange"};
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=ledger.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
@_ = map $$current{$_}, @{$self->{"listcols"}};
foreach (@_) {
$_ = "" if !defined $_;
s/"/""/g; # "
}
print join(",", map "\"$_\"", @_) . "\n";
}
return;
}
# html_pagebar: Display a page navigation bar
sub html_pagebar : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::html_pagebar;
$self->{"reverse"} = $rev;
return $r;
}
return 1;

View File

@@ -0,0 +1,422 @@
# Selima Website Content Management System
# Summary.pm: The summary ledger accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-30
package Selima::List::Accounting::Reports::Ledger::Summary;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use POSIX qw(floor);
use Text::Capitalize qw(capitalize_title);
use URI::Escape qw(uri_escape);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :env :l10n :lninfo :output :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::UserPref;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class, @cols, $sql);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The list type
$self->{"type"} = "ldgrsum";
# The subject
$self->{"subj"} = $self->{"FORM"}->param("subj");
$self->{"subj"} = ACCTSUBJ_CASH if !defined $self->{"subj"};
# The page title
$_ = acctsubj_title(acctsubj_sn($self->{"subj"}));
s/^\d+ //;
$self->{"title"} = capitalize_title(C_("Ledger Summary - [_1]", $_));
$self->{"title"} =~ s/ - /\x{2014}/;
# Construct the view
$self->{"view"} = "acctrep_ledger_summary_list";
$self->{"noselect"} = 1;
@_ = qw();
push @_, "list=ldgr";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, "r=m";
push @_, "m=";
$_ = $REQUEST_FILE . "?" . join "&", @_;
@cols = qw();
push @cols, $DBH->strcat($DBH->quote($_),
"lpad(cast(extract(year FROM date) AS text), 4, '0')",
"'-'", "lpad(cast(extract(month FROM date) AS text), 2, '0')") . " AS _viewurl";
push @cols, $DBH->strcat("lpad(cast(extract(year FROM date) AS text), 4, '0')",
"'-'", "lpad(cast(extract(month FROM date) AS text), 2, '0')") . " AS month";
push @cols, "acctsum_debit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer),"
. " " . $DBH->quote($self->{"subj"}) . ") AS debit";
push @cols, "acctsum_credit(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), "
. " " . $DBH->quote($self->{"subj"}) . ") AS credit";
push @cols, "acctsum_balance(cast(extract(year FROM date) AS integer),"
. " cast(extract(month FROM date) AS integer), "
. " " . $DBH->quote($self->{"subj"}) . ") AS balance";
$sql = "CREATE TEMPORARY VIEW " . $self->{"view"} . " AS"
. " SELECT " . join(", ", @cols)
. " FROM accttrx"
. " GROUP BY extract(year FROM date), extract(month FROM date)"
. " ORDER BY extract(year FROM date), extract(month FROM date);\n";
$DBH->do($sql);
# Column labels
$self->col_labels(
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $cols, $table, $where, $orderby, $limit, $sth, $sql, $error);
my ($sumdebit, $sumcredit);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# No need to run if there is no data at all
if (exists $self->{"nodata"} && $self->{"nodata"}) {
$self->{"total"} = 0;
return;
}
# See if we need to use views or not.
# Views make things much faster and easier, but some DBMS has no views.
# *MySQL has no views*
if ($self->{"useview"}) {
($cols, $table, $where, $orderby, $limit) = $self->select_with_view;
} else {
($cols, $table, $where, $orderby, $limit) = $self->select_without_view;
}
# Fetch everything
$self->{"select"} = sprintf "SELECT %s FROM %s%s%s%s;\n",
$cols, $table, $where, $orderby, $limit;
$sql = $self->{"select"};
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"current"} = [];
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Do calculation on each record
($sumdebit, $sumcredit) = (0, 0);
foreach (@{$self->{"current"}}) {
$$_{"balance"} = 0 if !defined $$_{"balance"};
$sumdebit += $$_{"debit"};
$sumcredit += $$_{"credit"};
}
# Remove the starting and ending empty records
@_ = @{$self->{"current"}};
shift @_ while @_ > 0 && ${$_[0]}{"credit"} == 0 && ${$_[0]}{"debit"} == 0;
pop @_ while @_ > 0 && ${$_[$#_]}{"credit"} == 0 && ${$_[$#_]}{"debit"} == 0;
$self->{"current"} = [@_];
# Append the total record
if (@{$self->{"current"}} > 0) {
@_ = qw();
push @_, "list=ldgr";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, "r=a";
push @{$self->{"current"}}, {
"_viewurl" => $REQUEST_FILE . "?" . join("&", @_),
"month" => C_("Total"),
"debit" => $sumdebit,
"credit" => $sumcredit,
"balance" => ${(reverse @{$self->{"current"}})[0]}{"balance"},
};
}
# The number of rows per page
$_ = userpref("listsize", ref $self);
$self->{"pagesize"} = defined $_? $_: $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
if (!defined $self->{"pagesize"}) {
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Done
return $self->{"error"};
}
# Obtain the total number
$self->{"total"} = scalar @{$self->{"current"}};
$self->{"lastpage"} = floor(($self->{"total"} - 1) / $self->{"pagesize"}) + 1;
# If last page is 0 (when total is 0), set to page 1
$self->{"lastpage"} = 1 if $self->{"lastpage"} < 1;
# Check the page number
# Show the last page by default, but not reverse the number colume
$_ = $self->{"reverse"};
$self->{"reverse"} = 1;
$error = $self->check_pageno;
$self->{"reverse"} = $_;
$self->{"error"} = $error if defined $error && !defined $self->{"error"};
# Calculate the start and end record number
$self->{"startno"} = ($self->{"pageno"} - 1) * $self->{"pagesize"};
$self->{"endno"} = $self->{"pageno"} * $self->{"pagesize"} - 1;
# If there is not enough remaining records, set to the last one
$self->{"endno"} = $self->{"total"} - 1
if $self->{"endno"} > $self->{"total"} - 1;
# If the last record is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Obtain everything in this page
$self->{"current"} = [ @{$self->{"current"}}[$self->{"startno"}...$self->{"endno"}] ]
if !$self->{"iscsv"};
# Set the columns to be displayed
$self->{"cols"} = [qw(_viewurl month debit credit balance)];
$self->{"listcols"} = [qw(month debit credit balance)];
# Done
return $self->{"error"};
}
# page_param: Obtain page parameters
sub page_param : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::page_param;
$self->{"reverse"} = $rev;
return $r;
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_notset()) if !defined $row{$col};
# The balance
if ($col eq "balance") {
if ($row{$col} > 0) {
return "<div class=\"amount\">" . h_abbr(C_("Debit")) . " "
. h_abbr(fmtntamount $row{$col}) . "</div>";
} elsif ($row{$col} < 0) {
return "<div class=\"amount\"><span class=\"neg\">"
. h_abbr(C_("Credit")) . " "
. h_abbr(fmtntamount -$row{$col}) . "</span></div>";
} else {
return "<div class=\"amount\">-</div>";
}
}
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# pre_filter: Set the pre-defined filter
# Make it a null function
sub pre_filter : method { }
# html_report_query: Display the report query box
sub html_report_query : method {
local ($_, %_);
my ($self, $form, $request_file, $label, $curlist);
$self = $_[0];
$form = $self->{"FORM"};
$request_file = h($REQUEST_FILE);
$label = h_abbr(C_("Query"));
$curlist = h($self->{"type"});
print << "EOT";
<form action="$request_file" method="get" accept-charset="<!--selima:charset-->">
<div class="acctrepquery">
<div><input type="hidden" name="list" value="$curlist" /></div>
EOT
# Display the subject selection if available
$self->html_select_subject if $self->can("html_select_subject");
print << "EOT";
<p><input type="hidden" name="charset" value="<!--selima:charset-->" /><input
type="submit" value="$label" />
</p>
</div>
</form>
EOT
return;
}
# html_select_subject: Display the subject search box
sub html_select_subject : method {
local ($_, %_);
my ($self, $label, @subjs);
my ($sql, $sth, $count, $row, $thiscol, $defcol);
$self = $_[0];
# The subject
$label = h_abbr(C_("Accounting subject:"));
# Obtain all the using subjects
$sql = "SELECT acctsubj.code FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " GROUP BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @_ = qw(); $i < $count; $i++) {
$row = $sth->fetchrow_hashref;
push @_, $$row{"code"};
}
undef $sth;
%_ = map { $_ => 1 } @_;
# Add all of their parents
foreach (keys %_) {
$_{$_} = 1 while s/.$//;
}
delete $_{""};
@_ = qw();
push @_, "code AS value";
if (@ALL_LINGUAS == 1) {
push @_, $DBH->strcat("code", "' '", "title")
. " AS content";
} else {
$thiscol = "title_" . getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
push @_, $DBH->strcat("code", "' '", $thiscol)
. " AS content";
} else {
$defcol = "title_" . ln($DEFAULT_LANG, LN_DATABASE);
push @_, $DBH->strcat("code", "' '", "COALESCE($thiscol, $defcol)")
. " AS content";
}
}
$sql = "SELECT " . join(", ", @_) . " FROM acctsubj"
. " WHERE " . join(" OR ", map "code=" . $DBH->quote($_), sort keys %_)
. " ORDER BY code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0, @subjs = qw(); $i < $count; $i++) {
push @subjs, {%{$sth->fetchrow_hashref}};
}
undef $sth;
# Display the form
print << "EOT";
<p>$label
<select name="subj">
EOT
foreach (@subjs) {
my ($val, $content, $selected);
$val = h($$_{"value"});
$content = h($$_{"content"});
$selected = defined $self->{"subj"} && $self->{"subj"} eq $$_{"value"}?
" selected=\"selected\"": "";
print << "EOT";
<option value="$val"$selected>$content</option>
EOT
}
print << "EOT";
</select>
</p>
EOT
return;
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=ldgrsum";
push @_, "subj=" . uri_escape($self->{"subj"});
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=ledger_summary.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
print join(",", map $$current{$_}, @{$self->{"listcols"}}) . "\n";
}
return;
}
# html_pagebar: Display a page navigation bar
sub html_pagebar : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::html_pagebar;
$self->{"reverse"} = $rev;
return $r;
}
return 1;

View File

@@ -0,0 +1,120 @@
# Selima Website Content Management System
# Search.pm: The accounting data search result list.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-29
package Selima::List::Accounting::Reports::Search;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use Selima::DataVars qw(:l10n :lninfo);
use Selima::GetLang;
use Selima::Logging;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
if (!defined $self->{"query"}) {
$self->{"title"} = C_("Search the Accounting Records");
} else {
$self->{"title"} = C_("Search Result");
}
if (@ALL_LINGUAS == 1) {
$self->{"view"} = "acctrep_search_list";
} else {
$self->{"view"} = "acctrep_search_list_" . getlang(LN_DATABASE);
}
# Column labels
$self->col_labels(
"trxno" => C_("Transaction Number"),
"note" => C_("Note"),
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my $self;
$self = $_[0];
# No search specified
if (!defined $self->{"query"}) {
$self->{"total"} = undef;
return $self->{"error"};
}
# Check the query phrase
# Regularize it
$self->{"query"} =~ s/^\s*(.*?)\s*$/$1/;
# Check if it is filled
if ($self->{"query"} eq"") {
$self->{"total"} = undef;
$self->{"error"} = {"msg"=>N_("Please fill in your query.")};
return $self->{"error"};
}
# Run the parent method
$self->SUPER::fetch;
# Set the columns to be displayed
$self->{"listcols"} = [qw(date trxno subj summary debit credit note)];
# Done
return $self->{"error"};
}
# check_pageno: Check the page number
# Default to the last page
sub check_pageno : method {
local ($_, %_);
my ($self, $rev);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$self->SUPER::check_pageno;
$self->{"reverse"} = $rev;
return;
}
# pre_filter: Set the pre-defined filter
# Make it a null function
sub pre_filter : method { }
# html_report_query: Display the report query box
# Make it a null function
sub html_report_query : method { }
# html_pagebar: Display a page navigation bar
# The first page needs a page number, because default to the last page
sub html_pagebar : method {
local ($_, %_);
my ($self, $rev, $r);
$self = $_[0];
$rev = $self->{"reverse"};
$self->{"reverse"} = 1;
# Run the parent method
$r = $self->SUPER::html_pagebar;
$self->{"reverse"} = $rev;
return $r;
}
return 1;

View File

@@ -0,0 +1,268 @@
# Selima Website Content Management System
# TriBlnc.pm: The trial balance accounting report.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-29
package Selima::List::Accounting::Reports::TriBlnc;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Reports);
use Selima::Accounting;
use Selima::CommText;
use Selima::DataVars qw($DBH :l10n :lninfo :output :requri);
use Selima::GetLang;
use Selima::LnInfo;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = C_("Trial Balance");
# The list type
$self->{"type"} = "tb";
# The default number of rows per page
$self->{"DEFAULT_LIST_SIZE"} = undef;
$self->{"noselect"} = 1;
# Column labels
$self->col_labels(
);
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, @cols, $title, $sth, $sql, $error);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# Construct the SQL query statement
# Obtain the period once
$self->sql_filter;
@cols = qw();
if (@ALL_LINGUAS == 1) {
$title = "acctsubj.title";
} else {
my ($lndb, $lndbdef);
$lndb = getlang LN_DATABASE;
if (getlang eq $DEFAULT_LANG) {
$title = "acctsubj.title_$lndb";
} else {
$lndbdef = ln $DEFAULT_LANG, LN_DATABASE;
$title = "COALESCE(acctsubj.title_$lndb, acctsubj.title_$lndbdef)";
}
}
push @cols, "acctsubj.code AS code";
push @cols, $DBH->strcat("acctsubj.code", "' '", $title) . " AS subj";
push @cols, "sum(CASE WHEN acctrecs.credit THEN -acctrecs.amount ELSE acctrecs.amount END)"
. " AS balance";
$self->{"current"} = [];
# The real accounts
$sql = "SELECT " . join(", ", @cols)
. " FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE date<=" . $DBH->quote($self->{"enddate"})
. " AND (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " GROUP BY acctsubj.code, $title"
. " ORDER BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# The nominal accounts
$sql = "SELECT " . join(", ", @cols)
. " FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE date>=" . $DBH->quote($self->{"startdate"})
. " AND date<=" . $DBH->quote($self->{"enddate"})
. " AND NOT (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " GROUP BY acctsubj.code, $title"
. " ORDER BY acctsubj.code;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Obtain the carry-over record of assets/liabilities
$_ = "sum(CASE WHEN acctrecs.credit THEN -acctrecs.amount ELSE acctrecs.amount END)"
. " AS balance";
$sql = "SELECT $_ FROM acctrecs"
. " LEFT JOIN acctsubj ON acctrecs.subj=acctsubj.sn"
. " LEFT JOIN accttrx ON acctrecs.trx=accttrx.sn"
. " WHERE (acctsubj.code LIKE '1%'"
. " OR acctsubj.code LIKE '2%'"
. " OR acctsubj.code LIKE '3%')"
. " AND accttrx.date<" . $DBH->quote($self->{"startdate"}) . ";\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$_ = $sth->fetchrow_hashref;
undef $sth;
if (defined $$_{"balance"}) {
push @{$self->{"current"}}, {
"subj" => acctsubj_title(acctsubj_sn(ACCTSUBJ_INCOME_ACUM)),
"balance" => -$$_{"balance"},
};
}
# Sort by the subject
$self->{"current"} = [ sort { $$a{"subj"} cmp $$b{"subj"} } @{$self->{"current"}} ];
# Set the debit and credit amount
if (@{$self->{"current"}} > 0) {
my ($sumdebit, $sumcredit, $viewurl);
($sumdebit, $sumcredit) = (0, 0);
@_ = qw();
push @_, "list=ldgr";
push @_, "subj=%s";
push @_, $self->{"actrange"};
$viewurl = $REQUEST_FILE . "?" . join "&", @_;
foreach (@{$self->{"current"}}) {
$$_{"_viewurl"} = sprintf $viewurl, $$_{"code"};
delete $$_{"code"};
if ($$_{"balance"} > 0) {
$$_{"debit"} = $$_{"balance"};
$$_{"credit"} = 0;
$sumdebit += $$_{"debit"};
} elsif ($$_{"balance"} < 0) {
$$_{"debit"} = 0;
$$_{"credit"} = -$$_{"balance"};
$sumcredit += $$_{"credit"};
} else {
$$_{"debit"} = 0;
$$_{"credit"} = 0;
}
delete $_{"balance"};
}
# Append the total record
if (@{$self->{"current"}} > 0) {
push @{$self->{"current"}}, {
"_viewurl" => undef,
"subj" => C_("Total"),
"debit" => $sumdebit,
"credit" => $sumcredit,
};
}
}
# The number of rows per page
$self->{"pagesize"} = $self->{"DEFAULT_LIST_SIZE"};
# Paging not in use
$self->{"total"} = scalar(@{$self->{"current"}});
$self->{"lastpage"} = 1;
$self->{"startno"} = 0;
$self->{"endno"} = $self->{"total"} - 1;
# If endno is -1 (when total is 0), set to 0
$self->{"endno"} = 0 if $self->{"endno"} < 0;
# Set the columns to be displayed
$self->{"cols"} = [qw(_viewurl subj debit credit)];
$self->{"listcols"} = [qw(subj debit credit)];
# Done
return $self->{"error"};
}
# html_data_download: Display the data download link
sub html_data_download : method {
local ($_, %_);
my ($self, $url, $title);
$self = $_[0];
# No data
return if $self->{"total"} == 0;
# Construct the URL
@_ = qw();
push @_, "list=tb";
push @_, $self->{"actrange"};
push @_, "format=csv";
$url = $REQUEST_FILE . "?" . join "&", @_;
$title = h_abbr(C_("Download the data as a CSV file."));
print << "EOT";
<p><a href="$url">$title</a></p>
EOT
return;
}
# html_csv: Return the data as CSV
sub html_csv : method {
local ($_, %_);
my $self;
$self = $_[0];
# Output a header to suggest for download instead of display
$HTTP_HEADERS{"Content-Disposition"} =
"attachment; filename=trial_balance.csv";
$CONTENT_TYPE = "text/csv; charset=" . getlang LN_CHARSET;
# The header column
@_ = map((exists ${$self->{"col_labels"}}{$_}? ${$self->{"col_labels"}}{$_}: $_),
@{$self->{"listcols"}});
s/"/""/g foreach @_; # "
print join(",", map "\"$_\"", @_) . "\n";
# The data
foreach my $current (@{$self->{"current"}}) {
@_ = map $$current{$_}, @{$self->{"listcols"}};
foreach (@_) {
$_ = "" if !defined $_;
s/"/""/g; # "
}
print join(",", map "\"$_\"", @_) . "\n";
}
return;
}
# html_listprefform: Display a form to change the list preference
# Make it a null function
sub html_listprefform : method {}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my ($self, $total);
$self = $_[0];
$total = $self->{"total"};
# Inherit the empty list statistics message
return $self->SUPER::liststat_message if $self->{"total"} == 0;
return C_("[*,_1,accounting subject].", $self->{"total"} - 1);
}
return 1;

View File

@@ -0,0 +1,95 @@
# Selima Website Content Management System
# Subjects.pm: The accounting subject list.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-08-23
package Selima::List::Accounting::Subjects;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "acctsubj" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
C_("Select an Accounting Subject"):
C_("Manage Accounting Subjects");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "code";
# Column labels
$self->col_labels(
"parent" => C_("Parent subject"),
"code" => C_("Code"),
);
return $self;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Add a new accounting subject."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for an accounting subject:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting subject].", $self->{"total"});
# List result
} else {
return C_("[*,_1,accounting subject].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting subject], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,accounting subject], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,45 @@
# Selima Website Content Management System
# LastLv.pm: The last-level accounting subject list.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-09-22
package Selima::List::Accounting::Subjects::LastLv;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Accounting::Subjects);
use Selima::DataVars qw(:l10n :lninfo);
use Selima::GetLang;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "acctsubj" if !defined $_[1];
$self = $class->SUPER::new(@_);
if (@ALL_LINGUAS == 1) {
$self->{"view"} = "acctsubj_lastlv_list";
} else {
$self->{"view"} = "acctsubj_lastlv_list_" . getlang LN_DATABASE;
}
return $self;
}
return 1;

View File

@@ -0,0 +1,116 @@
# Selima Website Content Management System
# Transacts.pm: The accounting transaction list.
# Copyright (c) 2007-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2007-08-23
package Selima::List::Accounting::Transacts;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::AddGet;
use Selima::DataVars qw(:requri);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "accttrx" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
C_("Select an Accounting Transaction"):
C_("Manage Accounting Transactions");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "-date,-ord";
# Column labels
$self->col_labels(
"num" => C_("Number"),
"note" => C_("Note"),
);
return $self;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
local ($_, %_);
my ($self, $urle, $urli, $urlt, $prompt);
$self = $_[0];
# No new item creation if it is a called form
return if $self->{"is_called_form"};
$_ = $REQUEST_FILEQS;
# Remove list parameters
$_ = rem_get_arg $_, "query", "sortby", "pageno", "form", "formcat", "formid", "statid";
$_ = add_get_arg $_, "form", "new", DUP_OK;
$urle = add_get_arg $_, "formsub", "expense", DUP_OK;
$urli = add_get_arg $_, "formsub", "income", DUP_OK;
$urlt = add_get_arg $_, "formsub", "trans", DUP_OK;
$prompt = C_("<a href=\"[_1]\">Add a new cash expense transaction</a>, <a href=\"[_2]\">add a new cash income transaction</a> or <a href=\"[_3]\">add a new transfer transaction</a>.",
h($urle), h($urli), h($urlt));
print << "EOT";
<p>$prompt</p>
EOT
return;
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for an accounting transaction:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting transaction].", $self->{"total"});
# List result
} else {
return C_("[*,_1,accounting transaction].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,accounting transaction], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,accounting transaction], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,256 @@
# Selima Website Content Management System
# ActLog.pm: The activity log record list.
# Copyright (c) 2005-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2005-05-10
package Selima::List::ActLog;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Encode qw(decode);
use Fcntl qw(:flock);
use Selima::A2HTML;
use Selima::DataVars qw(:lninfo :requri);
use Selima::GetLang;
use Selima::Logging;
use Selima::HTTP;
use Selima::Query;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = C_("Browse the Activity Log");
$self->{"rows"} = defined $self->{"FORM"}->param("rows")?
$self->{"FORM"}->param("rows"): undef;
$self->{"rdgt"} = 4;
$self->{"reverse"} = 1;
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $error, $FH);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
$self->{"total"} = undef;
# Check the query phrases
$self->check_query;
# Check the number of rows to display
$error = $self->check_pagesize;
if (defined $error) {
$self->{"error"} = $error if !defined $self->{"error"};
$self->{"pagesize"} = $self->{"DEFAULT_LIST_SIZE"};
} elsif (!defined $self->{"rows"}) {
$self->{"pagesize"} = $self->{"DEFAULT_LIST_SIZE"};
} else {
$self->{"pagesize"} = $self->{"rows"};
}
check_actlog_file;
open $FH, $ACTLOG or http_500 "$ACTLOG: $!";
flock $FH, LOCK_SH or http_500 "$ACTLOG: $!";
# Obtain all the log entries
if (@{$self->{"query_phrases"}} == 0) {
$self->{"current"} = [map decode("UTF-8", $_), <$FH>];
# Obtain all the matched log entries
} else {
$self->{"current"} = [];
while (defined($_ = <$FH>)) {
my $matched;
$_ = decode("UTF-8", $_);
$matched = 1;
foreach my $phrase (@{$self->{"query_phrases"}}) {
next if /\Q$phrase\E/i;
$matched = 0;
last;
}
push @{$self->{"current"}}, $_ if $matched;
}
}
flock $FH, LOCK_UN or http_500 "$ACTLOG: $!";
close $FH or http_500 "$ACTLOG: $!";
$self->{"total"} = scalar @{$self->{"current"}};
$self->{"endno"} = $self->{"total"} - 1;
$self->{"startno"} = 1;
if ($self->{"total"} > $self->{"pagesize"}) {
$self->{"startno"} = $self->{"endno"} - $self->{"pagesize"} + 1;
$self->{"current"} = [@{$self->{"current"}}[$self->{"startno"}...$self->{"endno"}]];
}
$self->{"current"} = [reverse @{$self->{"current"}}];
# Done
return $self->{"error"};
}
# check_query: Check the query phrases
sub check_query : method {
local ($_, %_);
my $self;
$self = $_[0];
# No query, return
return if !defined $self->{"query"};
# Regularize it
$self->{"query"} =~ s/^\s*(.*?)\s*$/$1/;
# Check if it is filled
return if $self->{"query"} eq "";
$self->{"query_phrases"} = [parse_query $self->{"query"}];
return;
}
# check_pagesize: Check the number of rows to display
sub check_pagesize : method {
local ($_, %_);
my ($self, $error, $errmsg);
$self = $_[0];
# No rows, return
return if !defined $self->{"rows"};
# Regularize it
$self->{"rows"} =~ s/^\s*(.*?)\s*$/$1/;
# Check if it is filled
return {"msg"=>N_("Please fill in the number of rows to display.")}
if $self->{"rows"} eq "";
# If there is any non-digit character
return {"msg"=>N_("Please fill in a positive integer number of rows to display.")}
unless $self->{"rows"} =~ /^[1-9][0-9]*$/;
# Set to an integer
$self->{"rows"} += 0;
# Check the length
return {"msg"=>N_("This number of rows to display is too long. (Max. length [#,_1])"),
"margs"=>[$self->{"rdgt"}]}
if length $self->{"rows"} > $self->{"rdgt"};
# OK
return;
}
# html_newlink: Display a link to add a new item
# Make it a null function
sub html_newlink : method {}
# html_search: Display the search box
sub html_search : method {
local ($_, %_);
my ($self, $prompt, $label, $query, $request_file);
my ($prompt2, $rows, $size);
($self, $prompt) = @_;
$prompt = C_("Search for log entries:") if !defined $prompt;
$prompt = h($prompt);
$request_file = h($REQUEST_FILE);
$query = defined $self->{"query"}? h($self->{"query"}): "";
$label = h(C_("Display"));
$prompt2 = h(C_("Display rows:"));
$rows = defined $self->{"rows"}? h($self->{"rows"}):
h($self->{"DEFAULT_LIST_SIZE"});
$size = h($self->{"rdgt"});
print << "EOT";
<form action="$request_file" method="get" accept-charset="<!--selima:charset-->">
<div class="searchbox">
<label for="query">$prompt</label><input
id="query" type="text" name="query" value="$query" />
<label for="rows">$prompt2</label><input
id="rows" type="text" name="rows" size="$size" maxlength="$size" value="$rows" />
<input type="hidden" name="charset" value="<!--selima:charset-->" />
<input type="submit" value="$label" />
</div>
</form>
EOT
return;
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,log entry,log entries].", $self->{"total"});
# List result
} else {
return C_("[*,_1,log entry,log entries].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,log entry,log entries], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,log entry,log entries], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
# html_pagebar: Display a page navigation bar
# Make it a null function
sub html_pagebar : method {}
# html_list: List the items
sub html_list : method {
local ($_, %_);
my $self;
$self = $_[0];
# Do not show the list
return if !defined $self->{"total"};
# No record to be listed
return if $self->{"total"} == 0;
$_ = a2html(join "", @{$self->{"current"}});
s/([\x00-\x08\x0B\x0C\x0E-\x1F])/sprintf("\\x%02x", ord($1));/ge;
print << "EOT";
<div>
$_
</div>
EOT
return;
}
return 1;

View File

@@ -0,0 +1,74 @@
# Selima Website Content Management System
# Category.pm: The base category list.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-03-21
package Selima::List::Category;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::ShortCut;
# html_newlink: Display a link to add a new item
sub html_newlink : method {
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Add a new category."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for a category:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,category,categories].", $self->{"total"});
# List result
} else {
return C_("[*,_1,category,categories].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,category,categories], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,category,categories], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,74 @@
# Selima Website Content Management System
# Categorz.pm: The base category membership list.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-03-21
package Selima::List::Categorz;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::ShortCut;
# html_newlink: Display a link to add a new item
sub html_newlink : method {
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Add a new categorization record."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for a categorization record:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,categorization record].", $self->{"total"});
# List result
} else {
return C_("[*,_1,categorization record].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,categorization record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,categorization record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,95 @@
# Selima Website Content Management System
# GroupMem.pm: The group-to-group membership list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-14
package Selima::List::GroupMem;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "groupmem" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
C_("Select a Group Membership Record"):
C_("Manage Group Membership");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "grp,member";
# Column labels
$self->col_labels(
"grp" => C_("Group"),
"member" => C_("Member"),
);
return $self;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Add a new membership record."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for a membership record:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,membership record].", $self->{"total"});
# List result
} else {
return C_("[*,_1,membership record].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,membership record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,membership record], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,94 @@
# Selima Website Content Management System
# Groups.pm: The account group list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-12
package Selima::List::Groups;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "groups" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}? C_("Select a Group"):
C_("Manage Groups");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "id";
# Column labels
$self->col_labels(
"id" => C_("Group ID."),
"dsc" => C_("Description"),
);
return $self;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Add a new group."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for a group:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,group].", $self->{"total"});
# List result
} else {
return C_("[*,_1,group].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,group], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,group], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,110 @@
# Selima Website Content Management System
# Guestbook.pm: The base administrative guestbook message list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-16
package Selima::List::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::AddGet;
use Selima::DataVars qw(:requri);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "guestbook" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
C_("Select a Message"):
C_("Manage Guestbook");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "created";
# Columns that should display its brief instead
push @{$self->{"COLS_BRIEF"}}, qw(message);
# Columns should be displayed in a reversed order
$self->{"reverse"} = 1;
# The list brief size
$self->{"DEFAULT_BRIEF_LEN"} = 20;
# Column labels
$self->col_labels(
"name" => C_("Signature"),
"identity" => C_("Identity"),
"location" => C_("Location"),
"message" => C_("Message"),
"ip" => C_("IP"),
"host" => C_("Host"),
"ct" => C_("Country"),
"pageno" => C_("Page No."),
"oldpageno" => C_("Old page No."),
);
return $self;
}
# html_newlink: Display a link to add a new item
sub html_newlink : method {
# Run the parent method
return $_[0]->SUPER::html_newlink(C_("Write a new message."));
}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(C_("Search for a message:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,message].", $self->{"total"});
# List result
} else {
return C_("[*,_1,message].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return C_("Your query found [*,_1,message], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return C_("[*,_1,message], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,226 @@
# Selima Website Content Management System
# Public.pm: The base guestbook message list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-23
package Selima::List::Guestbook::Public;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use IO::NestedCapture qw(CAPTURE_STDOUT);
use Selima::A2HTML;
use Selima::DataVars qw($DBH :requri);
use Selima::Format;
use Selima::GetLang;
use Selima::MungAddr;
use Selima::ShortCut;
use Selima::Unicode;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "guestbook" if !defined $_[1];
$self = $class->SUPER::new(@_);
$self->{"view"} = "guestbook_public";
# Entries should be displayed in a reversed order
$self->{"reverse"} = 1;
# Magical Traditional/Simplified Chinese conversion
$self->{"magic_zhconv"} = 0;
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my ($self, $table, $sth, $sql, $error);
$self = $_[0];
# Fetched before
return $self->{"error"} if $self->{"fetched"};
$self->{"fetched"} = 1;
# Initialize the error status
$self->{"error"} = undef;
# The view name
$table = $DBH->quote_identifier($self->{"view"});
# Find the last page number
$sql = "SELECT pageno FROM $table ORDER BY pageno DESC LIMIT 1;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
# No records yet
if ($sth->rows != 1) {
$self->{"lastpage"} = 1;
} else {
$self->{"lastpage"} = ${$sth->fetch}[0];
}
# Check the page number
$error = $self->check_pageno;
$self->{"error"} = $error if defined $error && !defined $self->{"error"};
# Obtain the total number
$self->{"select_total"} = sprintf "SELECT count(*) FROM $table;\n";
$sql = $self->{"select_total"};
$sth = $DBH->prepare($sql);
$sth->execute;
$self->{"total"} = ($sth->fetchrow_array)[0];
# Obtain everything in this page
$self->{"current"} = [];
# Always reverse
$self->{"select"} = "SELECT * FROM $table"
. " WHERE pageno=" . $self->{"pageno"} . ";\n";
$sql = $self->{"select"};
$sth = $DBH->prepare($sql);
$sth->execute;
push @{$self->{"current"}}, $_
while defined($_ = $sth->fetchrow_hashref);
undef $sth;
# Done
return $self->{"error"};
}
# page_param: Obtain page parameters
sub page_param : method {
local ($_, %_);
my ($self, $args);
$self = $_[0];
# Run the parent method
$args = $self->SUPER::page_param;
# Add the page bar to the page parameters
if (defined $args && $self->{"lastpage"} > 1) {
my $FD;
# Obtain the page bar
IO::NestedCapture->start(CAPTURE_STDOUT);
binmode IO::NestedCapture->instance->{"STDOUT_current"}[-1], ":utf8";
$self->html_pagebar;
IO::NestedCapture->stop(CAPTURE_STDOUT);
$FD = IO::NestedCapture->get_last_out;
$$args{"header_html_nav"} = join "", <$FD>;
$$args{"header_html_nav"} =~ s/\s+$//;
$$args{"footer_html_nav"} = $$args{"header_html_nav"};
}
return $args;
}
# html: Output the list
sub html : method {
local ($_, %_);
my $self;
$self = $_[0];
# Fetch the current list if not fetched yet
$self->fetch if !$self->{"fetched"};
# Display the error message
$self->html_errmsg;
# List the items
$self->html_list;
return;
}
# html_list: List the items
sub html_list : method {
local ($_, %_);
my ($self, @htmls, $emailalt);
$self = $_[0];
# No record to be listed
return if $self->{"total"} == 0;
$emailalt = h(C_("E-mail"));
foreach my $current (@{$self->{"current"}}) {
my $h;
# Magical Traditional/Simplified Chinese conversion
if ($self->{"magic_zhconv"}) {
$_ = getlang;
if ( $_ eq "zh-tw" &&
!(defined $$current{"lang"}
&& $$current{"lang"} eq "zh-tw")) {
foreach my $col (qw(name identity location email url message)) {
$$current{$col} = all_to_trad($$current{$col})
if defined $$current{$col};
}
} elsif ( $_ eq "zh-cn" &&
!(defined $$current{"lang"}
&& $$current{"lang"} eq "zh-cn")) {
foreach my $col (qw(name identity location email url message)) {
$$current{$col} = all_to_simp($$current{$col})
if defined $$current{$col};
}
}
}
$h = "";
$h .= "<div id=\"msg" . h($$current{"sn"}) . "\" class=\"entry\">\n";
$h .= "<div>\n" . a2html($$current{"message"}) . "\n</div>\n\n";
# <form ...>...</form> cannot live inside of <address>...</address>
$h .= "<form action=\"/cgi-bin/mailto.cgi\" method=\"post\">\n"
if defined $$current{"email"} && $$current{"email"} =~ /\@/;
$h .= "<address>\n";
$h .= "<cite>" . h($$current{"name"}) . "</cite><br />\n"
if defined $$current{"name"};
if (getlang eq "en") {
$h .= myfmttime($$current{"date"}) . "<br />\n";
} else {
$h .= "<span xml:lang=\"en\">" . myfmttime($$current{"date"}) . "</span><br />\n";
}
$h .= h($$current{"identity"}) . "<br />\n"
if defined $$current{"identity"};
$h .= h($$current{"location"}) . "<br />\n"
if defined $$current{"location"};
if (defined $$current{"email"}) {
if ($$current{"email"} =~ /\@/) {
$h .= "<samp>" . mung_email_span(h($$current{"email"})) . "</samp>"
. "<input\n type=\"hidden\" name=\"email\" value=\""
. h(mung_address_at($$current{"email"})) . "\" />"
. "<input\n type=\"image\" src=\"/images/email\" alt=\"$emailalt\" /><br />\n";
} else {
$h .= "<samp>" . mung_email_span(h($$current{"email"})) . "</samp><br />\n";
}
}
if (defined $$current{"url"}) {
if ($$current{"url"} =~ /^(?:http|https|ftp|gopher|telnet):\/\//) {
$h .= "<samp><a href=\"" . h($$current{"url"}) . "\">"
. h($$current{"url"}) . "</a></samp><br />\n";
} else {
$h .= h($$current{"url"}) . "<br />\n";
}
}
$h .= C_("~[<a href=\"[_1]\">Edit</a>~]",
h("/magicat/cgi-bin/guestbook.cgi?form=cur&sn=" . $$current{"sn"})) . "\n"
if $ENV{"REMOTE_ADDR"} =~ /^10\./;
$h .= "</address>\n";
$h .= "</form>\n" if defined $$current{"email"} && $$current{"email"} =~ /\@/;
$h .= "</div>\n\n";
push @htmls, $h;
}
$_ = h(C_("The message entry seperator"));
print "<hr />\n\n<div class=\"entries\">\n\n"
. join("<hr title=\"$_\" />\n\n", @htmls) . "</div>\n\n";
return;
}
return 1;

View File

@@ -0,0 +1,48 @@
# Selima Website Content Management System
# LinkCat.pm: The related-link category list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::List::LinkCat;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Category);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "linkcat" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
C_("Select a Link Category"):
C_("Manage Link Categories");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "ord,id";
# Column labels
$self->col_labels(
);
return $self;
}
return 1;

Some files were not shown because too many files have changed in this diff Show More