package MySQL::Admin::GUI;
use strict;
use warnings;
use utf8;
use DBI::Library::Database qw(:all);
use HTML::Menu::Pages;
use MySQL::Admin qw(:all :cgi-lib);
use HTML::Entities;
use User::pwent;
use MySQL::Admin::Settings;
use MySQL::Admin::Translate;
use MySQL::Admin::Config;
use MySQL::Admin::Session;
use HTML::Menu::TreeView qw(:all);
use HTML::Editor::Markdown;
use CGI::QuickForm;
use HTML::Editor;
use Encode;
use Fcntl qw(:flock);
use Symbol;
use URI::Escape;

#use diagnostics;
use Authen::Captcha;
use JSON::XS;
use XML::Simple;
use CGI::Carp qw(fatalsToBrowser);
require Exporter;
use vars qw(
  $m_outXML
  $m_sJson
  $m_bMainTemplate
  $DefaultClass
  $ACCEPT_LANGUAGE
  @EXPORT @ISA
  $m_bMod_perl
  %m_hUniq
  $m_hrParams
  $m_hrSettings
  $m_hrLng
  $m_nStart
  $m_nEnd
  $m_nRight
  $m_sAction
  $m_sSid
  $m_sStyle
  $m_sTitle
  $m_sUser
  $m_sCurrentDb
  $m_sCurrentHost
  $m_sCurrentUser
  $m_sCurrentPass
  $m_nSkipCaptch
);
@MySQL::Admin::GUI::EXPORT  = qw( ContentHeader Body ChangeDb Unique openFile action applyRights maxlength);
@ISA                        = qw( Exporter MySQL::Admin );
$MySQL::Admin::GUI::VERSION = '1.16';
$m_bMod_perl                = ( $ENV{MOD_PERL} ) ? 1 : 0;
local $^W = 0;
our @m_processlist;
our $m_bFirstTime = 0;
our $m_oDatabase;
our $m_dbh;

=head1 NAME

MySQL::Admin::GUI - Just a MySQL administration Web-App

=head1 SYNOPSIS

use MySQL::Admin::GUI;

ContentHeader("config/settings.pl");

print Body();

=head2 EXPORT

  action Body maxlength openFile

=cut

=head2 ContentHeader

    ContentHeader("/path/to/your/settings.pl");

=cut

sub ContentHeader {
    my $m_hrSettingsfile = shift;
    init($m_hrSettingsfile) unless $m_bMod_perl and $m_bFirstTime;
    $m_nSkipCaptch = 0;
    $m_bMainTemplate = param('m_blogin') eq 'true' ? 1 : 0;

    #todo klären wiso m_hrLng durch init nicht gesetzt wird.
    *m_hrLng     = \$MySQL::Admin::Translate::lang;
    $m_oDatabase = new DBI::Library::Database();
    $m_oDatabase->serverName( $m_hrSettings->{cgi}{serverName} );
    $m_oDatabase->floodtime( $m_hrSettings->{floodtime} );
    $m_dbh = $m_oDatabase->initDB(
        {
            name     => $m_hrSettings->{database}{name},
            host     => $m_hrSettings->{database}{host},
            user     => $m_hrSettings->{database}{user},
            password => $m_hrSettings->{database}{password},
        }
    ) unless $m_bFirstTime;
    $m_bFirstTime = 1;
    $m_sAction = param('action') ? param('action') : $m_hrSettings->{defaultAction};
    translate($m_sAction);
    $m_sAction = ( $m_sAction =~ /^(\w{3,50})$/ ) ? $1 : $m_hrSettings->{defaultAction};
    $m_sJson = {
        m_sCurrentAction => $m_sAction,
        m_nSize          => $m_hrSettings->{size},
        m_nRight         => $m_nRight,
        htmlright        => $m_hrSettings->{htmlright},
        style            => $m_hrSettings->{cgi}{style},
    };
    $m_sCurrentHost = param('m_shost') ? param('m_shost') : $m_hrSettings->{database}{CurrentHost};
    $m_sCurrentUser = param('m_suser') ? param('m_suser') : $m_hrSettings->{database}{CurrentUser};
    $m_sCurrentPass = param('m_spass') ? param('m_spass') : $m_hrSettings->{database}{CurrentPass};

    if ( param('ChangeCurrentDb') ) {
        $m_sCurrentDb                          = param('ChangeCurrentDb');
        $m_hrSettings->{database}{CurrentDb}   = $m_sCurrentDb;
        $m_hrSettings->{database}{CurrentHost} = $m_sCurrentHost;
        $m_hrSettings->{database}{CurrentUser} = $m_sCurrentUser;
        $m_hrSettings->{database}{CurrentPass} = $m_sCurrentPass;
        $m_sAction                             = 'ShowTables';
        saveSettings($m_hrSettingsfile);
    } else {
        $m_sCurrentDb = $m_hrSettings->{database}{CurrentDb};
    } ## end else [ if ( param('ChangeCurrentDb'...))]
    my $cookiepath = $m_hrSettings->{cgi}{cookiePath};
    $m_sSid =
        defined cookie( -name => 'sid' ) ? cookie( -name => 'sid' )
      : defined param('sid') ? param('sid')
      : '123'
      unless $m_sAction eq 'logout';
    $m_sUser = $m_oDatabase->getName($m_sSid);
    $m_sUser = defined $m_sUser ? $m_sUser : 'guest';
    my @aCookies;
    if ( $m_sAction eq 'logout' or ( $m_sUser eq 'guest' and $m_sAction ne 'login' ) ) {
        my $cookie = cookie(
            -name    => 'sid',
            -value   => '',
            -expires => '-1d',
            -path    => "$cookiepath"
        );
        push @aCookies, $cookie;
        print header(
            -type                        => 'text/xml',
            -charset                     => 'UTF-8',
            -access_control_allow_origin => '*',
            -cookie                      => [@aCookies]
        );
        $m_sUser   = 'guest';
        $m_sSid    = '123';
        $m_sAction = $m_bMainTemplate ? $m_hrSettings->{defaultAction} : 'news'
          if ( $m_sAction eq 'logout' );
    } elsif ( $m_sAction eq 'login' ) {
        my $ip = remote_addr();
        my $u  = param('user');
        my $p  = param('pass');
        $m_nSkipCaptch = 0;
        eval {
            my $captcha = Authen::Captcha->new(
                data_folder   => "$m_hrSettings->{cgi}{bin}/config/",
                output_folder => "$m_hrSettings->{cgi}{DocumentRoot}/images"
            );
            $m_nSkipCaptch = $captcha->check_code( param("captcha"), param("md5") );
        };
        $m_nSkipCaptch = 1 if $@;
        if ( defined $u && defined $p && defined $ip && $m_nSkipCaptch > 0 ) {
            use MD5;
            my $md5 = new MD5;
            $md5->add($u);
            $md5->add($p);
            my $cyrptpass = $md5->hexdigest();
            my $result    = 1;
            if ( $m_oDatabase->checkPass( $u, $cyrptpass ) ) {
                $m_nSkipCaptch = 2;
                $m_sSid = $m_oDatabase->setSid( $u, $p, $ip );
                my $cookie = cookie(
                    -name    => 'sid',
                    -value   => "$m_sSid",
                    -path    => "$cookiepath",
                    -expires => '+1y'
                );
                push @aCookies, $cookie if $result eq 1;
                print header(
                    -type                        => 'text/xml',
                    -access_control_allow_origin => '*',
                    -charset                     => 'UTF-8',
                    -cookie                      => [@aCookies]
                );
                $m_sUser = $u;
                $m_sAction = $m_bMainTemplate ? $m_hrSettings->{defaultAction} : 'news';
            } else {
                print header(
                    -type                        => 'text/xml',
                    -access_control_allow_origin => '*',
                    -charset                     => 'UTF-8',
                );
            } ## end else [ if ( $m_oDatabase->checkPass...)]
        } else {
            print header(
                -type                        => 'text/xml',
                -access_control_allow_origin => '*',
                -charset                     => 'UTF-8',
            );
        } ## end else [ if ( defined $u && defined...)]
    } else {
        print header(
            -type                        => 'text/xml',
            -access_control_allow_origin => '*',
            -charset                     => 'UTF-8'
        );
    } ## end else [ if ( $m_sAction eq 'logout'...)]
    my $ip = remote_addr();
    $m_nRight = $m_oDatabase->userright($m_sUser);
    print qq(<?xml version="1.0" encoding="UTF-8"?>\n<xml>\n\n);
    $m_sJson->{m_sSid} = $m_sSid;
    my $encode_json = encode_json $m_sJson;
    print qq|<output id="sid"><![CDATA[
    <script>
    m_sJson   = '$encode_json';
    m_sid     = "$m_sSid";
    cAction   = "$m_sAction";
    size      = $m_hrSettings->{size};
    right     = $m_nRight;
    htmlright = $m_hrSettings->{htmlright};
    style     = "$m_hrSettings->{cgi}{style}";
	m_oSettings = JSON.parse(m_sJson);
    </script>]]></output>|;
} ## end sub ContentHeader

=head2 Body

  print Body();

=cut

sub Body {
    CGI::upload_hook( \&hook );
    $m_sStyle = $m_hrSettings->{cgi}{style};
    $m_nStart = param('von') ? param('von') : 0;
    $m_nStart = ( $m_nStart =~ /^(\d+)$/ ) ? $1 : 0;
    $m_nEnd   = param('bis') ? param('bis') : 30;
    $m_nEnd   = ( $m_nEnd =~ /^(\d+)$/ ) ? $1 : 0;
    if ( $m_nStart < 0 ) {
        $m_sAction = 'exploit';
        $m_nStart  = 0;
    } ## end if ( $m_nStart < 0 )
    $m_nRight = defined $m_nRight ? $m_nRight : 0;
    my $exploit = 0;
    my $query   = "select * from actions join actions_set on actions.action = actions_set.foreign_action where actions_set.action = ?";
    $query .= " || actions_set.action = 'default'" if $m_bMainTemplate;
    my @action_set = $m_oDatabase->fetch_AoH( $query, $m_sAction );
    $exploit = 1 if $@;
    for ( my $i = 0 ; $i <= $#action_set ; $i++ ) {
        my $action = $action_set[$i];
        $m_sTitle = $action->{title};
        if ( $m_nRight >= $action->{right} ) {
            if ( $action->{type} eq 'xml' ) {
                print qq|<output id="$action->{output_id}" type="xml"  xsl="$action->{xsl}"><![CDATA[|;
                do("$m_hrSettings->{cgi}{bin}/Content/$action->{file}") if -e "$m_hrSettings->{cgi}{bin}/Content/$action->{file}";
                eval( $action->{sub} ) if $action->{sub} ne 'main';
                print XMLout( $m_outXML, NoAttr => 1, RootName => 'action' );
                print q|]]></output>|;
            } else {
                print qq|<output id="$action->{output_id}" type="html"><![CDATA[|;
                do("$m_hrSettings->{cgi}{bin}/Content/$action->{file}")
                  if -e "$m_hrSettings->{cgi}{bin}/Content/$action->{file}";
                eval( $action->{sub} ) if $action->{sub} ne 'main';
                print q|]]></output>|;
            } ## end else [ if ( $action->{type} eq...)]
            if ($@) {
                print qq|<output id="errorMessage"><![CDATA[$@]]></output>|;
                warn "MySQL::Admin::Body $@  $/";
            } ## end if ($@)
        } else {
            $exploit = 1;
        } ## end else [ if ( $m_nRight >= $action...)]
    } ## end for ( my $i = 0 ; $i <=...)
    if ($exploit) {
        print qq|<output id="content"><![CDATA[|;
        do("$m_hrSettings->{cgi}{bin}/Content/exploit.pl");
        print q|]]></output>|;
        if ($@) {
            print qq|<output id="errorMessage"><![CDATA[$@]]></output>|;
            warn "MySQL::Admin::Body $@  $/";
        } ## end if ($@)
    } ## end if ($exploit)
    my $encode_json = encode_json $m_sJson;
    $encode_json =~ s/\\/\\\\/g;
    $encode_json =~ s/'/\\'/g;
    print qq|<output id="postScript" type="script"><![CDATA[
	var oFile =  JSON.parse('$encode_json');
	showEditor(oFile.m_sFile);
    ]]></output>|;
    ChangeDb(
        {
            name     => $m_hrSettings->{database}{name},
            host     => $m_hrSettings->{database}{host},
            user     => $m_hrSettings->{database}{user},
            password => $m_hrSettings->{database}{password},
        }
    );
    print '</xml>';
} ## end sub Body

=head2 ChangeDb

    my %db = {

	    name => '',

	    host   => '',

	    user => '',

	    password => '',

    };

    ChangeDb(\%db);

=cut

sub ChangeDb {
    my $hash = shift;
    my $m_sTitle = defined $hash->{title} ? $hash->{title} : "";
    $m_dbh = $m_oDatabase->initDB(
        {
            name     => $hash->{name},
            host     => $hash->{host},
            user     => $hash->{user},
            password => $hash->{password},
        }
    );
} ## end sub ChangeDb

=head2 Unique()

  $unique =Unique();

=cut

sub Unique {
    my $unic;
    do { $unic = int( rand(1000000) ); } while ( defined $m_hUniq{$unic} );
    $m_hUniq{$unic} = 1;
    return $unic;
} ## end sub Unique

=head2 openFile

   my $file = openFile("filename");

=cut

sub openFile {
    my $file = shift;
    if ( -e $file ) {
        use Fcntl qw(:flock);
        use Symbol;
        my $fh = gensym;
        open $fh, "<:encoding(UTF-8)", $file or warn "$!: $file $/";
        seek $fh, 0, 0;
        my $lines;
        while ( my $line = <$fh> ) {
            $lines .= $line;
        } ## end while ( my $line = <$fh> )
        close $fh;
        return $lines;
    } else {
        warn "file exestiert nicht $/";
    } ## end else [ if ( -e $file ) ]
} ## end sub openFile

=head2 applyRights()

updates the user rights to a treeviewLink

        applyRights(\@tree);

=cut

sub applyRights {
    my $t = shift;
    for ( my $i = 0 ; $i < @$t ; $i++ ) {
        my $r = @$t[$i]->{right} ? @$t[$i]->{right} : 0;
        if ( $r > $m_nRight ) {
            undef @$t[$i];
        } elsif ( ref @$t[$i]->{subtree}[0] eq "HASH" ) {
            applyRights( \@{ @$t[$i]->{subtree} } );
        } ## end elsif ( ref @$t[$i]->{subtree...})
    } ## end for ( my $i = 0 ; $i < ...)
} ## end sub applyRights

=head2 action

        my %action = {

                title => '',

                src   => 'ea67',#webfont

                location => '',#url

                style => 'optional',

        };

        print action{\%action);

=cut

sub action {
    my $hash     = shift;
    my $title    = defined $hash->{title} ? $hash->{title} : "";
    my $src      = defined $hash->{src} ? $hash->{src} : "";
    my $location = defined $hash->{location} ? $hash->{location} : "";
    if ( $location !~ /^javascript:/ ) {
        $location = "requestURI('$location',this.id,'$title')";
    } ## end if ( $location !~ /^javascript:/)
    return qq(<a onclick="$location" class="batch">&#x$src;</a><a class="link" href="javascript:$location" >$title</a> );
} ## end sub action

=head2  maxlength()
     
     maxlength($length ,\$text);

=cut

sub maxlength {
    my $maxWidth = shift;
    ++$maxWidth;
    my $txt = shift;
    if ( length($$txt) > $maxWidth ) {
        my $maxLength = $maxWidth;
        my $i++;
        while ( $i < length($$txt) ) {
            if ( substr( $$txt, $i, 1 ) eq "<" ) {
                $maxLength = $maxWidth;
                do { $i++ } while ( substr( $$txt, $i, 1 ) ne ">" and $i < length($$txt) );
            } ## end if ( substr( $$txt, $i...))
            if ( substr( $$txt, $i, 1 ) eq "&" ) {
                $maxLength = $maxWidth;
                do { $i++ } while ( substr( $$txt, $i, 1 ) ne ";" and $i < length($$txt) );
            } ## end if ( substr( $$txt, $i...))
            $maxLength = ( substr( $$txt, $i, 1 ) =~ /\S/ ) ? --$maxLength : $maxWidth;
            if ( $maxLength eq 0 ) {
                substr( $$txt, $i, 1 ) = " ";
                $maxLength = $maxWidth;
            } ## end if ( $maxLength eq 0 )
            $i++;
        } ## end while ( $i < length($$txt...))
    } ## end if ( length($$txt) > $maxWidth)
} ## end sub maxlength

=head1 SEE ALSO

L<CGI> L<MySQL::Admin>
L<DBI> L<DBI::Library> L<DBI::Library::Database>
L<MySQL::Admin::Main> L<HTML::TabWidget>  L<HTML::Menu::Pages>


=head1 AUTHOR

Dirk Lindner <lze@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005-2018 by Hr. Dirk Lindner

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation;
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

=cut
1;
