# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be 
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: Template.pm 3043 2008-03-31 14:00:38Z josh $

package BigMed::Template;
use strict;
use utf8;
use Carp;
use BigMed;
use BigMed::DiskUtil ('bm_file_path', 'bm_load_file', 'bm_check_space', 'bm_write_file', 'bm_confirm_dir', 'bm_delete_file', 'bm_delete_dir', 'bm_untaint_filepath', 'bm_copy_file');
use BigMed::Error;

sub new {
    my $class = shift;
    my $site;
    if ( @_ == 1 ) {
        $site = shift;
    }
    else {
        my %param = @_;
        $site = $param{site};
    }
    if ( !ref $site || !$site->isa('BigMed::Site') ) {
        croak 'Usage: $class->new($site) or $class->new(site=>$site);';
    }
    elsif ( !$site->id ) {
        croak 'Site object must have id to create a BigMed::Template object';
    }

    my $bm       = BigMed->bigmed;
    my $site_dir = bm_file_path(
        $bm->env('MOXIEDATA'), 'templates_custom',
        'site_templates',      'site' . $site->id
    );
    my $sys_dir =
      bm_file_path( $bm->env('MOXIEDATA'), 'templates', 'site_templates' );

    my $self = bless {
        site     => $site,
        site_dir => $site_dir,
        sys_dir  => $sys_dir,
        section  => {},
    }, $class;

    $self;
}

sub default_template_path {
    my $self = shift;
    my ( $format, $type ) = @_;
    my $site_path = $self->_format_path( $self->{site_dir}, $format, $type );
    return $site_path if -e $site_path;
    my $sys_path = $self->_format_path( $self->{sys_dir}, $format, $type );
    return $sys_path if -e $sys_path;
    croak "No default template found for format '$format' and type '$type' "
      . "-- expected at $sys_path";
}

sub template_path {
    my $self   = shift;
    my %param  = @_;
    my $format = $param{'format'}
      or croak 'template_path requires format parameter';
    my $type = $param{'type'}
      or croak 'template_path requires type parameter';
    my $section = $param{'section'};
    if ( $section && !ref $section )
    {    #got an id as parameter, get section obj
        $section = $self->{site}->section_obj_by_id($section);
    }
    elsif (!$section) {
        return $self->default_template_path($format, $type);
    }

    #check the cache for the path, or find it (inheriting if necessary)
    my $id     = $section->id;
    my $lookup = $self->{sec_path}->{$id};
    if ( !$lookup || !$lookup->{$format} || !$lookup->{$format}->{$type} ) {
        my $check_path =
          $self->_format_path( $self->{site_dir}, $format, $type, $id );
        $self->{sec_path}->{$id}->{$format}->{$type} =
          -e $check_path
          ? $check_path
          : $self->_parent_template_path( $format, $type, $section );
    }
    return $self->{sec_path}->{$id}->{$format}->{$type};

}

sub template_text {
    my $self = shift;
    my %param  = @_;
    my $format = $param{'format'}
      or croak 'template_text requires format parameter';
    my $type = $param{'type'}
      or croak 'template_text requires type parameter';
    my $section = $param{'section'};
    my $path = $self->template_path(
        'format' => $format,
        'type' => $type,
        'section' => $section,
    );
    return join( "\n", bm_load_file($path) );
}


sub template_source {
    my $self = shift;
    my %param  = @_;
    my $format = $param{'format'}
      or croak 'template_source requires format parameter';
    my $type = $param{'type'}
      or croak 'template_source requires type parameter';
    my $section = $param{'section'};
    my $path = $self->template_path(
        'format' => $format,
        'type' => $type,
        'section' => $section,
    );

    if ($path =~ /(\d+)_\Q$type\E[.]txt$/) {
        return $1;
    }
    elsif (index($path, $self->{site_dir}) == 0) {
        return 'site';
    }
    else {
        return '';
    }
}

sub save_template {
    my $self = shift;
    my %param  = @_;
    my $format = $param{'format'}
      or croak 'save_template requires format parameter';
    my $type = $param{'type'}
      or croak 'save_template requires type parameter';
    defined( my $text = $param{'text'} )
      or croak 'save_template requires text parameter';
    my $section = $param{'section'};
    my $id = ref $section ? $section->id : ( $section || q{} );
    
    bm_confirm_dir($self->{site_dir}, {data =>1, build_path=>1}) or return;
    bm_check_space($self->{site_dir}, length $text) or return;
    my $path = $self->_format_path( $self->{site_dir}, $format, $type, $id );
    bm_write_file($path, $text, {data => 1, build_path => 1}) or return;
}


sub delete_template {
    my $self = shift;
    my %param  = @_;
    my $format = $param{'format'}
      or croak 'delete_template requires format parameter';
    my $type = $param{'type'}
      or croak 'delete_template requires type parameter';
    my $section = $param{'section'};
    my $id = ref $section ? $section->id : ( $section || q{} );
    
    my $path = $self->_format_path( $self->{site_dir}, $format, $type, $id );
    bm_delete_file($path); #returns true on success, undef/error on fail
}

sub delete_all_custom_templates { #delete the site's custom template directory
    my $self = shift;
    bm_delete_dir($self->{site_dir});
}

sub copy_all_to_site {
    my $self = shift;
    my ($tsite, $rmap) = @_;
    if (!ref $tsite || !$tsite->isa('BigMed::Site')) {
        croak 'copy_all_to_site requires target site object';
    }
    $rmap ||= {};
    my $dir = $self->{site_dir};
    return 1 if !-d $dir;
    
    #gather formats -- all directories in the site template dir
    my $ERR = 'BigMed::Error';
    my ($TDIR, $file, @formats);
    opendir( $TDIR, $dir )
      or return $ERR->set_io_error( undef, 'opendir', $dir, $! );
    while ( defined ( $file = readdir $TDIR ) ) {
        next if index($file, '.') >= 0;
        push @formats, $file;
    }
    closedir( $TDIR );
    
    #copy all templates from directory file
    my $target   = BigMed::Template->new($tsite);
    my $targ_dir = $target->{site_dir};
    foreach my $format (@formats) {
        my $tfdir = bm_file_path( $targ_dir, $format );
        $self->copy_format_to_dir($format, $tfdir, {build_path=>1,data=>1}, $rmap)
          or return;
    }
    return 1;
}

sub copy_format_to_dir {
    my ($self, $format, $tfdir, $rparam, $rmap) = @_;
    if (!$format || !$tfdir) {
        croak 'usage: $self->copy_format_to_dir($format_name, $target_dir)';
    }
    $rmap ||= {};

    my $fdir =
      bm_untaint_filepath( bm_file_path( $self->{site_dir}, $format ) )
      or return;
    return 1 if !-d $fdir;
    bm_confirm_dir( $tfdir, $rparam ) or return;

    my $ERR = 'BigMed::Error';
    my $FDIR;
    opendir( $FDIR, $fdir )
      or return $ERR->set_io_error( undef, 'opendir', $fdir, $! );
    while ( defined ( my $file = readdir $FDIR ) ) {
        next if substr( $file, 0, 1 ) eq q{.};

        my ( $id, $tmpl ) = ( $file =~ /\A(\d*)(_.*[.]txt)\z/ );
        next if !$tmpl; #doesn't fit format

        my $origpath = bm_file_path( $fdir,  $file );
        my $newfile = $tmpl;
        if ($id) {
            my $nid = $rmap->{$id} or next;    #skip if no section mapping
            $newfile = $nid . $tmpl;
        }
        my $newpath  = bm_file_path( $tfdir, $newfile );
        bm_copy_file( $origpath, $newpath, $rparam ) or return;
    }
    closedir($FDIR);
    return 1;
}

sub _parent_template_path {
    my $self = shift;
    my ( $format, $type, $section ) = @_;
    my $pid = ( $section->parents )[-1];

    my $no_parent = $section->is_homepage
      || !defined $pid
      || $pid == $self->{site}->homepage_id;
    if ($no_parent) {
        return $self->default_template_path( $format, $type );
    }

    my $parent = $self->{site}->section_obj_by_id($pid);
    croak "no such parent section '$pid' for section " . $section->name
      if !$parent;

    my $lookup = $self->{sec_path}->{ $parent->id };
    if ( $lookup && $lookup->{$format} && $lookup && $lookup->{$format}->{$type}  ) {
        return $lookup->{$format}->{$type};
    }
    else {                                    #look up path
        return $self->template_path(
            'section' => $parent,
            'format'  => $format,
            'type'    => $type
        );
    }
}

sub _format_path {
    my $self = shift;
    my ( $dir, $format, $type, $id ) = @_;
    $id = '' if !$id;
    return bm_file_path( $dir, $format, $id . "_$type.txt" );
}

1;

__END__

=head1 BigMed::Template

Template file management for Big Medium

=head1 Description

Handles locating, saving and deleting site template files for Big Medium.
It's "dumb" about BigMed::Format formats and does not check to see if the
requested format is valid or not.  But it's smart about sites and sections
and automagically handles template inheritance for custom sections,
correctly delivering an inherited template if none exists for the requested
site or section.

For more about format and template types, see BigMed::Format.

=head1 Synopsis

    #construct a template object
    my $tmpl = BigMed::Template->new($site_obj);
    
    # get the filepath for a site's master template (or the Big Medium default
    # template if no site master exists)
    my $master_tmpl =
      $tmpl->default_template_path($format_name, $template_type);

    # get the filepath for a section's effective template (inherited if
    # necessary)
    my $section_tmpl = $tmpl->template_path(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    );
    
    # get the template text as a string
    my $section_tmpl = $tmpl->template_text(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    );
    
    #find out where the template comes from
    my $source = $tmpl->template_source(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    );
    # source is a section id if it's a custom section template, 'site' if it's
    # the site master or an empty string '' if it's the system default.
    
    #save template
    $tmpl->save_template(
        text     => $template_text,
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    ) or BigMed->bigmed->error_stop();
    
    #delete template
    $tmpl->delete_template(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    ) or BigMed->bigmed->error_stop();

=head1 Methods

=head2 new

    my $tmpl = BigMed::Template->new($site_obj);

Returns a new BigMed::Template object. Requires a site object as the argument.

=head2 default_template_path

    my $path = $tmpl->default_template_path($format_name, $template_type);

Returns the path for the site's master template for the format and template
type specified in the argument hash. If there is no site master template,
the path for the Big Medium default template is returned instead.

Dies if no such format or template exists.

=head2 template_path

    my $path = $tmpl->template_path(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    );

Returns the path for the effective template for the section, format and
template type specified in the argument hash. If no such template exists
for the section the method climbs the section's inheritance tree and
returns the nearest template it finds.

The section key/value argument is optional and if omitted the path to the
site's master template (or the system default template) is returned instead.

Dies if no such format or template exists.

=head2 template_text

    my $text = $tmpl->template_text(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    );

Returns the full text of the effective template for the
section/format/type specified in the argument hash. Same as fetching
the file contents from the path supplied by C<template_path> when the same
arguments are passed to that method.

=head2 template_source

    my $source = $tmpl->template_source(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    );

Returns the source of the effective template for the section/format/type
specified in the argument hash.  If the source template is a custom section
ID, the ID is returned. If the source template is the site master, the string
'site' is returned. If the source template is the system default template,
an empty string '' is returned.

=head2 save_template

    $tmpl->save_template(
        text     => $template_text,
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    ) or BigMed->bigmed->error_stop();

Saves the text specified in the text key/value pair to the template file
specified by the section, format and type hash arguments. If section
is omitted, the template is saved as the site's master template for this
format and template type.

Returns undef and sets an error in the error queue if there is a problem.

=head2 delete_template

    $tmpl->delete_template(
        section  => $section_obj_or_id,
        format   => $format_name,
        type     => $template_type,
    ) or BigMed->bigmed->error_stop();

Removes the template specified by the section/format/type hash
arguments. If section is omitted, the site's master template is removed.

Returns undef and sets an error in the error queue if there is a problem.

=head2 delete_all_custom_templates

    $tmpl->delete_all_custom_templates() or BigMed->bigmed->error_stop();

Removes all custom templates for a site. Returns undef if there's trouble.

=head2 copy_all_to_site

    #copy sitewide templates
    $tmpl->copy_all_to_site( $target_site )
      or BigMed->bigmed->error_stop();
    
Copies all custom templates to the target site in the first argument.
If a hash reference is provided as the second argument, custom templates
for sections whose ids are included as keys in the has are copied to the
sections whose ids are in the corresponding values.

    my %map = (
        '10' => '94',
        '11' => '103',
    );
    
    #custom templates for section 10 will be copied to the target site's
    #section 94; likewise section 11's templates will be copied to the
    #target site's section 103.
    
    $tmpl->copy_all_to_site( $target_site, %map );
      or BigMed->bigmed->error_stop();

The routine returns true on success or false on error, adding a message
to the BigMed::Error error queue.

The routine does not first remove any existing templates from the target
site; it simply copies any custom template from the source site to the
target site, replacing any templates of the same type and level. If you
want to remove the target site's existing templates, you should do that
first before using C<copy_all_to_site>.

=head1 SEE ALSO

=over 4

=item * BigMed::Format

=back

=head1 AUTHOR & COPYRIGHTS

This module and all Big Medium modules are copyright Josh Clark
and Global Moxie. All rights reserved.

Use of this module and the Big Medium content
management system are governed by Global Moxie's software licenses
and may not be used outside of the terms and conditions outlined
there.

For more information, visit the Global Moxie website at
L<http://globalmoxie.com/>.

Big Medium and Global Moxie are service marks of Global Moxie
and Josh Clark. All rights reserved.

=cut

