view Lib/DOM/Providers/Page.pm @ 31:d59526f6310e

Small fixes to Test framework (correct handlinf of the compilation errors in the test units) Imported and refactored SQL DB schema from the old project
author Sergey
date Mon, 09 Nov 2009 01:39:16 +0300
parents 03e58a454b20
children 16ada169ca75
line wrap: on
line source

use strict;

package DOM::Providers::Page;
use Template::Provider;
#use PerfCounter;
use DOM::Page;
use Common;
use Encode;

our @ISA= qw(Object Exporter);

our $UseIndexPage;  #optional
our $PagesPath;     #required
our $IncludesPath;  #optional
our $CacheSize;     #optional
our $CachePath;     #optional
our $Encoding;      #optional
our $AllowExtPath;  #optional
our $PageResolver;  #optional


BEGIN {
    DeclareProperty('PageResolver');
    DeclareProperty('PagesBase');
    DeclareProperty('IndexPage');
    DeclareProperty('TemplatesProvider');
    DeclareProperty('PageEnc');
}

sub as_list {
    return( map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : defined $_ ? $_ : () } @_ );
}

sub GetProviderInfo {
    return {
        Name => 'Page',
        Host => 'DOM::Site',
        Methods => {
            LoadPage => \&SiteLoadPage,
            ReleasePage => \&SiteReleasePage,
        }
    }
}

sub CTOR {
    my ($this,%args) = @_;
    
    $this->{$PageResolver} = $args{'PageResolver'};
    $this->{$PagesBase} = $args{'TemplatesPath'};
    $this->{$IndexPage} = $args{'IndexPage'} || 'index.html';
    $this->{$PageEnc} = $args{'Encoding'};
    $this->{$TemplatesProvider} = new Template::Provider( INCLUDE_PATH => [$this->{$PagesBase}, as_list($args{'IncludePath'}) ], COMPILE_DIR => $args{'CachePath'}, CACHE_SIZE => $args{'CacheSize'}, ENCODING => $args{'Encoding'}, ABSOLUTE => $AllowExtPath, RELATIVE => $AllowExtPath, INTERPOLATE => 1, PRE_CHOMP => 3);
}

sub ResolveId {
    my ($this,$pageId) = @_;
    
    if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'ResolveId')) {
        return $this->{$PageResolver}->ResolveId($pageId);
    } else {
        return grep { $_ } split /\//,$pageId;
    }
}

sub MakePageId {
    my ($this,$raPath) = @_;
    
    if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'MakeId')) {
        return $this->{$PageResolver}->MakeId($raPath);
    } else {
        return join '/',@$raPath;
    }
}

sub PageIdToURL {
    my ($this,$pageId) = @_;
    
    if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'PageIdToURL')) {
        return $this->{$PageResolver}->PageIdToURL($pageId);
    } else {
        return '/'.$pageId;
    }
}

sub SiteLoadPage {
    my ($this,$site,$pageId) = @_;
    
    return $site->RegisterObject('Page', $this->LoadPage($pageId, Site => $site));
}
sub LoadPage {
    my ($this,$pageId,%args) = @_;
    
    #StartTimeCounter('LoadPageTime');

    my @pathPage = $this->ResolveId($pageId);

    my $pageNode = $this->LoadNode(\@pathPage);
    
    pop @pathPage;
    
    my @pathNode;
    
    # поскольку путь указан относительно корневого контейнера, то нужно его добавить в начало
    my @NavChain = map { push @pathNode, $_; $this->LoadNode(\@pathNode); } ('.',@pathPage);
    
    if ($pageNode->{'Type'} eq 'Section') {
        push @NavChain,$pageNode;
        $pageNode = $this->LoadNode($pageNode->{'pathIndexPage'});
    }
    
    # формируем меню страницы
    my %PageMenus;
    foreach my $MenuSet (map { $_->{'Menus'}} @NavChain, $pageNode->{'Menus'} ) {
        foreach my $menuName (keys %$MenuSet) {
            if ($PageMenus{$menuName}) {
                $PageMenus{$menuName}->Merge($MenuSet->{$menuName});
            } else {
                $PageMenus{$menuName} = $MenuSet->{$menuName};
            }
        }
    }
    
    # формируем ключевые слова и свойства
    my @keywords;
    my %Props;
    foreach my $PropSet ( (map { $_->{'Props'}} @NavChain), $pageNode->{'Props'} ) {
        if(ref $PropSet->{'Keywords'} eq 'ARRAY') {
            push @keywords, @{$PropSet->{'Keywords'}};
        } elsif (not ref $PropSet->{'Keywords'} and exists $PropSet->{'Keywords'}) {
            push @keywords, $PropSet->{'Keywords'};
        }
        
        while (my ($prop,$value) = each %$PropSet) {
            next if $prop eq 'Keywords';
            $Props{$prop} = $value;
        }
    }
        
    #StopTimeCounter('LoadPageTime');
    # загружаем шаблон
    
    #StartTimeCounter('FetchTime');
    my ($Template,$error) = $this->{$TemplatesProvider}->fetch($pageNode->{'TemplateFileName'});
    die new Exception("Failed to load page $pageId",$Template ? $Template->as_string : 'Failed to parse') if $error;
    #StopTimeCounter('FetchTime');
    
    my $page = new DOM::Page(TemplatesProvider => $this->{$TemplatesProvider}, Properties => \%Props, Menus => \%PageMenus, NavChain => \@NavChain, Template => $Template, %args);
    $page->Properties->{url} = $this->PageIdToURL($pageId);
    return $page;
}

sub LoadNode {
    my ($this,$refNodePath) = @_;
    
    my $fileNameNode = $this->{$PagesBase} . join('/',grep $_, @$refNodePath);
    my $fileNameMenus;
    my $fileNameProps; 
    
    my %Node;

    if ( -d $fileNameNode ) {
        $Node{'Type'} = 'Section';
        $fileNameMenus = $fileNameNode . '/.menu.pl';
        $fileNameProps = $fileNameNode . '/.prop.pl';
    } elsif ( -e $fileNameNode ) {
        $Node{'Type'} = 'Page';
        $Node{'TemplateFileName'} = join('/',@$refNodePath);;
        $fileNameMenus = $fileNameNode . '.menu.pl';
        $fileNameProps = $fileNameNode . '.prop.pl';
    } else {
        die new Exception("Page not found: $fileNameNode");
    }
    
    if ( -f $fileNameProps ) {
        local ${^ENCODING};
        my $dummy = '';
        open my $hnull,'>>',\$dummy;
        local (*STDOUT,*STDIN) = ($hnull,$hnull);
        $Node{'Props'} = do $fileNameProps or warn "can't parse $fileNameProps: $@";
    }
    
    if ( -f $fileNameMenus ) {
        local ${^ENCODING};
        my $dummy = '';
        open my $hnull,'>>',\$dummy;
        local (*STDOUT,*STDIN) = ($hnull,$hnull);
        $Node{'Menus'} = do $fileNameMenus or warn "can't parse $fileNameMenus: $@";
    }
    
    if ($Node{'Menus'}) {
        my %Menus;
        foreach my $menu (keys %{$Node{'Menus'}}) {
            $Menus{$menu} = new DOM::PageMenu( DATA => $Node{'Menus'}->{$menu} );
        }
        $Node{'Menus'} = \%Menus;
    }
    
    $Node{'pathIndexPage'} = [@$refNodePath, $Node{'Props'}->{'IndexPage'} || $this->{$IndexPage}] if $Node{'Type'} eq 'Section';
    
    return \%Node;
}

sub SiteReleasePage {
    my ($this,$site) = @_;
    
    my $page = $site->Objects()->{'Page'};
    $page->Release() if $page;
    
    return 1;
}

sub construct {
    my $self = shift;
    
    return new DOM::Providers::Page(TemplatesPath => $PagesPath, IncludePath => $IncludesPath, IndexPage => $UseIndexPage, CachePath => $CachePath, CacheSize => $CacheSize, Encoding => $Encoding);
}

sub DecodeData {
    my ($Encoding, $data) = @_;
    
    if (ref $data) {
        if (ref $data eq 'SCALAR') {
            my $decoded = Encode::decode($Encoding,$$data,Encode::LEAVE_SRC);
            return \$decoded;
        } elsif (UNIVERSAL::isa($data, 'HASH')) {
            return {map {Encode::decode($Encoding,$_,Encode::LEAVE_SRC),DecodeData($Encoding,$data->{$_})} keys %$data };
        } elsif (UNIVERSAL::isa($data, 'ARRAY')) {
            return [map {DecodeData($Encoding,$_)} @$data];
        } elsif (ref $data eq 'REF') {
            my $decoded = DecodeData($Encoding,$$data);
            return \$decoded;
        } else {
            die new Exception('Cant decode data type', ref $data);
        }
    } else {
        return Encode::decode($Encoding,$data,Encode::LEAVE_SRC);
    }
}

1;

=pod
Хранилище шаблонов на основе файловой системы.

Хранилище состоит из разделов, каждый раздел имеет набор свойств и меню
Специальны свойства разделов
    Keywords Ключевые слова
    Name Название
    IndexPage страница по умолчанию

В разделах находятся страницы, каждая страница имеет набор свойств и меню

При загрузке страницы полностью загружаются все родительские контейнеры,
При этом одноименные меню сливаются,
Свойства keywords объеъединяются,
Если имя страницы не задано, то используется имя раздела

=cut