#!/bin/bash
# -*- coding:utf-8 -*-
# file KLEIDER/web/src/pinw/pinw_setup
# 2020-05-23 Herbert Schiemann <h.schiemann@herbaer.de>
# Pinwand-Darstellung einrichten
# 2020-11-19 check_command
# 2020-11-23 bugfix check_infile statt check_infiles

# Zähler, Variable, Aktionen
declare_vars ()
{
   # Ein Leerzeichen als Wert bedeutet, dass Positionsargumente verarbeitet werden
   _argv="";

   # Suchpfad für rc-Dateien, : - getrennte Liste von Verzeichnispfaden
   # Falls leer, wird die Option --rc nicht speziell behandelt
   g_configpath= ;

   # Zähler
   g_counters="  \
      verbose    \
      overwrite  \
      websrv     \
      keeptmp    ";

   # Variable
   g_variables=" \
      base       \
      webbase    \
      srcbase    \
      docroot    \
      secrets    \
      tempdir    " ;

   # Aktionen
   g_actions="  \
      xslt      \
      xhtml     \
      cgi       \
      htaccess  \
      htpasswd  \
      freexsl   \
      db        \
      dbtables  \
      dbuser    \
      upload    " ;

  has_actions=0 ;
} # declare_vars

# Default-Werte setzen
set_defaults () {
   local b=$(realpath $0);
   b=${b%/web/src/*};
   no_db=1;
   no_dbtables=1;
   no_dbuser=1;
   no_upload=1;
   [[ -n $verbose       ]] || verbose=1;
   [[ -n $overwrite     ]] || overwrite=0;
   [[ -n $websrv        ]] || websrv=0;
   [[ -n $base          ]] || base=$b ;
   [[ -n $webbase       ]] || webbase=$base/web ;
   [[ -n $srcbase       ]] || srcbase=$webbase/src ;
   [[ -n $docroot       ]] || docroot=$webbase/docroot ;
   [[ -n $secrets       ]] || secrets=$webbase/secrets ;
   [[ -n $tempdir       ]] || tempdir=$webbase/temp ;
   tempdir=$tempdir/$(date +%Y%m%d%H%M%S%N) ;
} # set_defaults

# Variable und Zähler initialisieren
init_vars () {
   local v;
   declare_vars ;
   for v in $g_counters $g_variables $g_actions; do
      eval "$v=" ;
   done;
} # init_vars

# is_secure path/to/file
# Ist der Dateipfad sicher, d.h
# - Ist die Datei eine gewöhnliche Datei, lesbar und nicht leer?
# - Ist die Datei nicht im Wurzelverzeichnis?
# - Hat nur der Besitzer mehr als nur Leserecht für die Datei?
# - Hat nur der Besitzer Schreibrecht für das Verzeichnis?
# - Ist der Besitzer der Datei auch der Besitzer des Verzeichnisses?
is_secure ()
{
   local chk ;
   (( verbose )) && echo "prüfe Sicherheit $1" ;
   [[ -f "$1" && -r "$1" && -s "$1" ]] || return 1 ;
   [[ $(stat --format=%A "$1") =~ ^.{4}[r-]{6}$ ]] || return 1 ;
   chk=${1%/*};
   [[ -n "$chk" ]] || return 1;
   [[ "$chk" != "$1" ]] || chk=$(pwd);
   [[ -d "$chk" ]] || return 1 ;
   [[ $(stat --format=%u "$chk") == $(stat --format=%u "$1") ]] || return 1;
   if [[ $(stat --format=%A "$chk") =~ ^.{4}[rx-]{6}$ ]]; then
      (( verbose )) && echo "Datei $1 scheint sicher";
      return 0;
   fi;
   return 1;
} # is_secure

# Argumente verarbeiten
read_args ()
{
   local wd ;
   local lastwd ;
   local var ;
   local ok ;

   has_actions=0 ;
   for wd in "$@"; do
      if [[ "$lastwd" = "--" ]]; then
          _argv="$_argv $wd";
      elif [[ -n "$lastwd" ]]; then
         if [[ "$wd" =~ ^[\ a-zA-Z0-9./_#-]+$ ]]; then
            if [[ "$lastwd" == "rc" && -n "$g_configpath" ]]; then
               if ! read_configuration $wd; then
                  (( verbose )) && echo "Kann Konfiguration $wd nicht lesen" ;
                  exit 10 ;
               fi ;
            else
               ok=0 ;
               for var in $g_variables; do
                  if [[ "$var" == "$lastwd" ]]; then
                     (( ++ok )) ;
                     eval "$var=\"$wd\"" ;
                     break ;
                  fi ;
               done ;
               if (( ! ok )); then
                  (( verbose )) && echo "Unbekannte Option --$lastwd $wd" ;
                  exit 11 ;
               fi ;
            fi ;
         else
            (( verbose )) && echo "Ungültiger Optionswert --$lastwd $wd" ;
            exit 12;
         fi;
         lastwd= ;
      else
         case "$wd" in
            --version )
               show_version ;
               exit 0 ;
               ;;
            --help )
               show_version ;
               show_help ;
               exit 0 ;
               ;;
            -- )
               if [[ -n "$_argv" ]]; then
                  lastwd=--;
                  continue;
               else
                  (( verbose )) && echo "Ungültige Option $wd" ;
                  exit 13 ;
               fi ;
               ;;
            --* )
               if [[ "$wd" =~ ^--[a-z][a-z0-9_]*$ ]]; then
                  lastwd=${wd#--} ;
                  ok=0 ;
                  for var in $g_counters ; do
                     if   [[ "$lastwd" == $var    ]]; then
                        eval "(( ++$lastwd ))" ;
                     elif [[ "$lastwd" == "no_$var" ]]; then
                        eval "${lastwd#no_}=0" ;
                     else
                        continue;
                     fi;
                     (( ++ok )) ;
                     break ;
                  done;
                  if (( !ok )); then
                     for var in $g_actions; do
                        if [[ "$lastwd" == "$var" ]]; then
                           eval "(( ++$var ))" ;
                           (( ++ok ));
                           has_actions=1;
                           break;
                        elif [[ "$lastwd" == "no_$var" ]]; then
                           eval "(( ++no_$var ))" ;
                           (( ++ok ));
                           break;
                        fi;
                     done;
                  fi;
                  (( ok )) && lastwd=;
               else
                  (( verbose )) && echo "Ungültige Option $wd" ;
                  exit 14 ;
               fi ;
               ;;
            * )
               if [[ -n $_argv ]]; then
                   _argv="$_argv $wd";
               else
                   (( verbose )) && echo "Ungültige Option $wd" ;
                   exit 15 ;
               fi;
               ;;
         esac ;
      fi ;
   done ;
   if [[ -n $lastwd && "$lastwd" != "--" ]]; then
      (( verbose )) && echo "Unverarbeitete Option --$lastwd";
      exit 16 ;
   fi ;
} # read_args

# Aktionen ausführen
run_actions ()
{
   local act ;
   for act in $g_actions; do
     eval "(( ! has_actions && ! no_$act || $act )) && process_$act";
   done;
} # run_actions

# Werte der Variablen anzeigen
show_variables ()
{
   local v ;
   for v in $g_counters $g_variables $g_actions $1; do
      eval "echo \"$v = \$$v\"" ;
   done;
} # show_variables

# Zeigt eine kurze Hilfe an
show_help ()
{
   local cmd=${0#*/} ;
   set_defaults ;
   cat << .HELP ;
$cmd --version
$cmd --help
$cmd [Option]*
--[no_]verbose         Ablauf nach stdout ausgeben ($verbose)?
--[no_]overwrite       Existierende Dateien überschreiben ($overwrite)?
--[no_]websrv          htaccess-Dateien für den Webserver ($websrv)?
--base     BASE        Datenträger-Verzeichnis 
                       ($base)
--webbase  WEBBASE     Web-Basisverzeichnis
                       ($webbase)
--srcbase  SRCBASE     übergeordnetes Quell-Verzeichnis
                       ($srcbase)
--docroot  DOCROOT     Wurzel-Verzeichnis der Website
                       ($docroot)
--secrets  SECRETS     Pfad der Geheimnis-Datei
                       ($secrets)
--tempdir  TEMPDIR     Verzeichnis für temporäre Dateien
                       ($tempdir)
--[no_]keeptmp         Temporäre Dateien behalten ($keeptmp)?
--xslt                 Datei pinw.xslt einrichten
--xhtml                XHTML-Dateien einrichten
--cgi                  CGI-Skripte
--htaccess             htaccess-Dateien anlegen
--htpasswd             htpasswd-Datei anlegen
--freexsl              freistehende XSLT-Dateien
--db                   Lokale Datenbank anlegen
--dbtables             Lokalen Datenbank-Tabellen anlegen
--dbuser               Lokalen Datenbank-Nutzer anlegen
--upload               erstellte Dateien hochladen
.HELP
} # show_help

# Zeigt die Version an
show_version ()
{
   cat << .VERSION ;
pinw_setup 20200528
Website kleider.herbaer.de: Pinwand-Darstellung einrichten
2020-09-04, Herbert Schiemann, h.schiemann@herbaer.de
GPL Version 2 oder neuer
.VERSION
} # show_version

# ist ein Befehl verfügbar?
# check_command xsltproc sed
check_command ()
{
    local f ;
    for f in "$@"; do
        if [[ -z "$(which $f)" ]]; then
            (( verbose )) && echo "Befehl $f ist nicht verfügbar.";
            return 1;
        fi;
    done;
} # check_command

# Kann die Ausgabedatei erstellt werden?
check_outfile ()
{
   local fp=$1;
   local dir;
   local verb;
   (( verbose )) && verb=--verbose ;
   if [[ ! -e $fp ]]; then
      dir=${fp%/*};
      if [[ -n $dir && ! -e $dir ]]; then
         mkdir -p $verb $dir ;
         if [[ ! -d $dir ]]; then
            (( verbose )) && echo "$dir ist kein Verzeichnis";
            return 1;
         fi;
      fi;
   fi;

   if [[ -d $fp ]]; then
      (( verbose )) && echo "$fp ist ein Verzeichnis";
      return 1;
   elif [[ -d $fp. ]]; then
      (( verbose )) && echo "$fp. ist ein Verzeichnis";
      return 1;
   elif (( overwrite )); then
      if [[ -e $fp ]]; then
         (( verbose )) && echo "lösche $fp";
         rm $fp;
      fi;
      if [[ -e $fp. ]]; then
         (( verbose )) && echo "lösche $fp.";
         rm $fp.;
      fi;
   else
      if [[ -e $fp ]]; then
         (( verbose )) && echo "$fp existiert";
         return 1;
      fi;
      if [[ -e $fp. ]]; then
         (( verbose )) && echo "$fp. existiert";
         return 1;
      fi;
   fi;
   (( verbose )) && echo "$fp";
   return 0;
} # check_outfile

# Sind Dateien lesbar und nicht leer?
# check_infile file1 file2 ...
check_infile ()
{
   local f ;
   for f in "$@"; do
      if [[ ! -f "$f" ]]; then
         (( verbose )) && echo "Datei $f existiert nicht";
         return 1;
      fi;
      if [[ ! -s "$f" ]]; then
         (( verbose )) && echo "Datei $f ist leer";
         return 1;
      fi;
      if [[ ! -r "$f" ]]; then
         (( verbose )) && echo "Datei $f kann nicht gelesen werden";
         return 1;
      fi;
   done;
   return 0;
} # check_infile

# Sind die Dateien ausführbar?
# check_executeable first/path/to/script path/to/second_srcipt ;
check_executeable ()
{
   local f ;
   for f in "$@"; do
      if [[ ! -f "$f" ]]; then
         (( verbose )) && echo "$f\" ist keine gewöhnliche Datei";
         return 1;
      fi;
      if [[ ! -x "$f" ]]; then
         (( verbose )) && echo "$f\" ist keine ausführbare Datei";
         return 1;
      fi;
   done;
   return 0;
} # check_executeable

# gzip-komprimierte Datei(en) hinzufügen
add_gzip ()
{
   local f;
   for f in "$@"; do
      [[ -f $f ]] || continue;
      [[ -f $f.gz ]] && rm $f.gz;
      [[ -e $f.gz ]] && continue;
      (( verbose )) && echo "erstelle $f.gz";
      gzip --best --stdout $f > $f.gz ;
      (( verbose )) && echo "umbennen $f -> $f.";
      mv $f $f.;
   done ;
} # add_gzip

# Hilfsfunktion: temporäre Javascript und CSS-Dateien erzeugen
# proc_tempfiles subdir
proc_tempfiles ()
{
   local td=$1 ; # Verzeichis der Zwischendateien
   local s;      # Quelldatei
   local o;      # Ausgabedatei
   local i=$srcbase/sitestyle ;
   local p=$srcbase/pinw ;
   check_executeable $i/clean_js.pl $i/clean_css.pl $p/js_conditional.pl || return 1;
   for s in $p/*.js ; do
      [[ -f $s ]] || continue;
      o=$td/${s##*/} ;
      check_outfile $o || continue;
      $p/js_conditional.pl < $s \
      | $i/clean_js.pl --in - --out $o ;
   done;
   for s in $p/*.css ; do
      [[ -f $s ]] || continue;
      o=$td/${s##*/} ;
      check_outfile $o || continue;
      $i/clean_css.pl --in $s --out $o ;
   done;
} # proc_tempfiles

# Hilfsfunktion: Datei mit kurzen Text-Schlüsseln
# proc_shortids subdir
proc_shortids ()
{
   local sd=$1;
   (( verbose )) && echo "proc_shortids $1";
   local p=$srcbase/sitestyle/shortids.pl ;
   local t=$srcbase/sitestyle/localization_idlist.xslt ;
   local ids=$sd/shortids.xml ;
   local loc=$srcbase/sitestyle/local.xml.de ;
   check_executeable $p || return 1 ;
   check_infile $t $loc || return 1 ;
   check_outfile $ids   || return 1 ;
   xsltproc $t $loc | $p > $ids;
   return 0 ;
} # proc_shortids

# XSLT-Dateien
process_xslt ()
{
   local s ;                # Pfad einer XSLT-Datei (Quelle)
   local n ;                # Basis-Name ohne Verzeichnispfad oder Suffix
   local a ;                # Dateinamenssuffix '.xslt' oder '.stub'
   local o=$docroot/style ; # Zielverzeichnis $docroot/style
   [[ -L $o ]] && return ;
   local o2;                # Zielpfad
   local i=$srcbase/sitestyle ;
   local p=$srcbase/pinw ;
   (( verbose )) && echo "process_xslt";
   local t=$tempdir/upload ;
   [[ -f $t ]] || check_outfile $t || return;
   local td="$tempdir/files" ;
   proc_tempfiles $td ;
   proc_shortids  $td  || return ;
   for n in pinw values ;
   do
      for a in .xslt .stub ;
      do
         s=$p/$n$a ;
         check_infile $s || continue;
         o2=$o/$n$a ;
         check_outfile $o2 || continue;
         xsltproc                                         \
            --stringparam p_tmpprefix $td/                \
            --stringparam p_shortids $td/shortids.xml     \
            $i/styleincl_step_1.xslt $s                   \
         | xsltproc --xinclude $i/styleincl_step_2.xslt - \
         > $o2                                            ;
         add_gzip $o2 ;
         echo "put ${o2#$docroot/}."   >> $t;
         echo "put ${o2#$docroot/}.gz" >> $t;
      done;
   done;
   (( keeptmp )) || rm --recursive $td ;
} # process_xslt

# XHTML-Dateien
process_xhtml ()
{
   (( verbose )) && echo "process_xhtml";
   local p=$srcbase/pinw ;
   local d=$p/pival.pl ;
   local e=$p/rm_installpi.xslt ;
   local i=$srcbase/sitestyle ;
   local t0=$i/localization_repltext.xslt ;
   local t1=$i/help_step_1.xslt;
   local t2=$i/help_step_2.xslt;
   local r=$i/rmxmlns.pl ;
   check_executeable $d $r     || return;
   check_infile $e $t0 $t1 $t2 || return;
   local t=$tempdir/upload ;
   [[ -f $t ]] || check_outfile $t || return;
   local s;  # Quelle
   local o;  # Ziel
   local l;  # Sprache
   local td="$tempdir/files" ;
   proc_tempfiles $td ;
   for s in $p/*.xhtml*; do
      [[ $s =~ ~$ ]] && continue;
      o=$($d < $s);
      [[ -n "$o" ]] || continue;
      [[ $o =~ ^/ ]] || o=$docroot/$o;
      check_outfile $o || continue;
      o=$(realpath $o);
      if [[ $s =~ \.xhtml\.([a-z]+)$ ]]; then
         l=${BASH_REMATCH[1]};
      else
         l=de ;
      fi;
      check_infile $i/local.xml.$l || continue;
      xsltproc $e $s                                  \
      | xsltproc                                      \
         --stringparam p_local $i/local.xml.$l        \
         $t0 -                                        \
      | xsltproc --stringparam p_tmpprefix $td/ $t1 - \
      | xsltproc --xinclude $t2 -                     \
      | $r > $o                                       ;
      add_gzip $o;
      echo "put ${o#$docroot/}."   >> $t;
      echo "put ${o#$docroot/}.gz" >> $t;
   done;
} # process_xhtml

# CGI-Skripte
process_cgi ()
{
   (( verbose )) && echo "process_cgi";
   local b=$srcbase/pinw;
   check_infile $secrets || return;
   local r=$srcbase/localization/replace.pl;
   local c=$b/clean_pl.pl;
   local p=$b/pival.pl
   check_executeable $r $c $p || return;
   local d;
   local o;
   local t=$tempdir/upload ;
   [[ -f $t ]] || check_outfile $t || return;
   local f;
   for f in $b/*.cgs; do
      o=$($p < $f);
      (( verbose )) && echo "$f -> $o";
      if [[ -z "$o" ]]; then
         (( verbose )) && echo "Zielpfad nicht angegeben";
         o=${f#$b};
         o=${o%.cgs};
      fi;
      o=$docroot/$o;
      check_outfile $o || continue;
      $c < $f | $r --val $secrets > $o;
      chmod +x $o;
      echo "put ${o#$docroot/}" >> $t;
   done;
} # process_cgi

# htaccess - Dateien
process_htaccess ()
{
   (( verbose )) && echo "process_htaccess";
   local so=$overwrite ;
   overwrite=1 ;
   local o;  # Ziel-Datei
   local s=$webbase/secrets;
   local d=$srcbase/pinw/pival.pl ;
   local r=$srcbase/localization/replace.pl;
   local c=$srcbase/sitestyle/clean_config.pl;
   if ! check_executeable $d $r $c; then
      overwrite=$so;
      return;
   fi;
   local t=$tempdir/upload ;
   if ! [[ -f $t ]] && ! check_outfile $t; then
      overwrite=$so;
      return;
   fi;
   local f;
   local w= ;
   (( websrv )) && w="--var web";
   for f in $srcbase/pinw/*htaccess; do
      o=$($d < $f);
      [[ -n "$o" ]] || continue;
      [[ $o =~ ^/ ]] || o=$docroot/$o;
      check_outfile $o || continue;
      o=$(realpath $o);
      $r --val $s $w < $f | $c > $o;
      if (( websrv )) && [[ $o =~ ^$docroot ]]; then
         echo "put ${o#$docroot/}" >> $t;
      fi;
   done;
   overwrite=$so;
} # process_htaccess

# htpasswd - Datei
process_htpasswd ()
{
   (( verbose )) && echo "process_htpasswd";
   local htpw=$(which htpasswd);
   local g=$srcbase/pinw/get_data.pl ;
   check_executeable $htpw  $g || return;
   check_infile $secrets || return;
   local pwf=$docroot/cgi-bin/private/.htpasswd ;
   check_outfile $pwf || return;
   local u=$($g --key website.login < $secrets);
   local p=$($g --key website.passwd < $secrets);
   if [[ -z $u ]]; then
      (( verbose )) && echo "website.login nicht definiert";
      return;
   fi;
   if [[ -z $p ]]; then
      (( verbose )) && echo "website.passwd nicht definiert";
      return;
   fi;
   $htpw -bc $pwf $u $p;
   check_infile $pwf || return;
   local t=$tempdir/upload ;
   [[ -f $t ]] || check_outfile $t || return;
   echo "put ${pwf#$docroot/}" >> $t;
} # process_htpasswd

# freistehende XSLT-Dateien
process_freexsl ()
{
   (( verbose )) && echo "process_freexsl";
   local p=$srcbase/pinw ;
   local d=$p/pival.pl ;
   check_executeable $d || return;
   local e=$p/rm_installpi.xslt ;
   local m=$base/pool/xslt_minimize.xslt ;
   check_infile $e $m || return;
   local t=$tempdir/upload ;
   [[ -f $t ]] || check_outfile $t || return;
   local f;
   local s;
   local o;
   for f in              \
      attrvals_pinw_xslt \
      attrvals           \
      storage_keys       \
      pinw_help_keys     ;
   do
      s=$p/$f.xslt;
      check_infile $s || continue;
      o=$($d < $s);
      [[ -n "$o" ]] || continue;
      [[ $o =~ ^/ ]] || o=$docroot/$o;
      check_outfile $o || continue;
      o=$(realpath $o);
      xsltproc $e $s       \
      | xsltproc $m - > $o ;
      add_gzip $o;
      echo "put ${o#$docroot/}."   >> $t;
      echo "put ${o#$docroot/}.gz" >> $t;
   done;
} # process_freexsl

# run_sql name (user, db)
run_sql ()
{
   local s=$srcbase/pinw/like_$1.sql ;
   local r=$srcbase/localization/replace.pl ;
   local m=$(which mysql);
   check_infile $s $secrets || return 1;
   check_executeable $r $m   || return 2;
   (( verbose )) && echo $s;
   $r --val $secrets < $s | $m ;
} # run_sql

# Lokale Datenbank anlegen
process_db ()
{
   (( verbose )) && echo "process_db";
   run_sql db ;
} # process_db

# Lokale Datenbank-Tabellen anlegen
process_dbtables ()
{
   (( verbose )) && echo "process_dbuser";
   run_sql tables ;
} # process_dbtables

# Lokalen Datenbank-Nutzer anlegen
process_dbuser ()
{
   (( verbose )) && echo "process_dbuser";
   run_sql user ;
} # process_dbuser

# Dateien hochladen
process_upload ()
{
   (( verbose )) && echo "process_upload";
   local verb;
   (( verbose )) && verb=--verbose ;
   local t=$tempdir/upload ;
   if check_infile $t && check_executeable $srcbase/localization/ftp.pl;
   then
      $srcbase/localization/ftp.pl $verb --putbase $docroot < $t ;
   fi;
   (( keeptmp )) || rm $t;
} # process_upload

# Sicherheit
# export PATH=/bin:/usr/local/bin:/usr/bin ;
IFS=$' \t\n' ;
set -o noclobber ;   # existierende Dateien werden nicht überschrieben
shopt -s extglob nullglob ;
init_vars ;
read_args "$@" ;
set_defaults ;
check_command xsltproc realpath date stat pwd which || exit 1;

(( verbose > 1 )) && show_variables ;
run_actions ;
exit 0;

# end of file KLEIDER/web/src/pinw/pinw_setup
