view Lib/IMPL/Web/View/TTFactory.pm @ 340:c090d9102a38

web application security refactoring
author cin
date Fri, 21 Jun 2013 02:43:56 +0400
parents 97628101b765
children 9bdccdf1f50b
line wrap: on
line source

package IMPL::Web::View::TTFactory;
use strict;

use Template::Context();

use Carp qw(carp);
use IMPL::lang qw(:hash is);
use IMPL::Exception();
use Scalar::Util qw(weaken);
use IMPL::Resources::Format qw(FormatMessage);
use IMPL::Resources::Strings();


use IMPL::Const qw(:prop);
use IMPL::declare {
    require => {
        Loader => 'IMPL::Code::Loader',
        Resources => 'IMPL::Resources', 
        OpException => '-IMPL::InvalidOperationException',
        ArgException => '-IMPL::InvalidArgumentException'
    },
    base => [
        'IMPL::Object::Factory' => sub {
            shift;
        }
    ],
    props => [
        template => PROP_RW,
        activation => PROP_RW,
        context => PROP_RW,
        baseLocation => PROP_RW,
        base => PROP_RW,
        registry => PROP_RO, 
        blocks => PROP_RO,
        path => PROP_RO,
        _instance => PROP_RW,
        _labels => PROP_RW
    ]
};

sub CTOR {
    my ($this,$class,$template,$context,$path,$registry) = @_;
    
    die ArgException->new("A control class must be specified")
        unless $class;
    die ArgException->new("A template is required") unless $template;
    
    Loader->safe->Require($class)
        unless ref $class ;
    
    $context ||= new Template::Context();
    my $baseLocation = join( '/', splice( @{[split(/\//,$path)]}, 0, -1 ) );
    
    $this->activation($template->activation || 'new');
    $this->template($template);
    $this->context($context);
    $this->baseLocation($baseLocation);
    $this->path($path);
    $this->registry($registry);
    
    if (my $baseTplName = $template->extends) {
        $baseTplName =~ s{^\./}{$baseLocation/};
        
        my $base = $registry->Require($baseTplName)
            or die OpException->new("The specified base template isn't found");
            
        $this->base($base);
            
        $this->blocks(hashMerge($base->blocks, $template->blocks));

        # блок BASE должен меняться в процессе выполнения, в зависимости от
        # шаблона, который рендерится, по мере перехода в BASE 
        
        my @baseStack;
        
        $this->blocks->{BASE} = sub {
            my $ctx = shift;
            
            die OpException->new("This tamplate doesn't have a base template")
                unless $base;
            
            push @baseStack, $base;            
            my $block = $base->template->block;
            
            $base = $base->base;
            
            my $result = eval {
                $ctx->process($block);
            };
            my $e = $@;
            
            $base = pop @baseStack;
            
            die $e if $e;
            return $result;
        };
    } else {
        $this->blocks( $template->blocks );
    }
    
    if(my $blocks = $this->blocks) {
        while (my ($name,$block) = each %$blocks) {
            $context->define_block($name,$block);
        }
    }
    
    $context->stash->update({
        require => sub {
            my ($module) = @_;
                
            $module =~ s/^\.\//$baseLocation\//;
            return $registry->Require($module);
        }
    });
    $this->LoadLabelsToContext($context);
}

#TODO optimize labels loading for extended classes
sub LoadLabelsToContext {
	my ($this,$context) = @_;
	
	my %vars;
	
	%vars = %{$this->base->_labels || {}}
	   if $this->base;
	$this->_labels(\%vars);
	
	if(my $fname = $this->registry->loader->ResolveFileName($this->path)) {
        my $flabels = "$fname.labels";
        
        if (-f $flabels) {
            
            my %labels;
            $labels{default} = IMPL::Resources::Strings::ParseStringsMap("$flabels");
            
            while(my($label,$text) = each %{$labels{default}}) {
                $vars{$label} = sub {
                    my ($params) = @_;
                    my $locale = Resources->currentLocale;
                    
                    unless ($labels{$locale}) {
                        $labels{$locale} = -f "$fname.$locale" ? 
                          IMPL::Resources::Strings::ParseStringsMap("$fname.$locale") :
                          {};
                    }
                    
                    return FormatMessage(($labels{$locale}{$label} || $text),$params);
                }
            }
            
        }
    }
    $context->stash->update(\%vars);
}

sub MergeParameters {
    my $this = shift;
    my $refProps = shift || {};
    
    unless (ref($refProps) eq 'HASH') {
        carp "Passing control name through the first parameter is deprecated";
        my $name = $refProps;
        $refProps = shift;
        $refProps->{name} ||= $name;
    }
    
    $refProps->{factory} = $this;
    my $ctx = $this->CloneContext();
        
    return ($this->template, $ctx, $refProps);
}

sub CreateObject {
    my $this = shift;
    
    $this->activation eq 'singleton' ?
        $this->_instance || $this->_instance($this->next::method(@_)) :
        $this->next::method(@_);
}

sub CloneContext {
    my ($this) = @_;
    
    $this->context->localise();
    
    my $args = { %{$this->context} };
    delete $args->{CONFIG};
    
    $this->context->delocalise();
    
    return Template::Context->new($args);
}

sub Render {
    my ($this, $args) = @_;
    
    return $this->new()->Render($args);
}

sub save {
    die new IMPL::NotImplementedException("This class doesn't support serialization");
}

sub restore {
    die new IMPL::NotImplementedException("This class doesn't support serialization");
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::View::TTFactory> - фабрика элементов управления

=head1 SYNOPSIS

=begin code

my $factory = new IMPL::Web::View::TTFactory(
    'IMPL::Web::View::TTControl',
    $doc,
    $context,
    {
        TRIM => 1
    },
    {
        myprop => 'my value'
    },
);

my $input1 = $factory->new('login', { class => "required" } );

my $propval = $input->nodeProperty('myprop'); # 'my value'

=end code

=begin text

[%
    this.appendChild(
        my.org.input.new('login', class = this.errors('login') ? "invalid" : "" )
    );
%]

=end text

=head1 DESCRIPTION

C< Inherits L<IMPL::Object::Factory> >

=head1 MEMBERS

=over

=item C<[get,set]template>

Документ C<Template::Document> который описывает элемент управления. См. C<IMPL::Web::View::TTControl>.

=item C<[get,set]context>

Контекст фабрики элементов управления, в этом контексте выполняет шаблон элемента управления при загрузке.
Далее в этом контексте будет выполнен блок инициализации при создании первого элемента управления.

=item C<[get,set]opts>

Параметры контекста элемента управления (ссылка на хеш). Каждый элемент управления при создании получает свой контекст,
который создает с данными параметрами и хранилищем переменных, дочерним к контексту фабрики.

=item C<[get,set]nodeProperties>

Ссылка на хеш со значениями свойств по умолчанию для создаваемого элемента управления.

=item C<[get]instances>

Количество созданных элементов управления данной фабрикой

=item C<[override]MergeParameters($name,$nodeProps)>

Превращает значения переданные методу C<new> фабрики в параметры для создания элемента управления.

=over

=item C<$name>

Имя создаваемого узла (C<nodeName>).

=item C<$nodeProps>

Ссылка на шех со значениями свойств узла. Данные значения будут совмещены со значениями из свойства C<nodeProperties>

=back

=item C<[override]CreateObject(@params)>

Создает экземпляр элемента управления стандартным образом. Учитывает количество экземпляров и если это первый,
то производит дополнительную инициализацию контекста выполнив блок шаблона C<INIT>.

=item C<[inherited]new($name,$nodeProps)>

Создает элемент управления с указанным именем и набором свойств. 

=back

=cut