The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Labyrinth::Writer;

use warnings;
use strict;

use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
$VERSION = '5.29';

=head1 NAME

Labyrinth::Writer - Output Management for Labyrinth

=head1 SYNOPSIS

  use Labyrinth::Writer;
  Publish('mytemplate.html');
  PublishCode('MESSAGE');

=head1 DESCRIPTION

The Publish package contains one function, Publish(), which handles
the parsing of a given template with global variables and prints the result.

=head1 EXPORT

  Publish
  PublishCode
  UnPublish
  Transform
  Croak

=cut

# -------------------------------------
# Export Details

require Exporter;
@ISA       = qw( Exporter );
@EXPORT_OK = qw( Publish PublishCode UnPublish Transform Croak );
@EXPORT    = qw( Publish PublishCode UnPublish Transform Croak );

# -------------------------------------
# Library Modules

use File::Basename;
use MIME::Types;

use Labyrinth::Audit;
use Labyrinth::Globals;
use Labyrinth::Variables;
use Labyrinth::MLUtils;

# -------------------------------------
# Variables

my ($PARSER,$RENDER);

my $published;

my %codes = (
    BADLAYOUT       => 'public/badlayout.html',
    BADPAGE         => 'public/badpage.html',
    BADCMD          => 'public/badcommand.html',
    MESSAGE         => 'public/error_message.html',
);

my %binary = (
    pdf             => 'application/pdf'
);

my %knowntypes = (
    html            => 'text/html',
    ics             => 'text/calendar',
    #js              => 'application/javascript',
    js              => 'text/html',
    json            => 'application/json',
    rss             => 'application/xml',
    txt             => 'text/plain',
    xml             => 'application/xml',
    yml             => 'text/yaml',
    yaml            => 'text/yaml'
);

# -------------------------------------
# The Subs

=head1 FUNCTIONS

=over 4

=item Config()

Configure template parser and output method.

=cut

sub Config {
    $settings{'writer-parser'} ||= 'TT';
    $settings{'writer-render'} ||= 'CGI';

    my $parser = 'Labyrinth::Writer::Parser::' . $settings{'writer-parser'};
    my $render = 'Labyrinth::Writer::Render::' . $settings{'writer-render'};

    eval {
        eval "CORE::require $parser";
        $PARSER = $parser->new();
    };
    die "Cannot load Writer::Parser package for '$settings{'writer-parser'}': $@" if($@);

    eval {
        eval "CORE::require $render";
        $RENDER = $render->new();
    };
    die "Cannot load Writer::Render package for '$settings{'writer-render'}': $@" if($@);

}

=item Publish()

Publish() parses a given template, via Template Toolkit, and prints the
result.

=item PublishCode

Publishes a template based on an internal code. Current codes and associated
templates are:

    BADLAYOUT       => 'public/badlayout.html',
    BADPAGE         => 'public/badpage.html',
    BADCMD          => 'public/badcommand.html',
    MESSAGE         => 'public/error_message.html',

TODO: Provide these and more as configurable codes.

=item UnPublish

Used to reset publishing status. Usually only applicable in mod_perl 
environments.

=item Transform

Given a template and a set of variables, parse without publishing the content.

=cut

sub Publish {
    return  if($published);

    Config()    unless($PARSER && $RENDER);

    # redirects require minimal processing
    if($tvars{redirect} && $tvars{redirect} ne $tvars{request}) {
        $RENDER->redirect($tvars{redirect});
        $published = 1;
        return;
    }

    # binary files handled directly
    if($tvars{contenttype} && $binary{$tvars{contenttype}}) {
        $tvars{'writer'} = { 'ctype' => $binary{$tvars{'contenttype'}}, 'file' => $tvars{'file'} };
        $RENDER->binary($tvars{'writer'});
        $published = 1;
        return;
    }

    my $path = $settings{'templates'} || '';
    my $vars = \%tvars;

    unless($vars->{'layout'} && -r "$path/$vars->{'layout'}") {
        $vars->{'badlayout'} = $vars->{'layout'};
        $vars->{'layout'} = $codes{BADLAYOUT};
    }
    unless($vars->{'content'} && -r "$path/$vars->{'content'}") {
        $vars->{'badcontent'} = $vars->{'content'};
        $vars->{'content'} = $codes{BADPAGE};
    }
    my $layout = $vars->{'layout'};
    my $content = $vars->{'content'};

#   LogDebug( "layout=[$layout]" );
#   LogDebug( "content=[$content]" );
#   LogDebug( "cookie=[$vars->{cookie}]" )  if($vars->{cookie});
#   use Data::Dumper;
#   LogDebug( "vars=".Dumper($vars) );


    $vars->{evalperl} = ($content eq $codes{BADPAGE} ? 1 : 0);

    #LogDebug("<!-- $layout : $content -->");

    my $output;
    eval { $output = $PARSER->parser($layout,$vars) };
    if($@ || !$output) {
        LogDebug( "template error=$@" );
        $$output = $@;
    }

    my ($ext) = $layout =~ m/\.(\w+)$/;
    $ext ||= 'html';

    # split HTML and process etc
    if($ext =~ /htm/) {
        if(defined $settings{parsehtml} && $settings{parsehtml}) {
            my ($top,$body,$tail) = ($$output =~ m!^(.*?<body[^>]*>)(.*?)(</body>.*)$!si);
#           LogDebug( "parsehtml=[$settings{parsehtml}]" );
#           LogDebug( "html=[$html]" );
#           LogDebug( "top=[$top]" );
#           LogDebug( "tail=[$tail]" );
#           LogDebug( "body=[$body]" );
            my $html = $top . process_html($body,0,1) . $tail;

            if($settings{parsetest}) {
                DumpToFile($settings{parsetest},"=== ORIGINAL ===",$$output,"=== PROCESSED ===",$html,"=== END ===");
            }
            $output = \$html;
        }
    }

    $tvars{headers}{type} = $knowntypes{$ext} || do {
        my $types = MIME::Types->new;
        my $mime = $types->mimeTypeOf($ext);
        $mime->type || 'text/html';
    };

    $tvars{headers}{'charset'}    = 'utf-8';
    $tvars{headers}{'status'}     = '404 Page Not Found'    if($content eq $codes{BADPAGE} || $content eq $codes{BADCMD});
    $tvars{headers}{'cookie'}     = $tvars{cookie}          if($tvars{cookie});
    $tvars{headers}{'attachment'} = basename($content)      if($layout =~ /\.ics$/);

    $published = 1;

    return $RENDER->publish($tvars{headers}, $output);
}

sub PublishCode {
    $tvars{'content'} = $codes{$_[0]};
    return Publish();
}

sub UnPublish {
    $published = 0;
}

sub Transform {
    my ($template,$vars,$file,$binary) = @_;

    my $path = $settings{'templates'};
    my $layout = "$path/$template";

    die "Missing template [$layout]\n"  unless(-e $layout);

    Config()    unless($PARSER && $RENDER);

    if($file) {
        $PARSER->parse_to_file($layout,$vars,$file,$binary);
        return;
    }

    my $output = $PARSER->parser($layout,$vars);
    return $$output;
}

=item Croak

A shorthand call to publish and record errors.

=cut

sub Croak {
    my $errmess = join(" ",@_);
    LogError($errmess);
    print STDERR "$errmess\n";
    PublishCode('MESSAGE');
    exit;
}

1;

__END__

=back

=head1 SEE ALSO

  Labyrinth

=head1 AUTHOR

Barbie, <barbie@missbarbell.co.uk> for
Miss Barbell Productions, L<http://www.missbarbell.co.uk/>

=head1 COPYRIGHT & LICENSE

  Copyright (C) 2002-2014 Barbie for Miss Barbell Productions
  All Rights Reserved.

  This module is free software; you can redistribute it and/or
  modify it under the Artistic License 2.0.

=cut