package Contenido::Project; # ---------------------------------------------------------------------------- # Contenido::Project - класс, который будет содержать перечень полей с # общей информацией о проекте (ширина, основные цвета, общее название # для title's и так далее). Объект этого класса будет создаваться при # старте Apache-сервера. Данные он будет брать из специального хэша # (который возможно будет редактироваться через редакторский интерфейс) # и перечитывать их каждый раз при измении файла на диске. # ---------------------------------------------------------------------------- use strict; use warnings; use locale; use vars qw($VERSION $AUTOLOAD); $VERSION = '1.0'; use Utils; use Contenido::Globals; use Data::Dumper; # ---------------------------------------------------------------------------- # Конструктор. Создает объект с описанием проекта. # # Формат вызова: # Contenido::Project->new(); # ---------------------------------------------------------------------------- sub new { my ($proto, $state, $local_keeper) = @_; unless ( ref $state ) { $log->error("Неправильный вызов конструктора объекта проекта. В параметрах нет объекта класса Contenido::State."); die; } $local_keeper ||= $keeper; my $class = ref($proto) || $proto; my $self = {}; bless($self, $class); $self->_init_; # Запомним то, что нас интересует... $self->{state} = $state; $self->{project_name} = $state->project_name(); $self->{debug} = $state->debug(); # $self->{mtime} = 0; $self->{data} = {}; $self->restore($local_keeper); return $self; } # ------------------------------------------------------------------------------------------------ # Инициализация внутренних структур # ------------------------------------------------------------------------------------------------ sub _init_ { my $self = shift; $self->{attributes} = {}; foreach my $a ( qw(project_name mtime debug) ) { $self->{attributes}->{ $a } = 'SCALAR'; } return 1; } # ------------------------------------------------------------------------------------------------ # Метод restore() для заполнения объекта Project данными... # # Формат использования: # $project->restore() # ------------------------------------------------------------------------------------------------ sub restore { my $self = shift; my $local_keeper = shift; my $force = shift; do { $log->error("Метод restore() можно вызывать только у объектов, но не классов"); die } unless ref $self; do { $log->error("Метод restore() require established connection to DB"); die } unless (ref($local_keeper)); return 1 if (!$force and $self->{"mtime"} and $self->{"mtime"} > time - $state->{"options_expire"}); #TODO: implement hardcoded project attributes if db_type eq 'none' return 1 if $self->{state}->db_type eq 'none'; delete $self->{"data"}; delete $self->{"attributes"}; my $sth = $local_keeper->SQL->prepare_cached("SELECT id, pid, name, value, type FROM options", {}, 1) || do { $log->error("Not connected to DB"); die }; $sth->execute(); my $data_ref = $sth->fetchall_arrayref() || []; $sth->finish(); foreach (@$data_ref) { $_->[1] = 0 unless ($_->[1]); } $self->{data} = restore_recursive('HASH',$data_ref, 0); foreach my $key (keys %{$self->{"data"}}) { $self->{"attributes"}{$key} = "DATA"; } $self->{"mtime"} = time; return 1; } sub restore_recursive { my $type = shift; my $data_ref = shift; my $pid = shift; my $struct_ref; if ($type eq 'ARRAY') { $struct_ref = []; foreach my $item (@$data_ref) { my ($id, $local_pid, $local_name, $local_value, $local_type) = @$item; next unless ($local_pid == $pid); if ($local_type eq 'SCALAR') { $struct_ref->[$local_name] = $local_value; } else { $struct_ref->[$local_name] = restore_recursive($local_type, $data_ref, $id); } } } elsif ($type eq 'HASH') { $struct_ref = {}; foreach my $item (@$data_ref) { my ($id, $local_pid, $local_name, $local_value, $local_type) = @$item; next unless ($local_pid == $pid); if ($local_type eq 'SCALAR') { $struct_ref->{$local_name} = $local_value; } else { $struct_ref->{$local_name} = restore_recursive($local_type, $data_ref, $id); } } } elsif ($type eq 'SCALAR') { do { $log->error("Recursive call with scalar value... "); die }; } else { do { $log->error("Wrong type in call '$type'"); die }; } return $struct_ref; } # ---------------------------------------------------------------------------- # Метод store() для сохранения объекта Project на диск... то есть в БД # # Формат использования: # $project->store() # ---------------------------------------------------------------------------- sub store { my $self = shift; my $local_keeper = shift; do { $log->error("Метод store() можно вызывать только у объектов, но не классов"); die } unless ref($self); do { $log->error("Метод store() require established connection to DB"); die } unless (ref($keeper) and $keeper->is_connected()); return 1 if $self->{state}->db_type eq 'none'; $local_keeper->t_connect() || do { $local_keeper->error(); return undef; }; my $insert = $keeper->TSQL->prepare("INSERT INTO options (pid, name, value, type) VALUES (?, ?, ?, ?)"); $local_keeper->TSQL->do("DELETE FROM options"); store_recursive(undef, undef, $self->{"data"}, $insert, $local_keeper); $insert->finish(); $local_keeper->t_finish(); undef $self->{"mtime"}; return 1; } # TODO переделать нафиг ужос sub store_recursive { my $pid = shift; my $key = shift; my $data = shift; my $insert = shift; my $local_keeper = shift; my $new_pid; if (ref $data eq "HASH") { if (defined $key) { $insert->execute($pid, $key, undef, "HASH"); $new_pid = $local_keeper->TSQL->selectrow_array("SELECT currval('documents_id_seq')"); } foreach my $new_key (keys %$data) { store_recursive($new_pid, $new_key, $data->{$new_key}, $insert, $local_keeper); } } elsif (ref $data eq "ARRAY") { if (defined $key) { $insert->execute($pid, $key, undef, "ARRAY"); $new_pid = $local_keeper->TSQL->selectrow_array("SELECT currval('documents_id_seq')"); } foreach my $new_key (0 .. $#$data) { store_recursive($new_pid, $new_key, $data->[$new_key], $insert, $local_keeper); } } else { $insert->execute($pid, $key, $data, "SCALAR") || return $local_keeper->t_abort(); } } # ---------------------------------------------------------------------------- # Это умный AUTOLOAD. Ловит методов для установки/чтения полей... # Версия 0.2/Модифицированная версия # ---------------------------------------------------------------------------- sub AUTOLOAD { my $self = shift; my $attribute = $AUTOLOAD; $attribute =~ s/.*:://; return undef unless $attribute =~ /[^A-Z]/; # Отключаем методы типа DESTROY if (! exists($self->{attributes}->{$attribute})) { $log->error("Вызов метода, для которого не существует обрабатываемого свойства: ->$attribute(). Создаем запись."); $self->{attributes}->{$attribute} = 'DATA'; } if ($self->{attributes}->{$attribute} eq 'DATA') { $self->{data}->{ $attribute } = shift @_ if (scalar(@_) > 0); return $self->{data}->{ $attribute }; } else { $self->{ $attribute } = shift @_ if (scalar(@_) > 0); return $self->{ $attribute }; } } 1;