Revision 3 (by ahitrov@rambler.ru, 2010/03/24 15:19:32) The CORE
package Contenido::Section;

# ----------------------------------------------------------------------------
# ����� ������.
# ������ ��� ����� �� - ������� �����, ������ � ���� �������������� 
#   ����������������.
# ----------------------------------------------------------------------------

use strict;
use warnings;
use locale;

use vars qw($VERSION $ROOT);
$VERSION = '6.0';

use base 'Contenido::Object';
use Contenido::Globals;

$ROOT = 1;          # �������� ������

sub class_name {
    return '������';
}

sub class_description {
    return '������ �� ���������';
}

# DEFAULT ���� ���������� �������
sub class_table {
    return 'SQL::SectionTable';
}

# ----------------------------------------------------------------------------
# �����������. ������� ����� ������ ������.
#
# ������ �������������:
#   Contenido::Section->new()
#   Contenido::Section->new($keeper)
#   Contenido::Section->new($keeper,$id)
#   Contenido::Section->new($keeper,$id,$pid)
# ----------------------------------------------------------------------------
sub new {
    my ($proto, $keeper, $id, $pid) = @_;
    my $class = ref($proto) || $proto;
    my $self;

    if (defined($id) && ($id>0) && defined($keeper)) {
        $self=$keeper->get_section_by_id($id, class=>$class);
    } else {
        $self = {};
        bless($self, $class);
        $self->init();
        $self->keeper($keeper)      if (defined($keeper));
        $self->{class} = $class;
        $self->id($id)          if (defined($id) && ($id > 0));
        $self->pid($pid)        if (defined($pid) && ($pid > 0));
    }

    return $self;
}

sub _get_table {
    class_table()->new();
}

#��������� ������ store
sub store {
    my $self=shift;

    #��� ������������� ������ ������ ����� sorder
    unless ($self->{id}) {
        my ($sorder) = $self->keeper->SQL->selectrow_array("select max(sorder) from ".$self->class_table->db_table(), {});
        $self->{sorder} = $sorder + 1;
    }

    return $self->SUPER::store();
}


## ����������� �������� ��� ������ � ���������� ����������� ����������
sub add_properties {
    return (
        {                           # ������� "������ � �����������"
            'attr' => '_sorted',
            'type' => 'checkbox',
            'rusname' => '������ ���������� ����������',
        },
        {                           # ������� ���������� (������ id)
            'attr' => '_sorted_order',
            'type' => 'string',
            'rusname' => '������� ���������� � ������',
            'hidden' => 1
        },
        {
            'attr' => 'default_document_class',
            'type' => 'string',
            'rusname' => '����� ���������� � ������, ������������ �� ���������',
        },
        {
            'attr' => 'default_table_class',
            'type' => 'string',
            'rusname' => '����� �������, ��������� ������� ����� �������� �� ���������',
        },
        {
            'attr' => 'order_by',
            'type' => 'string',
            'rusname' => '���������� ����������',
        },
        {
            'attr' => 'no_count',
            'type' => 'checkbox',
            'rusname' => '�� ������������� ��������� � ������� �������',
        },
        {
            'attr' => 'filters',
            'type' => 'struct',
            'rusname' => '�������������� ������� �������',
        },
    );
}

# ����������� ��� �������� ��������� ������
sub root {
    return new(shift, shift, $ROOT);
}


# �������� ��� ������ parent
sub parent {
    return shift->pid(@_);
}

# ----------------------------------------------------------------------------
# �������� ������. ����� ��������� ���������� �������� - � �����
#   �� �� ����� ������� (����� ���� �������). ���� ���� �������, �� ������
#   �� ���������. �������� �� ������� �������� � ���� ������ �� ������������,
#   �� ������ ���������� �� ���� ���������.
# ----------------------------------------------------------------------------
sub delete
{
    my $self = shift;
    do { $log->error("����� ->delete() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);
    do { $log->warning("����� ������ ->delete() ��� �������� �������������� ��� ��������"); return undef } unless ($self->{id});

    # �������� ������� �����...
    my ($one_id) = $self->keeper->SQL->selectrow_array('select id from '.$self->class_table->db_table.' where pid = ?', {}, $self->id);
    if (defined($one_id) && ($one_id > 0)) { return "������ ������� ������, � ������� ���� ��������� ������\n"; };

    $self->SUPER::delete();

    return 1;
}



# ----------------------------------------------------------------------------
# �����, ������������ ������ ������� (� ������� sorder ��� 
#   ������� ������). � ��������� ���������� �������.
#
# ������ ������:
#   $section->childs([�������]);
# ----------------------------------------------------------------------------
sub childs {
    my ($self, $depth) = @_;
    do { $log->error("����� ->childs() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);

    # ������� �� ��������� - 1
    $depth ||= 1;

    my $SIDS = [];
    my $NEW_SIDS = [$self->id];

    #���� �� ���������� ������ ������� � ���� ������� ����� ����
    while ($depth>0) {
        $NEW_SIDS = $self->keeper->get_sections(s=>$NEW_SIDS, ids=>1, return_mode=>'array_ref', order_by=>'pid, sorder');
        if (ref($NEW_SIDS) and @$NEW_SIDS) {
            push (@$SIDS, @$NEW_SIDS);
        } else {
            last;
        }
        $depth--;
    }
    return @$SIDS;
}



# ----------------------------------------------------------------------------
# ����� ��� ����������� ������ �����/���� �� ����������� (���������
#   sorder)...
#
# ������ ������:
#   $section->move($direction); ����������� �������� ������� 'up'/'down'
# ----------------------------------------------------------------------------
sub move {
    my ($self, $direction) = @_;
    do { $log->error("����� ->move() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);

    return undef if ($self->keeper->state->readonly());

    my $keeper = $self->keeper;
    do { $log->error("� ������� ������ �� ���������� ������ �� ���� ������"); die } unless ref($keeper);
    do { $log->warning("����� ������ ->move() ��� �������� �������������� ������"); return undef }
                                                unless (exists($self->{id}) && ($self->{id} > 0));
    do { $log->warning("����� ������ ->move() ��� �������� ������� ���������� (sorder)"); return undef }
                                                unless (exists($self->{sorder}) && ($self->{sorder} >= 0));
    do { $log->warning("����� ������ ->childs() ��� �������� ��������"); return undef }  unless (exists($self->{pid}) && ($self->{pid} >= 0));

    $direction = lc($direction);
    if ( ($direction ne 'up') && ($direction ne 'down') ) { $log->warning("����������� ����������� ������ ������ ��������"); return undef };


    $keeper->t_connect() || do { $keeper->error(); return undef; };
    $keeper->TSQL->begin_work();


    # ��������� �������� ������ ��� ������...
    my ($id_, $sorder_);
    if ($direction eq 'up')
    {
        ($id_, $sorder_) = $keeper->TSQL->selectrow_array("select id, sorder from ".$self->class_table->db_table." where sorder < ? and pid = ? order by sorder desc limit 1", {}, $self->{sorder}, $self->{pid});
    } else {
        ($id_, $sorder_) = $keeper->TSQL->selectrow_array("select id, sorder from ".$self->class_table->db_table." where sorder > ? and pid = ? order by sorder asc limit 1", {}, $self->{sorder}, $self->{pid});
    }


    # ���������� �����...
    if ( defined($id_) && ($id_ > 0) && defined($sorder_) && ($sorder_ > 0) )
    {
        $keeper->TSQL->do("update ".$self->class_table->db_table." set sorder = ? where id = ?", {}, $sorder_, $self->{id})
            || return $keeper->t_abort();
        $keeper->TSQL->do("update ".$self->class_table->db_table." set sorder = ? where id = ?", {}, $self->{sorder}, $id_)
            || return $keeper->t_abort(); 
    } else {
        $log->warning("�� ���� ���������� � ��������� (�� ������� �������� ��� ��� ���)"); return 2;
    }

    $keeper->t_finish();
    $self->{sorder} = $sorder_;
    return 1;
}



# ----------------------------------------------------------------------------
# ����� ��� ����������� ��������� � id = $doc_id �����/���� 
#   �� ������� ���������� (� �������� ������� ������)...
#
# ������ ������:
#   $doc->dmove($doc_id, $direction); ����������� �������� ������� 'up'/'down'
# ----------------------------------------------------------------------------
sub dmove {
    my ($self, $doc_id, $direction) = @_;
    do { $log->error("����� ->dmove() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);

    return undef if ($self->keeper->state->readonly());
    
    my $keeper = $self->keeper;
    do { $log->error("� ������� �� ���������� ������ �� ���� ������"); die } unless ref($keeper);
    do { $log->warning("����� ������ ->dmove() ��� �������� �������������� ������"); return undef }
                                                unless (exists($self->{id}) && ($self->{id} > 0));

    $direction = lc($direction);
    if ( ($direction ne 'up') && ($direction ne 'down') ) { $log->warning("����������� ����������� ��������� ������ �������"); return undef };

    my $sorder_;
    if ($self->_sorted()) {
        my @ids = $keeper->get_documents( ids =>1, s => $self->id(), ($self->default_document_class ? (class => $self->default_document_class) : ()), order => ['date', undef], light => 1);
        my %ids = map { $_ => 1 } @ids;
        unless ($self->{_sorted_order}) {
            $self->{_sorted_order} = join ',', @ids;
        }

        my @order = split(/,/, $self->{_sorted_order});
        @order = grep {
            my $res;
            if (exists $ids{$_}) {
                $res = 1;
                delete $ids{$_};
            }
            $res
        } @order;

        push @order, keys %ids;

        foreach my $i (0 .. $#order) {
            if ($order[$i] == $doc_id) {
                my $t;
                if ($direction eq 'up') {
                    last if $i == 0;
                    $t = $order[$i-1];
                    $order[$i-1] = $order[$i];
                    $order[$i] = $t;
                    $sorder_ = $i - 1;
                    last;
                } elsif ($direction eq 'down') {
                    last if $i == $#order;
                    $t = $order[$i+1];
                    $order[$i+1] = $order[$i];
                    $order[$i] = $t;
                    $sorder_ = $i + 1;
                    last;
                }
            }
        }

        $self->{_sorted_order} = join ',', @order;
        $self->store();
    } else {
        $log->warning("dmove called for section without enabled sorted feature... $self->{id}/$self->{class}");
    }

    $self->{sorder} = $sorder_;
    return 1;
}




# ----------------------------------------------------------------------------
# ����� ��� ���������� ���� ����� ����� ���������... ����������
#   ������ ��������������� ������ �� ������� ������ �� $root_id �����
#   �����. $root_id ����������� ������ ���� ���� �� �����������. � ���������
#   ���������� ��� �������. ���� ������� �����, �� ������������ ������ ��
#   ������ ��������, ���� ���� ��� - �� ������ ������.
#
# ������ ������:
#   $section->trace($root_id)
# ----------------------------------------------------------------------------
sub trace {
    my ($self, $root_id) = @_;
    do { $log->error("����� ->trace() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);

    do { $log->warning("����� ������ ->trace() ��� �������� �������������� ������"); return () }
                                                unless (exists($self->{id}) && ($self->{id} > 0));
    $root_id ||= $ROOT;

    my $id_ = $self->{id};
    my @SIDS = ($id_);
    my $sth = $self->keeper->SQL->prepare_cached("select pid from ".$self->class_table->db_table." where id = ?");

    while ($id_ != $root_id)
    {
        $sth->execute($id_);
        ($id_) = $sth->fetchrow_array();
        if (defined($id_) && ($id_ > 0))
        {
            unshift (@SIDS, $id_);
        } else {
            # �� �������� ����������� ����� �� ��������, � �� ����� �� �����...
            $sth->finish;
            return ();          
        }
    }
    $sth->finish;
    return @SIDS;
}


# ----------------------------------------------------------------------------
# ������ 
# ���������� ������ ��������������� ���� ������� (��������� �� ���� �������) ������ ������
# ----------------------------------------------------------------------------
sub ancestors 
{
    my $self = shift;
    do { $log->error("����� ->ancestors() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);

    my $keeper = $self->keeper;
    do { $log->error("� ������� ������ �� ���������� ������ �� ���� ������"); die } unless ref($keeper);

    do { $log->warning("����� ������ ->ancestors() ��� �������� �������������� ������"); return () } unless (exists($self->{id}) && ($self->{id} > 0));

    my @ancestors = ();
    my $sectionid = $self->{id};
    while ($sectionid)
    {
        $sectionid = $keeper->SQL->selectrow_array("select pid from ".$self->class_table->db_table." where id = ?", {}, $sectionid);
        push @ancestors, $sectionid if defined $sectionid && $sectionid;
    }   
    return @ancestors;
}

# ----------------------------------------------------------------------------
# �������
# ���������� ������ ��������������� ���� �������� (����� �� ���� �������) ������ ������
# ----------------------------------------------------------------------------
sub descendants 
{
    my $self = shift;
    do { $log->error("����� ->descendants() ����� �������� ������ � ��������, �� �� �������"); die } unless ref($self);

    my $keeper = $self->keeper;
    do { $log->error("� ������� ������ �� ���������� ������ �� ���� ������"); die } unless ref($keeper);

    do { $log->warning("����� ������ ->descendants() ��� �������� �������������� ������"); return () } unless (exists($self->{id}) && ($self->{id} > 0));

    my @descendants = ();
    my @ids = ($self->{id});
    while (scalar @ids)
    {
        my $sth = $keeper->SQL->prepare("select id from ".$self->class_table->db_table." where pid in (" . (join ", ", @ids) . ")");
        $sth->execute;
        @ids = ();
        while (my ($id) = $sth->fetchrow_array)
        {
            push @ids, $id;
        }
        $sth->finish();
        push @descendants, @ids;
        last if !$sth->rows;
    }
    return @descendants;
}

# -------------------------------------------------------------------------------------------------
# ��������� �������...
# ���������:
#   light => ����������� ������
#   root => ������ ������ (�� ��������� - 1)
# -------------------------------------------------------------------------------------------------
sub get_tree {
    my ($self, %opts) = @_;
    do { $log->warning("����� ->get_tree() ����� �������� ������ � ��������, �� �� �������"); return undef } unless ref($self);

    my $root = $opts{root} || $ROOT;

    
    # ----------------------------------------------------------------------------------------
    # �������� ��� ������
    $opts{no_limit} = 1;
    delete $opts{root};
    my @sections = $self->keeper->get_sections(%opts);

    my $CACHE = {};
    foreach my $section (@sections) {
        if (ref($section)) {
            $CACHE->{$section->id()} = $section;
        }
    }

    for my $id (sort { $CACHE->{$a}->sorder() <=> $CACHE->{$b}->sorder() } (keys(%{ $CACHE }))) {
        my $pid = $CACHE->{$id}->pid() || '';
        $CACHE->{$pid}->{childs} = []       if (! exists($CACHE->{$pid}->{childs}));
        $CACHE->{$id}->{parent} = $CACHE->{$pid};
        push (@{ $CACHE->{$pid}->{childs} }, $CACHE->{$id} );
    }

    return $CACHE->{$root};
}

1;