#!/usr/bin/perl -w
#
# Zymonic Business Process and Information Management System
# Copyright Zednax Limited 2008 -
# For Authors and Changelog see the subversion history
use strict;

# Library path
BEGIN
{
    use vars qw($location $script_name $script_location $utils);
    use Zymonic::Utils qw(clean debug death_handler debug_exception rethrow_exception deep_replace_card_numbers);
    $main::SIG{__DIE__} = \&death_handler;
}

# Load Zymonic and clear globals
use Zymonic;
$Zymonic::session = '';
$Zymonic::system  = '';

# Config Vars
my $saxon = 0;

# XML out
my $xmlout = {};

# Global objects
my $db;
my $auth;

# Modules
use Zymonic::Config;
use Zymonic::Session;
use Zymonic::DB;
use Zymonic::Auth;
use Zymonic::Locking;
use Zymonic::Messaging;
use XML::Simple;

# Clean env
$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/sbin:/usr/sbin";

$ENV{SERVER_NAME}     = clean( $ENV{SERVER_NAME},     "-_." );
$ENV{SCRIPT_NAME}     = clean( $ENV{SCRIPT_NAME},     "-_\/" );
$ENV{REMOTE_ADDR}     = clean( $ENV{REMOTE_ADDR},     "\.-_" );
$ENV{HTTP_USER_AGENT} = clean( $ENV{HTTP_USER_AGENT}, "-\/\.():;_,+\*\?!'\"%&" );
$ENV{HTTPS}           = clean( $ENV{HTTPS} );
$ENV{HTTP_X_FORWARDED_FOR} = clean( $ENV{HTTP_X_FORWARDED_FOR} );
$ENV{PATH_INFO}            = clean( $ENV{PATH_INFO}, "-_\/." );
$ENV{REQUEST_METHOD}       = clean( $ENV{REQUEST_METHOD} );

my $protocol = ( ( defined( $ENV{HTTPS} ) and $ENV{HTTPS} eq 'on' ) ? 'https' : 'http' );

%Zymonic::ENV = %ENV;

# set binmode for STDOUT and STDERR to handle unicode
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';

# setup the field factory
$Zymonic::field_factory = Zymonic::FieldFactory->new();

# enable cache for table includes and relationship permissions
$Zymonic::object_cache = { 'Zymonic::TableInclude' => {}, 'Zymonic::RelationshipPermissions' => {}, };

# reset tabindex for this request
$Zymonic::tabindex = 0;

# Create a session (CGI only)
$Zymonic::session = Zymonic::Session->new(
    ip_address      => $ENV{REMOTE_ADDR},
    x_forwarded_for => $ENV{HTTP_X_FORWARDED_FOR} || '',
    protocol        => $protocol,
    request_source  => 'cgi',
);

unless ( $Zymonic::session->https() eq 'true' )
{
    my $url = $Zymonic::session->{cgi}->url( -path_info => 1, -query => 1 );
    $url =~ s/http/https/ unless substr( $url, 0, 5 ) eq "https";
    print $Zymonic::session->redirect($url);
    debug( "Redirecting URL: " . $url );
    exit;
}
debug( "URL not needing to be redirected: " . $Zymonic::session->{cgi}->url( -path_info => 1, -query => 1 ) );

# Problem with creating session, normally caused by a request with invalid xml
if ( @{ $Zymonic::session->{ZMESSAGE} } )
{
    my $xmlhash = { session_errors => $Zymonic::session->{ZMESSAGE}, };
    my $output_response = $Zymonic::session->convert_for_output($xmlhash);

    # Produce XML response for client instead of generic system error, $output_xml_string
    # will typically contain a brief summary of the problem and the error log reference
    print $Zymonic::session->header();
    print $output_response;
}
else
{
    # Throws Error 422 Invalid or missing system name page if system name doesn't exist
    eval {
        $Zymonic::system = $Zymonic::session->system_name( $ENV{SERVER_NAME}, "/etc/zymonic" );
        1;
    }
      or do
    {
        if ( ref($@) and ref($@) =~ /Zymonic::Exception::Session::System/ )
        {
            $Zymonic::session->set_header( '-status', "422 Invalid or missing system name" );
            print $Zymonic::session->header( type => 'text/html' );
            print "<head><title>422 Invalid or missing system name</title></head>";
            print "<body><h1>Invalid or missing system name</h1>";
            print "<p>The server understands the media type of the request entity, but";
            print " was unable to process the contained instructions.</p></body>";
        }
        else
        {
            my $error = Zymonic::Utils::death_handler( $@, 'from zymonic.pl', 'return_error' );
            $Zymonic::session->set_header( '-status', "500 Internal Server Error" );
            print $Zymonic::session->header( type => 'text/html' );
            print "<head><title>500 Internal Server Error</title></head>";
            print "<body><h1>Internal Server Error</h1>";
            print "<p>The server encountered an internal error or misconfiguration and";
            print " was unable to complete your request.</p>";
            print "<p>Please contact the server administrator to inform them of the time";
            print " this error ocurred, and the actions you performed just before this";
            print " error.</p><p>More information about this error may be available in the";
            print " server error log.</p></body>";
        }
    };

    if ($Zymonic::system)
    {
        # Get any flags
        my $no_display_attributes = $Zymonic::session->no_display_attributes;

        debug("Zymonic starting... system: $Zymonic::system");

        $Zymonic::ZCONFIG{$Zymonic::system} = Zymonic::Config->new(
            system_name => $Zymonic::system,
            config_dir  => "/etc/zymonic",
            protocol    => $protocol,
        );

        my $redirect_url   = '';
        my $file_retrieval = undef;

        $db = Zymonic::DB->new( config => $Zymonic::ZCONFIG{$Zymonic::system} );
        $Zymonic::session->{DB} = $db;

        $auth = Zymonic::Auth->new(
            config      => $Zymonic::ZCONFIG{$Zymonic::system},
            session     => $Zymonic::session,
            DB          => $db,
            user        => '',
            credentials => '',
            logged_in   => '',
            ip_address  => $Zymonic::session->{ip_address}
        );

        # setup locks
        $Zymonic::locking =
          Zymonic::Locking->new( config => $Zymonic::ZCONFIG{$Zymonic::system}, auth => $auth, DB => $db, );

        # setup messaging
        $Zymonic::messaging =
          Zymonic::Messaging->new( config => $Zymonic::ZCONFIG{$Zymonic::system}, auth => $auth, DB => $db, );

        # Initialise the session fully
        $Zymonic::session->initialise_session(
            {
                config => $Zymonic::ZCONFIG{$Zymonic::system},
                auth   => $auth,
                DB     => $db
            }
        );

        # check if servside xslt is required
        $saxon = $Zymonic::session->server_side_xslt();

        # build documentation if needed
        if ( $Zymonic::session->build_documentation )
        {
            eval {
                $Zymonic::ZCONFIG{$Zymonic::system}->build_documentation($auth);
                1;
            } or do
            {

                # don't let any exceptions here bring the system down
                # debug it but ignore it
                my $exception = $@;
                if ($exception)
                {
                    if ( ref($exception) and $exception->isa('Zymonic::Exception') )
                    {
                        debug_exception($exception);
                    }
                    else
                    {
                        debug( 'Unknown Exception (or a die): ' . Dumper($exception) );
                    }
                }
            };
        }

        eval {
            $xmlout = $Zymonic::session->view_control($no_display_attributes);
            1;
        }
          or do
        {
            my $exception = $@;
            if (
                    $exception
                and ref($exception)
                and (  $exception->isa('Zymonic::Exception::State::Redirect')
                    or $exception->isa('Zymonic::Exception::Redirect') )
              )
            {
                $redirect_url = $exception->redirect_url;
            }
            elsif ( $exception and ref($exception) and $exception->isa('Zymonic::Exception::FileIO::Retrieval') )
            {
                $file_retrieval = {
                    file         => $exception->file(),
                    file_name    => $exception->file_name(),
                    content_type => $exception->content_type(),
                };
            }
            else
            {
                rethrow_exception($exception);
            }
        };

        # check if serverside xslt flag was forced
        # doing this again rather than moving the above to lessen impact
        $saxon = $Zymonic::session->server_side_xslt() if $Zymonic::session->{force_server_side_xslt};

        # check for custom xsl
        my $zz_xsl = $Zymonic::session->clean_param( 'ZZxsl', '/\\-._' );
        my $xsl = $zz_xsl || $Zymonic::session->xsl();
        my $xsl_with_path = $Zymonic::ZCONFIG{$Zymonic::system}->{htmlroot};
        unless ($xsl)
        {
            if ( ref( $xmlout->{currentpage} ) && $xmlout->{currentpage}->{xsl} )
            {
                $xsl = $xmlout->{currentpage}->{xsl};
            }
            else
            {
                $xsl = $Zymonic::ZCONFIG{$Zymonic::system}->xslt;
            }
        }

        my $language = $Zymonic::session->language;

        if (  -f $Zymonic::ZCONFIG{$Zymonic::system}->{htmlroot} . "/"
            . $language->{lang_code} . "-"
            . $language->{subset} . "/"
            . $xsl )
        {
            $xsl = "/" . $language->{lang_code} . "-" . $language->{subset} . "/" . $xsl;
        }

        if ($redirect_url)
        {
            print $Zymonic::session->header;
            print $Zymonic::session->redirect($redirect_url);
        }
        elsif ($file_retrieval)
        {
            $Zymonic::session->send_retrieved_file(
                $file_retrieval->{file},
                $file_retrieval->{content_type},
                $file_retrieval->{file_name}
            );
        }
        else
        {

            # Output the response in the correct format
            my $output_type = $Zymonic::session->output_type($xmlout) || '';
            my $output_response = $Zymonic::session->convert_for_output($xmlout);

            if (   $Zymonic::ZCONFIG{$Zymonic::system}->{debug_io_xml}
                && $Zymonic::ZCONFIG{$Zymonic::system}->{debug_io_xml} eq 'true' )
            {
                debug( "Outgoing Response: " . deep_replace_card_numbers($output_response), 'true' );
            }

            # if no ZZxsl and param to suppress XSL or API mode, then don't output XSL info
            if ( !$zz_xsl
                && ( $Zymonic::session->clean_param("notransform") or $no_display_attributes ) )
            {
                print $Zymonic::session->header;
                print $output_response;
            }
            elsif ( !$saxon )
            {
                print $Zymonic::session->header;
                print "<?xml-stylesheet href=\"" . $xsl . "\" type=\"text/xsl\"?>\n" if $output_type eq 'xml';
                print $output_response;
            }
            else
            {
                open( XT, ">/tmp/xml.$$" );
                print XT $output_response;
                close(XT);

                debug("Server Side XSLT: /usr/local/bin/saxon /tmp/xml.$$ $xsl_with_path/$xsl");
                print $Zymonic::session->header( type => 'text/html' );
                print `/usr/local/bin/saxon /tmp/xml.$$ $xsl_with_path/$xsl`;
            }
        }
    }
}

# Explicitly destroy session to trigger closing methods
$Zymonic::session = undef;
