VMXTemplate/VMX/Common.pm

370 lines
11 KiB
Perl
Raw Normal View History

#!/usr/bin/perl
2008-09-06 02:38:55 +04:00
# Некоторые простые полезные функции
package VMX::Common;
2008-09-23 20:14:01 +04:00
use strict;
2008-10-13 02:49:29 +04:00
use utf8;
2008-02-21 23:56:43 +03:00
use Encode;
2008-10-13 02:49:29 +04:00
use DBI;
2007-05-29 19:40:59 +04:00
use Digest::MD5;
2008-10-17 02:44:03 +04:00
use Date::Parse;
2008-10-13 02:49:29 +04:00
require Exporter;
2008-09-23 20:14:01 +04:00
our @EXPORT_OK = qw(
quotequote min max trim htmlspecialchars strip_tags strip_unsafe_tags
file_get_contents dbi_hacks ar1el filemd5 mysql_quote updaterow_hashref
2008-10-19 01:00:17 +04:00
insertall_hashref dumper_no_lf str2time
2008-09-23 20:14:01 +04:00
);
our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
2008-02-13 04:18:50 +03:00
2008-09-23 20:14:01 +04:00
our $allowed_html = [qw/
div span a b i u p h\d+ strike strong small big blink center ol pre sub
sup font br table tr td th tbody tfoot thead tt ul li em img marquee
/];
2008-10-13 02:49:29 +04:00
# Exporter-ский импорт + подмена функции в DBI
2008-09-06 02:38:55 +04:00
sub import
{
foreach (@_)
{
if ($_ eq '!dbi_hacks')
{
return Exporter::import(@_);
2008-09-06 02:38:55 +04:00
}
elsif ($_ eq 'dbi_hacks')
{
2008-02-13 04:18:50 +03:00
$_ = '!dbi_hacks';
}
}
*DBI::_::st::fetchall_hashref = *VMX::Common::fetchall_hashref;
*DBI::st::fetchall_hashref = *VMX::Common::fetchall_hashref;
$DBI::DBI_methods{st}{fetchall_hashref} = { U =>[1,2,'[ $key_field ]'] };
2007-09-02 15:07:58 +04:00
$DBI::DBI_methods{db}{selectall_hashref} = { U =>[2,0,'$statement [, $keyfield [, \%attr [, @bind_params ] ] ]'], O=>0x2000 };
$Exporter::ExportLevel = 1;
my $r = Exporter::import(@_);
$Exporter::ExportLevel = 0;
return $r;
}
2008-10-13 02:49:29 +04:00
# Функция возвращает минимальное из значений
# $r = min (@list)
2008-09-06 02:38:55 +04:00
sub min
{
return undef if (@_ < 1);
my $r = shift;
foreach (@_) { $r = $_ if $r > $_; }
return $r;
}
2008-10-13 02:49:29 +04:00
# Функция возвращает максимальное из значений
# $r = max (@list)
2008-09-06 02:38:55 +04:00
sub max
{
2007-05-30 19:40:07 +04:00
return undef if (@_ < 1);
my $r = shift;
foreach (@_) { $r = $_ if $r < $_; }
return $r;
}
2008-10-13 02:49:29 +04:00
# ar1el($a) - аналог ($a || [])->[0], только ещё проверяет, что $a есть arrayref
2008-09-06 02:38:55 +04:00
sub ar1el
{
return undef unless 'ARRAY' eq ref $_[0];
return shift @{$_[0]};
2007-05-25 03:13:23 +04:00
}
2008-10-13 02:49:29 +04:00
# Функция обрезает пробельные символы в начале и конце строки
# trim ($r) in-place
2008-09-06 02:38:55 +04:00
sub trim
{
2008-09-07 01:18:12 +04:00
$_ = $_[0];
s/^\s+//so;
s/\s+$//so;
$_;
}
2008-10-13 02:49:29 +04:00
# аналог HTML::Entities::encode_entities
# $str = htmlspecialchars ($str)
2008-09-06 02:38:55 +04:00
sub htmlspecialchars
{
2008-10-18 17:56:52 +04:00
local $_ = shift;
2008-09-06 02:38:55 +04:00
s/&/&apos;/gso;
s/</&lt;/gso;
s/>/&gt;/gso;
s/\"/&quot;/gso;
s/\'/&apos;/gso;
return $_;
}
2008-10-13 02:49:29 +04:00
# удаление тегов из строки, кроме заданных
# $str = strip_tags ($str)
2008-09-06 02:38:55 +04:00
sub strip_tags
{
$_ = shift;
2008-09-23 20:14:01 +04:00
my $ex = join '|', @{(shift)};
s/<\/?(?!\/?($ex))([a-z0-9_\-]+)[^<>]*>//gis;
return $_;
}
2008-10-13 02:49:29 +04:00
# удаление небезопасных HTML тегов (всех кроме our $allowed_html)
2008-09-06 02:38:55 +04:00
sub strip_unsafe_tags
{
2008-09-23 20:14:01 +04:00
strip_tags($_[0], $allowed_html);
2008-09-06 02:38:55 +04:00
}
2008-10-13 02:49:29 +04:00
# аналог File::Slurp
# $contents = file_get_contents ($filename)
2008-09-06 02:38:55 +04:00
sub file_get_contents
{
my ($tmp, $res);
open ($tmp, '<'.$_[0]);
2008-09-06 02:38:55 +04:00
if ($tmp)
{
2007-05-25 22:14:24 +04:00
local $/ = undef;
$res = <$tmp>;
close ($tmp);
}
return $res;
}
2008-10-13 02:49:29 +04:00
# изменённый вариант функции DBI::_::st::fetchall_hashref
# первая вещь - аналог fetchall_arrayref({Slice=>{}}), т.е. просто возвращает
# массив хешей при передаче в качестве $key_field ссылки на пустой массив или undef.
# вторая вещь - о которой все мы, пользователи MySQL, давно мечтали - возможность
# сделать SELECT t1.*, t2.*, t3.* и при этом успешно разделить поля таблиц,
# распределив их по отдельным хешам.
# весь смысл в том, что при передаче в качестве $key_field хеша делает из каждой
# строчки вложенный hashref, а колонки из результата запроса разделяет по
# $key_field->{Separator} или '_' по умолчанию.
# то есть например $dbh->selectall_hashref(
# "SELECT t1.*, 0 AS `_`, t2.* FROM t1 JOIN t2 USING (join_field)",
# { Separator => '_', Names => [ 't1', 't2' ] }, {}
# ) вернёт ссылку на массив хешрефов вида { t1 => { ... }, t2 => { ... } },
# т.е. поля t1 и t2 будут разделены по подхешам даже в случае, если в t1 и t2
# существуют поля с одинаковыми именами
# кроме того, кэширует все свои вспомогательные массивы в объекте запроса
# для дополнительной оптимальности
2008-09-06 02:38:55 +04:00
sub fetchall_hashref
{
my ($sth, $key_field) = @_;
return multifetchall_hashref($sth, $key_field) if ref($key_field) eq 'HASH';
my $hash_key_name = $sth->{FetchHashKeyName} || 'NAME';
my $names_hash = $sth->FETCH("${hash_key_name}_hash");
my @key_fields = (ref $key_field) ? @$key_field : $key_field ? ($key_field) : ();
my $cachename = "__cache_key_fields_".join "_", @key_fields;
my $key_indexes = $sth->{$cachename};
my $num_of_fields = $sth->FETCH('NUM_OF_FIELDS');
unless ($key_indexes)
2008-09-06 02:38:55 +04:00
{
$key_indexes = [];
foreach (@key_fields)
{
my $index = $names_hash->{$_}; # perl index not column
$index = $_ - 1 if !defined $index && DBI::looks_like_number($_) && $_ >= 1 && $_ <= $num_of_fields;
return $sth->set_err(1, "Field '$_' does not exist (not one of @{[keys %$names_hash]})")
unless defined $index;
push @$key_indexes, $index;
}
$sth->{$cachename} = $key_indexes;
}
my $rows = {};
$rows = [] unless scalar @key_fields;
my $NAME = $sth->FETCH($hash_key_name);
my @row = (undef) x $num_of_fields;
$sth->bind_columns(\(@row)) if @row;
my $ref;
if (scalar @key_fields)
{
while ($sth->fetch)
{
$ref = $rows;
$ref = $ref->{$row[$_]} ||= {} for @$key_indexes;
@$ref{@$NAME} = @row;
}
}
else
{
while ($sth->fetch)
{
push @$rows, $ref = {};
@$ref{@$NAME} = @row;
}
}
return $rows;
}
# вот здесь-то и реализовано вертикальное разбиение результата
sub multifetchall_hashref
{
my ($sth, $key_field) = @_;
$key_field = [] unless ref($key_field->{Multi}) eq 'ARRAY';
return fetchall_hashref($sth, $key_field) if ref($key_field) ne 'HASH';
my $NAME = $sth->FETCH($sth->{FetchHashKeyName} || 'NAME');
my $num_of_fields = $sth->FETCH('NUM_OF_FIELDS');
my $cachename = "__cache_multi_key_fields";
my ($nh, $ni, $i, $hs);
unless ($sth->{$cachename})
2008-09-06 02:38:55 +04:00
{
# массивы индексов и имён ещё не построены, построим
my $split = $key_field->{Separator} || '_';
$nh = [[]];
$ni = [[]];
$i = 0;
for my $k (0..$#$NAME)
2008-09-06 02:38:55 +04:00
{
if ($NAME->[$k] eq $split)
{
$i++;
$nh->[$i] = [];
$ni->[$i] = [];
}
else
{
push @{$nh->[$i]}, $NAME->[$k];
push @{$ni->[$i]}, $k;
}
2008-09-06 02:38:55 +04:00
}
$sth->{$cachename} = [ $nh, $ni ];
}
else
{
($nh, $ni) = @{$sth->{$cachename}};
}
my $rows = [];
my @row = (undef) x $num_of_fields;
$sth->bind_columns(\(@row)) if @row;
$hs = $key_field->{Multi};
my $ref;
while ($sth->fetch)
{
push @$rows, $ref = {};
for $i (0..$#$hs)
2008-09-06 02:38:55 +04:00
{
$ref->{$hs->[$i]} = {};
@{$ref->{$hs->[$i]}}{@{$nh->[$i]}} = @row[@{$ni->[$i]}];
}
}
return $rows;
}
2008-10-13 02:49:29 +04:00
# Обновить все строки, у которых значения полей с названиями ключей %$key
# равны значениям %$key, установив в них поля с названиями ключей %$row
# значениям %$row
2008-09-06 02:38:55 +04:00
sub updaterow_hashref
{
2008-02-13 04:18:50 +03:00
my ($dbh, $table, $row, $key) = @_;
return 0 unless
2008-09-23 20:14:01 +04:00
$dbh && $table &&
2008-02-13 04:18:50 +03:00
$row && ref($row) eq 'HASH' && %$row &&
$key && ref($key) eq 'HASH' && %$key;
my @f = keys %$row;
my @k = keys %$key;
my $sql =
2008-09-23 20:14:01 +04:00
'UPDATE `'.$table.'` SET '.
2008-02-13 04:18:50 +03:00
join(', ', map { "`$_`=?" } @f).
2008-03-12 03:52:05 +03:00
' WHERE '.join(' AND ', map { "`$_`=?" } @k);
2008-02-13 04:18:50 +03:00
my @bind = (@$row{@f}, @$key{@k});
return $dbh->do($sql, {}, @bind);
}
2008-10-13 02:49:29 +04:00
# Вставить набор записей $rows = [{},{},{},...] в таблицу $table
# Возможно после этого дополнить каждую запись $reselect полями (напр. '*'),
# сделав дополнительный запрос выборки. Для этого требуются ещё поля
# `ji` INT DEFAULT NULL и `jin` INT DEFAULT NULL, и индекс по ним.
2008-08-15 21:31:24 +04:00
sub insertall_hashref
{
2008-09-05 20:46:22 +04:00
my ($dbh, $table, $rows, $reselect, $replace) = @_;
2008-02-13 04:18:50 +03:00
return 0 unless
2008-09-23 20:14:01 +04:00
$dbh && $table &&
2008-02-13 04:18:50 +03:00
$rows && ref($rows) eq 'ARRAY' && @$rows;
2008-09-02 17:30:08 +04:00
my $conn_id = undef;
2008-08-15 21:31:24 +04:00
if ($reselect)
{
2008-02-13 04:18:50 +03:00
my $i = 0;
2008-09-02 17:30:08 +04:00
$conn_id = $dbh->{mysql_connection_id};
@$_{'ji','jin'} = ($conn_id, ++$i) foreach @$rows;
2008-02-13 04:18:50 +03:00
}
my @f = keys %{$rows->[0]};
2008-10-19 13:59:15 +04:00
my $sql = ($replace ? 'REPLACE' : 'INSERT').
2008-09-23 20:14:01 +04:00
' INTO `'.$table.'` (`'.join('`,`',@f).'`) VALUES '.
2008-02-13 04:18:50 +03:00
join(',',('('.(join(',', ('?') x scalar(@f))).')') x scalar(@$rows));
my @bind = map { @$_{@f} } @$rows;
my $st = $dbh->do($sql, {}, @bind);
return $st if !$st || !$reselect;
2008-08-15 21:31:24 +04:00
if (ref($reselect) eq 'ARRAY')
{
2008-02-13 04:18:50 +03:00
$reselect = '`'.join('`,`',@$reselect).'`';
2008-08-15 21:31:24 +04:00
}
elsif ($reselect ne '*')
{
2008-02-13 04:18:50 +03:00
$reselect = "`$reselect`";
}
2008-02-14 03:39:17 +03:00
# осуществляем reselect данных
2008-09-23 20:14:01 +04:00
$sql = "SELECT $reselect FROM `$table` WHERE `ji`=? ORDER BY `jin` ASC";
2008-09-02 17:30:08 +04:00
@bind = ($conn_id);
2008-02-14 03:39:17 +03:00
my $resel = $dbh->selectall_hashref($sql, [], {}, @bind);
2008-08-15 21:31:24 +04:00
for (my $i = 0; $i < @$resel; $i++)
{
2008-02-14 03:39:17 +03:00
$rows->[$i]->{$_} = $resel->[$i]->{$_} for keys %{$resel->[$i]};
}
2008-09-23 20:14:01 +04:00
$sql = "UPDATE `$table` SET `ji`=NULL, `jin`=NULL WHERE `ji`=?";
2008-02-14 03:39:17 +03:00
$dbh->do($sql, {}, @bind);
return $st;
2008-02-13 04:18:50 +03:00
}
2008-10-13 02:49:29 +04:00
# вычисление MD5 хеша от файла
2008-08-15 21:31:24 +04:00
sub filemd5
{
2007-05-29 19:40:59 +04:00
my ($file) = @_;
my $f;
my $r;
2008-08-15 21:31:24 +04:00
if (open $f, "<$file")
{
2007-05-29 19:40:59 +04:00
my $ctx = Digest::MD5->new;
$ctx->addfile($f);
$r = $ctx->hexdigest;
close $f;
}
return $r;
}
2008-10-13 02:49:29 +04:00
# тоже <ни фига не нужный велосипед>, экранирование символов для MySQL,
# да ещё и несколько кривое
2008-08-15 21:31:24 +04:00
sub mysql_quote
{
2007-06-07 03:09:26 +04:00
my ($a) = @_;
$a =~ s/\'/\'\'/gso;
2008-02-13 04:18:50 +03:00
$a =~ s/\\/\\\\/gso;
2007-06-07 03:09:26 +04:00
return "'$a'";
}
2008-10-13 02:49:29 +04:00
# экранирование кавычек
2008-08-15 21:31:24 +04:00
sub quotequote
{
my ($a) = @_;
$a =~ s/\'|\"/\\$&/gso;
return $a;
}
2008-10-13 02:49:29 +04:00
# Dumper без переводов строки
2008-08-15 21:31:24 +04:00
sub dumper_no_lf
{
2008-03-13 17:04:57 +03:00
my $r = Data::Dumper::Dumper (@_);
$r =~ s/\s+/ /giso;
return $r;
}
2008-10-17 02:44:03 +04:00
# str2time, принимающий формат даты вида DD.MM.YYYY
sub str2time
{
my ($str) = @_;
$str =~ s/(\d{2})\.(\d{2})\.(\d{4})/$2\/$1\/$3/gso;
return Date::Parse::str2time($str);
}
1;
2008-09-06 02:38:55 +04:00
__END__