From: kilian (dks-laptop) Date: Thu, 7 Nov 2019 08:11:36 +0000 (+0100) Subject: v20191107 X-Git-Tag: 1.0.15~91 X-Git-Url: http://cloud.dks.lu/git/?a=commitdiff_plain;h=8156604ee2961c70b336d40486a0cf1f61dac21e;p=pot_lu.git v20191107 --- 8156604ee2961c70b336d40486a0cf1f61dac21e diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2ff3f331 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +backoffice/api/lib/perl5 + diff --git a/backoffice/.htaccess b/backoffice/.htaccess new file mode 100644 index 00000000..ff153d93 --- /dev/null +++ b/backoffice/.htaccess @@ -0,0 +1,9 @@ +RewriteEngine on + +DirectoryIndex index.cgi index.html +AddHandler cgi-script .cgi +#RewriteBase / +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +RewriteRule "^(.*)$" "index.cgi" [NC,L,QSA] \ No newline at end of file diff --git a/backoffice/api/db.cgi b/backoffice/api/db.cgi new file mode 100644 index 00000000..5e529491 --- /dev/null +++ b/backoffice/api/db.cgi @@ -0,0 +1,93 @@ +#!/usr/bin/perl +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use CGI; +use CGI::Cookie; +# use CGI::Carp qw/fatalsToBrowser/; +use File::Basename; +use JSON::PP; + +use dksconfig qw/$sitecfg/; +use dksdb; + +use session; +#use sendemail; +my $cgi = new CGI(); +my $scriptpath = $cgi->url(-absolute => 1); +my $p = (); +my @params = $cgi->param(); +foreach my $pe (@params){ + $p->{$pe} = $cgi->param($pe); +} +my $html->{result} = (); +$p->{sid} = $cgi->cookie($sitecfg->{cookiename}); +my $se = session->new(); +my $sess = $se->getsession($p->{sid}); +print $cgi->header(-type=>"application/json", -charset => "utf-8"); +if ($sess == undef){ + $html->{error} = "No Authorisation"; + print JSON::PP::encode_json($html); + exit(0); +} +# $html->{p} = $p; +# $html->{sess} =$sess; +#my $datapath = $ENV{"DOCUMENT_ROOT"}.dirname(dirname($scriptpath)).'/data/'; +if (($cgi->request_method() eq "GET") || ($cgi->request_method() eq "POST")){ + + my @params = $cgi->param(); + foreach my $pp (@params){ + $p->{$pp} = $cgi->param($pp); + } + my $db = dksdb->new(); + if (exists($p->{get})){ + my $sql = "select * from vw_".$p->{get}; + if (exists($p->{fields}) ){ + $sql = "select ".$p->{fields}." from vw_".$p->{get}; + } + if (exists($p->{filter})){ + $sql .= " WHERE ".$p->{filter}.";"; + } + $html->{result}->{sqldata} = $db->dbqueryarray($sql); + } + elsif (exists($p->{set})){ + my $type = "ins"; + foreach my $x (keys(%{$p})){ + if (($x =~ /^ident_/) && ($p->{$x} ne "")){ + $type = "upd"; + last; + } + } + my $x = $p; + delete $x->{sid}; + delete $x->{set}; + my @sql = (); + if ($type eq "ins"){ + @sql = $db->create_ddl_insert($x); + }else { + @sql = $db->create_ddl_update($x); + } + if (scalar(@sql) > 0 ){ + my $rid = $db->dbquerysorted($sql[0]); + if (keys(%{$rid}) > 0 ){ + $html->{result} = $rid->{0}; + } + } + } + elsif (exists($p->{del})){ + my $x = $p; + delete $x->{sid}; + delete $x->{del}; + my @sql = $db->create_ddl_delete($x); + if (scalar(@sql) > 0 ){ + my $rid = $db->dbexec($sql[0]); + if (keys(%{$rid}) > 0 ){ + $html->{result} = $rid->{0}; + } + } + } +} +print JSON::PP::encode_json($html); +# for my $e ( keys %ENV ) { +# print "$e: $ENV{$e}
"; +# } \ No newline at end of file diff --git a/backoffice/api/file.cgi b/backoffice/api/file.cgi new file mode 100644 index 00000000..33d3bee8 --- /dev/null +++ b/backoffice/api/file.cgi @@ -0,0 +1,136 @@ +#!/usr/bin/perl + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use CGI; +use CGI::Cookie; +# use CGI::Carp qw/fatalsToBrowser/; +use File::Basename; +use File::Path qw/make_path/; +use JSON::PP; +use Image::Size; +use dksconfig qw/$sitecfg/; +use dksdb; + +use session; +#use sendemail; +my $cgi = new CGI(); +my $scriptpath = $cgi->url(-absolute => 1); +my $p = (); +my @params = $cgi->param(); +foreach my $pe (@params){ + $p->{$pe} = $cgi->param($pe); +} +# if ($sitecfg->{basepath} ne "/"){ +$sitecfg->{docroot} = $sitecfg->{docroot}.substr($sitecfg->{basepath},0,index($sitecfg->{basepath},'backoffice')-1); +# } +my $html->{result} = (); +$p->{sid} = $cgi->cookie($sitecfg->{cookiename}); +my $se = session->new(); +my $sess = $se->getsession($p->{sid}); +print $cgi->header(-type=>"application/json", -charset => "utf-8"); +if ($sess == undef){ + $html->{error} = "No Authorisation"; + print JSON::PP::encode_json($html); + exit(0); +} +$html->{docroot} = $sitecfg->{docroot}; +# $html->{basepath} = $sitecfg->{basepath}; +if (($cgi->request_method() eq "GET") || ($cgi->request_method() eq "POST")){ + + if (exists($p->{fn}) && $p->{fn} eq "list"){ + if (exists($p->{folder})){ + + $p->{folder} =~ s/\.\.\///g; + my $cpath = $sitecfg->{docroot}.'/'.$p->{folder}; + my @items = (); + my $up = (); + # if (index($p->{folder},'/') > 0){ + # $up->{type} = "dir"; + # $up->{name} = ".."; + # $up->{sitepath} = $p->{folder}; + # $up->{path} = $p->{folder}; + # $up->{mimtype} = 'directory'; + # $up->{size} = ''; + # $up->{dimension}; + # $up->{thumb} = ''; + # push (@items,$up); + # } + opendir(DIR,$cpath); + + while (my $d = readdir(DIR)){ + if ($d =~ /^\./){ next; } + if ($d eq "thb"){ next; } + my $e = (); + if (-d $cpath.'/'.$d){ + $e->{type} = "dir"; + $e->{name} = $d; + $e->{sitepath} = $p->{folder}.'/'.$d; + $e->{path} = $p->{folder}.'/'.$d; + $e->{mimtype} = 'directory'; + $e->{size} = ''; + + my $cmd = 'find "'.$cpath.'/'.$d.'" -type f | grep -v \'/thb/\' | wc -l'; + my $dim = `$cmd`; + chomp($dim); + $e->{dimension} = $dim; + + $e->{thumb} = ''; + } + if (-f $cpath.'/'.$d){ + my $cmd = 'file -i "'.$cpath.'/'.$d.'" | awk -F": " \'{ print $2 }\' | awk -F";" \'{ print $1 }\''; + my $mt = `$cmd`; + chomp($mt); + #if (($mt !~ /^image/ ) || ($mt !~ /^application\/pdf/ )) { next; } + if (($mt =~ /^image/ ) || ($mt !~ /^application\/pdf/ )) { + if (! -f $cpath.'/thb/'.$d.'.thb.png'){ + if (! -d $cpath.'/thb'){ + make_path($cpath.'/thb'); + } + system('convert -thumbnail x96 -background white -alpha remove "'.$cpath.'/'.$d.'" "'.$cpath.'/thb/'.$d.'.thb.png"'); + } + if ($mt =~ /^image/ ){ + my ($ix, $iy) = imgsize($cpath.'/'.$d); + $e->{dimension} = $ix."x".$iy; + } + } + my @st = stat($cpath.'/'.$d); + $e->{type} = "file"; + $e->{mimetype} = $mt; + my $s = $st[7]; + my $hrs =""; + if ($s > 1000000024){ + $s = $s/1024; + $s = $s/1024; + $s = $s/1024; + ($hrs) = $s =~ m/^(\d+\.\d\d).*/; + $hrs = $hrs." GB"; + } elsif ($s > 1000024){ + $s = $s/1024; + $s = $s/1024; + ($hrs) = $s =~ m/^(\d+\.\d\d).*/; + $hrs = $hrs." MB"; + } elsif ($s > 1024){ + $s = $s/1024; + ($hrs) = $s =~ m/^(\d+\.\d\d).*/; + $hrs = $hrs." KB"; + } else { + $hrs = $s." B"; + } + $e->{hrsize} = $hrs; + $e->{size} = $st[7]; + $e->{name} = $d; + $e->{sitepath} = $p->{folder}.'/'.$d; + $e->{thumb} = ''; + $e->{path} = '../../../'.$p->{folder}.'/'.$d; + } + push (@items,$e); + } + closedir(DIR); + $html->{result} = \@items; + + } + } +} +print JSON::PP::encode_json($html); diff --git a/backoffice/api/fingerprint.cgi b/backoffice/api/fingerprint.cgi new file mode 100644 index 00000000..db482dff --- /dev/null +++ b/backoffice/api/fingerprint.cgi @@ -0,0 +1,23 @@ +#!/usr/bin/perl + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use CGI; +use CGI::Cookie; +#use CGI::Carp qw/fatalsToBrowser/; +use File::Basename; +use File::Path qw/make_path/; +use JSON::PP; +use Image::Size; +use dksconfig qw/$sitecfg/; +use dksdb; + +my $cgi = new CGI(); +my $scriptpath = $cgi->url(-absolute => 1); +my $p = (); +my @params = $cgi->param(); +foreach my $pe (@params){ + $p->{$pe} = $cgi->param($pe); +} + diff --git a/backoffice/api/index.cgi b/backoffice/api/index.cgi new file mode 100644 index 00000000..833a85db --- /dev/null +++ b/backoffice/api/index.cgi @@ -0,0 +1,152 @@ +#!/usr/bin/perl +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use CGI; +use CGI::Cookie; +#use CGI::Carp qw/fatalsToBrowser/; +use File::Basename; +use JSON::PP; + +use dksconfig qw/$sitecfg/; +use dksdb; + +use session; +use sendemail; +my $cgi = new CGI(); +my $scriptpath = $cgi->url(-absolute => 1); +my $p = (); +my @params = $cgi->param(); +foreach my $pe (@params){ + $p->{$pe} = $cgi->param($pe); +} +my $html->{result} = (); +$p->{sid} = $cgi->cookie($sitecfg->{cookiename}); +my $se = session->new(); +my $sess = $se->getsession($p->{sid}); +print $cgi->header(-type=>"application/json", -charset => "utf-8"); +if ($sess == undef){ + $html->{error} = "No Authorisation"; + print JSON::PP::encode_json($html); + exit(0); +} +$html->{p} = $p; +# $html->{sess} =$sess; +#my $datapath = $ENV{"DOCUMENT_ROOT"}.dirname(dirname($scriptpath)).'/data/'; +if (($cgi->request_method() eq "GET") || ($cgi->request_method() eq "POST")){ + + my @params = $cgi->param(); + foreach my $pp (@params){ + $p->{$pp} = $cgi->param($pp); + } + + if (exists($p->{fn})){ + + my $db = dksdb->new(); + if ($p->{fn} eq "savefield"){ + # $html->{p} = $p; + $html->{result}->{ident} = $p->{ident}; + delete $p->{ident}; + delete $p->{fn}; + delete $p->{sid}; + my $retid=undef; + my $type = "upd"; + foreach my $px (keys(%{$p})){ + $html->{result}->{datafield} = $px; + if (($px =~ /\_id$/) && ($p->{$px} eq "")){ + $type = "ins"; + + } + } + my @sql = (); + if ($type eq "ins"){ + @sql = $db->create_ddl_insert($p); + } + else { + @sql = $db->create_ddl_update($p); + } + $html->{result}->{sql} = \@sql; + foreach my $s (@sql){ + $retid= $db->dbquerysorted($s); + } + $html->{result}->{id} = $retid->{0}; + #$p->{table},#$p->{field},$p->{value},$p->{id},$p->{type} + } + + elsif ($p->{fn} eq "saveform"){ + # $html->{p} = $p; + $html->{result}->{ident} = $p->{ident}; + delete $p->{ident}; + delete $p->{fn}; + delete $p->{sid}; + my $retid=undef; + my $type = "upd"; + foreach my $px (keys(%{$p})){ + $html->{result}->{datafield} = $px; + if (($px =~ /\_id$/) && ($p->{$px} eq "")){ + $type = "ins"; + delete $p->{$px}; + } + } + my @sql = (); + if ($type eq "ins"){ + @sql = $db->create_ddl_insert($p); + } + else { + @sql = $db->create_ddl_update($p); + } + # $html->{sql} = \@sql; + foreach my $s (@sql){ + #if ($type eq "ins"){ + $retid= $db->dbquerysorted($s); + #}else { + + #} + + } + $html->{result}->{id} = $retid->{0}; + #$p->{table},#$p->{field},$p->{value},$p->{id},$p->{type} + } + elsif($p->{fn} eq "deleterow"){ + delete $p->{fn}; + delete $p->{sid}; + my $retid = undef; + my @sql = $db->create_ddl_delete($p); + foreach my $s (@sql){ + $retid= $db->dbexec($s); + } + $html->{result}->{id} = $retid->{0}; + } + elsif($p->{fn} eq "savepassword"){ + if ((length($p->{pwd}) > 7) && ($p->{pwd} =~ /\d/) && ($p->{pwd} =~ /[a-z]/) && ($p->{pwd} =~ /[A-Z]/) ){ + $se->savepassword($sess->{id},$p->{pwd}); + $html->{result} = "OK"; + }else { + $html->{result} = "NOT OK"; + } + + } + elsif ($p->{fn} eq "sendmailvcode"){ + my $vcode = $se->randomstring(6); + $vcode = lc($vcode); + $db->dbexec("UPDATE users set vcode='".$vcode."' where id=".$sess->{id}.";"); + my $eml = sendemail->new(); + my $mret = $eml->sendemail('user_verification',$sess->{id},$p->{mail},{vcode => $vcode},undef); + if ($mret == 0){ + $html->{result}->{vcode} = $vcode; + } else { + $html->{result}->{vcode} = $vcode; + $html->{result}->{error} = $mret; + } + } + elsif ($p->{fn} eq "savenewemail"){ + $db->dbexec("update users set username='".$p->{email}."' where id=".$sess->{id}." and vcode='".$p->{vcode}."';"); + $html->{result} = "OK"; + } + elsif($p->{fn} eq "getsitemedia"){ + my $path = dirname(dirname($0)).'/img'; + } + } + +} +print JSON::PP::encode_json($html); diff --git a/backoffice/api/lib/dksconfig.pm b/backoffice/api/lib/dksconfig.pm new file mode 100644 index 00000000..270fff14 --- /dev/null +++ b/backoffice/api/lib/dksconfig.pm @@ -0,0 +1,28 @@ +package dksconfig; + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use lib ('./'); +use File::Basename; +use Exporter 'import'; +our @EXPORT_OK = qw($sitecfg); + +our $sitecfg ={ + cookiename => 'hourtrax', + dbtype => 'PgPP', + dsn => 'DBI:PgPP:dbname=potlu_db;host=DKS-LAPTOP.fritz.box', + #dsn => 'DBI:PgPP:dbname=solarch_db;host=sql629.your-server.de', + dbuser => 'potlu_user', + dbpassword => 'r2btTTRfuJz4whez', + page => 'index.tt', + pagename => 'index', + basepath => substr((exists($ENV{"SCRIPT_FILENAME"})?dirname($ENV{"SCRIPT_FILENAME"}):dirname($0)),length($ENV{"DOCUMENT_ROOT"})), + #datapath => substr((exists($ENV{"SCRIPT_FILENAME"})?dirname($ENV{"SCRIPT_FILENAME"}):dirname($0)),length($ENV{"DOCUMENT_ROOT"})).'/data/', + docroot => $ENV{"DOCUMENT_ROOT"}, + registration_enabled => '0', + default_group => 'users', + sitename => 'Accès - Client' +}; + +1; \ No newline at end of file diff --git a/backoffice/api/lib/dksdb.pm b/backoffice/api/lib/dksdb.pm new file mode 100644 index 00000000..2234a013 --- /dev/null +++ b/backoffice/api/lib/dksdb.pm @@ -0,0 +1,409 @@ +package dksdb; + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use lib ('./'); +BEGIN { $ENV{DBI_PUREPERL} = 2 } +use DBI; +use File::Basename; + +use Digest::SHA::PurePerl qw(sha256_hex); +use DBD::PgPP; +use URI::Encode qw(uri_encode uri_decode); +use Encode; +use dksconfig qw($sitecfg); +use Text::Unidecode; + + +sub new { + my $class = shift; + my $p = shift; + my $self = bless {}, $class; + return $self; +} + +sub securetext(){ + my $self = shift; + my $text = shift; + $text =~ s/'/''/g; + return $text; +} + +sub dbquery(){ + my $self = shift; + my $stat = shift; + # my $vw_info = shift; + my $retdata = undef; + my $dbh = DBI->connect($sitecfg->{dsn},$sitecfg->{dbuser},$sitecfg->{dbpassword},{PrintError=>0,RaiseError=>0,AutoCommit=>1}) or return $retdata->{error} = "dbquery Connection Error!".$!; + $stat = encode("utf8", $stat); + # open FILE,">>tmp/sql.log"; + # print FILE "$stat\n"; + # close FILE; + my $sth = $dbh->prepare($stat) or return $retdata->{error} = "dbquery".$dbh->errstr. "- SQL: ".$stat;; + + + $sth->execute() or return $retdata->{error} = "dbquery: ".$sth->errstr; + + my $data = $sth->fetchrow_hashref(); + foreach my $k (keys %{$data}){ + $retdata->{$k} = decode("utf-8",$data->{$k}); + } + + $sth->finish(); + $dbh->disconnect(); + + return $retdata; +} + +sub dbquerybykey(){ + my $self = shift; + my $key = shift; + my $stat = shift; + #my $retempty = shift; + my $retdata =(); + my $dbh = DBI->connect($sitecfg->{dsn},$sitecfg->{dbuser},$sitecfg->{dbpassword},{PrintError=>0,RaiseError=>0,AutoCommit=>1}) or return $retdata->{error} = "dbquery Connection Error!".$!; + # $stat = encode("utf8", $stat); + + # open FILE,">>sql.log"; + # print FILE "$stat\n"; + # close FILE; + my $sth = $dbh->prepare($stat) or return $retdata->{error} = "dbquery: ".$stat; + $sth->execute() or return $retdata->{error} = "dbquery: ".$stat; + while(my $data = $sth->fetchrow_hashref()) + { + if (exists $data->{$key}){ + foreach my $k (keys %{$data}){ + #if ($k ne $key){ + + $retdata->{$data->{$key}}{$k} =decode("utf-8",$data->{$k}); + #} + } + } + } + if (keys(%{$retdata}) == 0){ + $retdata =(); + } + $sth->finish(); + $dbh->disconnect(); + return $retdata; +} + +sub dbquerysorted(){ + my $self = shift; + my $stat = shift; + # my $vw_info = shift; + my $retdata; + my $dbh = DBI->connect($sitecfg->{dsn},$sitecfg->{dbuser},$sitecfg->{dbpassword},{PrintError=>0,RaiseError=>0,AutoCommit=>1}) or return $retdata->{error} = "dbquery Connection Error!".$!; + # $stat = encode("utf8", $stat); + # open FILE,">>tmp/sql.log"; + # print FILE "\n==\n$stat\n==\n"; + # close FILE; + my $sth = $dbh->prepare($stat) or return $retdata->{error} = "dbquerysorted ".$dbh->errstr. "- SQL: ".$stat;; + + + $sth->execute() or return $retdata->{error} = "dbquerysorted: ".$sth->errstr; + my $count = 0; + + while(my $data = $sth->fetchrow_hashref()) + { + #$retdata->{$count} = $data; + foreach my $k (keys %{$data}){ + $retdata->{$count}->{$k} = decode("utf-8",$data->{$k}); + } + $count++; + } + +# my $qstruct = (); +# my $num_fields = $sth->{NUM_OF_FIELDS}; + +# for ( my $i=0; $i< $num_fields; $i++ ) { +# $qstruct->{$i}->{name} = $sth->{NAME}->[$i]; +# #$qstruct->{$i}->{type} = $sth->{COMMENT}->[$i]; +# #$qstruct->{$i}->{precision} = $sth->{PRECISION}->[$i]; +# } + + $sth->finish(); + $dbh->disconnect(); + + return $retdata; +} + +sub dbexec(){ + my $self = shift; + my $stat = shift; + my $retdata; + my $dbh = DBI->connect($sitecfg->{dsn},$sitecfg->{dbuser},$sitecfg->{dbpassword},{PrintError=>0,RaiseError=>0,AutoCommit=>1}) or return $retdata->{error} = "dbquery Connection Error!".$!; + # $stat = decode("UTF-8", $stat); + # open FILE,">>tmp/sql.log"; + # print FILE "\n==\n$stat\n==\n"; + # close FILE; + my $sth = $dbh->prepare($stat) or return $retdata->{error} = "dbexec ".$dbh->errstr. "- SQL: ".$stat;; + $retdata->{success} = $dbh->do($stat) or return $retdata->{error} = "dbexec ".$dbh->errstr. "- SQL: ".$stat; + $dbh->disconnect(); + return $retdata; +} + +sub dbqueryarray(){ + my $self = shift; + my $stat = shift; + my @retdata = (); + my $dbh = DBI->connect($sitecfg->{dsn},$sitecfg->{dbuser},$sitecfg->{dbpassword},{PrintError=>0,RaiseError=>0,AutoCommit=>1}) or return ({"error" => "dbqueryarray Connection Error!".$!}); + #$stat = encode("utf8", $stat); + #open FILE,">>/tmp/sql.log"; + #print "$stat\n"; + # close FILE; + my $sth = $dbh->prepare($stat); + + $sth->execute() or print "dbqueryarray: ".$sth->errstr; + my $count = 0; + + while(my $data = $sth->fetchrow_hashref()) + { + my $row = (); + foreach my $k (keys %{$data}){ + $row->{$k} = decode("utf-8",$data->{$k}); + } + push @retdata,$row; + } + + $sth->finish(); + $dbh->disconnect(); + #%retdata = sort {$a <=> $b} keys %retdata; + return \@retdata; +} + + +sub create_ddl_insert(){ + my $self = shift; + my $data = shift; + my $fields = (); + my @ddl = (); + + foreach my $f (keys(%{$data})){ + if (($f =~ /\_/) && ($f !~ /^ident_/)){ + my $t = substr($f,0,index($f,"_")); + my $c = substr($f,length($t)+1); + #my ($t,$c) = $f =~ m/(.+)\_(.+)/; + $fields->{$t}->{$c} = $data->{$f}; + } elsif ($f =~ /^ident_/){ + my $f2 = $f; + $f2 =~ s/^ident_//; + + my $t = substr($f2,0,index($f2,"_")); + my $c = substr($f2,length($t)+1); + $fields->{$t}->{$c} = $data->{$f}; + } + + } + + foreach my $tb (keys(%{$fields})){ + my @sqlcol = (); + my @sqlval = (); + foreach my $c (keys(%{$fields->{$tb}})){ + my $v = $fields->{$tb}->{$c}; + $v =~ s/'/''/g; + push (@sqlcol,$c); + if ($v eq ''){ + $v = 'null'; + } else { + $v = "'".$v."'"; + } + push (@sqlval,$v); + } + push(@ddl,"INSERT INTO public.".$tb." (".join(",",@sqlcol).") VALUES (".join(",",@sqlval).") returning id;"); + } + return @ddl; +} + +sub create_ddl_insert_json(){ + my $self = shift; + my $schema = shift; + my $table = shift; + my $columns = shift; + my $data = shift; + my @ddl = (); + my @sqlcol = (); + my @sqlval = (); + foreach my $c (keys(%{$data})){ + #if (exists($columns->{$c})){ + push (@sqlcol,'"'.$c.'"'); + my $v = $data->{$c}; + + if ($v eq ''){ + $v = 'null'; + }elsif ($v =~ /^data:.+;base64,/){ + $v =~ s/'/''/g; + $v = "'".$v."'"; + } + else { + $v= uri_decode($v); + $v =~ s/'/''/g; + if ($columns->{$c}->{data_type} eq "ARRAY"){ + if (ref($data->{$c}) eq "ARRAY"){ + $v = "{\"".join("\",\"",@{$data->{$c}})."\"}"; + } + else { + $v = 'null'; + } + $v =~ s/""/null/g; + }elsif ($columns->{$c}->{data_type} =~ /^timestamp/ ){ + + }elsif($columns->{$c}->{data_type} eq "date"){ + + }elsif($columns->{$c}->{data_type} eq "time"){ + + } + $v = "'".$v."'"; + } + push (@sqlval,$v); + #} + } + return "INSERT INTO public.".$schema.".\"".$table."\" (".join(",",@sqlcol).") VALUES (".join(",",@sqlval).");"; +} + +sub create_ddl_update(){ + my $self = shift; + my $data = shift; + my $fields = (); + my @ddl = (); + foreach my $f (keys(%{$data})){ + if ($f =~ /^ident_/){ + my $fx = substr($f,6); + my $t = substr($fx,0,index($fx,"_")); + my $c = substr($fx,length($t)+1); + #my ($t,$c) = $f =~ m/^ident_(.+)\_([a-z0-9|\_]+)/; + $fields->{$t}->{cond}->{$c} = $data->{$f}; + } elsif ( ($f !~ /^ident/) && ($f =~ /.+\_.+/) ){ + my $t = substr($f,0,index($f,"_")); + my $c = substr($f,length($t)+1); + #my ($t,$c) = $f =~ m/^(.+)\_([a-z0-9|\_]+)/; + $fields->{$t}->{fields}->{$c} = $data->{$f}; + } + } + foreach my $tb (keys(%{$fields})){ + my @sqlupd = (); + my @sqlcond = (); + foreach my $c (keys(%{$fields->{$tb}->{fields}})){ + + my $v = $fields->{$tb}->{fields}->{$c}; + $v =~ s/'/''/g; + + if ($c =~ /-/){ + my @jp = split('-',$c); + if ($v eq ''){ + $v = 'null'; + } else { + $v = '"'.$v.'"'; + } + $c = 'jsonb_set(to_jsonb('.$jp[0].'),\'{"'.$jp[1].'"}\',\''.$v.'\')::json'; + push (@sqlupd,$jp[0]."=".$c); + }else { + if ($v eq ''){ + $v = 'null'; + } else { + $v = "'".$v."'"; + } + push (@sqlupd,$c."=".$v); + } + + } + foreach my $c (keys(%{$fields->{$tb}->{cond}})){ + my $v = $fields->{$tb}->{cond}->{$c}; + $v =~ s/'/''/g; + if ($v eq ''){ + $v = 'null'; + } else { + $v = "'".$v."'"; + } + push (@sqlcond,$c."=".$v); + } + push(@ddl,"UPDATE public.".$tb." SET ".join(",",@sqlupd)." WHERE ".join(" AND ",@sqlcond).";"); + } + + return @ddl; +} + +sub create_cnt_statement(){ + my $self = shift; + my $data = shift; + my $fields = (); + my @ddl = (); + foreach my $f (keys(%{$data})){ + if ($f =~ /^ident_/){ + my $fx = substr($f,6); + my $t = substr($fx,0,index($fx,"_")); + my $c = substr($fx,length($t)+1); + #my ($t,$c) = $f =~ m/^ident_(.+)\_([a-z0-9|\_]+)/; + $fields->{$t}->{cond}->{$c} = $data->{$f}; + } + } + foreach my $tb (keys(%{$fields})){ + my @sqlcond = (); + foreach my $c (keys(%{$fields->{$tb}->{cond}})){ + my $v = $fields->{$tb}->{cond}->{$c}; + $v =~ s/'/''/g; + if ($v eq ''){ + $v = 'null'; + } else { + $v = "'".$v."'"; + } + push (@sqlcond,$c."=".$v); + } + push(@ddl,"SELECT count(*) as cnt from ".$tb." WHERE ".join(" AND ",@sqlcond).";"); + } + # open FILE,">>tmp/sql.log"; + # print FILE "\n==\n".join("\n",@ddl)."\n==\n"; + # close FILE; + return @ddl; +} + +sub create_ddl_delete(){ + my $self = shift; + my $data = shift; + my $fields = (); + my @ddl = (); + my @refcols = (); + my $refdata = (); + foreach my $f (keys(%{$data})){ + if ($f =~ /^ident_/){ + my ($t,$c) = $f =~ m/ident_(.+)\_(.+)/; + + $fields->{$t}->{cond}->{$c} = $data->{$f}; + push(@refcols,"'".$c.'_'.$t."'"); + $refdata->{$c.'_'.$t} = $data->{$f}; + } + } + +# my $ref = $self->dbquerysorted("select TABLE_NAME,COLUMN_NAME from information_schema.KEY_COLUMN_USAGE where COLUMN_NAME in (".join(",",@refcols).") and CONSTRAINT_SCHEMA='".$self->{dbname}."';"); +# foreach my $r (keys(%{$ref})){ +# my $refv = $refdata->{$ref->{$r}->{COLUMN_NAME}}; +# if ($refv eq ''){ +# $refv = ' is null'; +# } else { +# $refv =~ s/'/''/g; +# $refv = "='".$refv."'"; +# } +# push(@ddl,"DELETE from ".$ref->{$r}->{TABLE_NAME}." where ".$ref->{$r}->{COLUMN_NAME}.$refv.";"); +# } + foreach my $tb (keys(%{$fields})){ + my @sqlcond = (); + foreach my $c (keys(%{$fields->{$tb}->{cond}})){ + my $v = $fields->{$tb}->{cond}->{$c}; + $v =~ s/'/''/g; + push (@sqlcond,$c."='".$v."'"); + } + push(@ddl,"DELETE FROM public.".$tb." WHERE ".join(" AND ",@sqlcond).";"); + } + return @ddl; +} + +sub textunidecode(){ + my $self = shift; + my $text = shift; + $text = lc(unidecode(decode("utf-8",$text))); + $text =~ s/^[a-z0-9]//g; + return $text; +} + +1; diff --git a/backoffice/api/lib/dkssavefile.pm b/backoffice/api/lib/dkssavefile.pm new file mode 100644 index 00000000..55c260f2 --- /dev/null +++ b/backoffice/api/lib/dkssavefile.pm @@ -0,0 +1,49 @@ +package dkssavefile; + + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use lib ('./'); +use MIME::Base64; +use MIME::Types; +use URI; +use Data::Dumper; +use File::Path qw(make_path); + +sub new { + my $class = shift; + my $self = bless {}, $class; + return $self; +} + +sub saveb64toFile(){ + my $self = shift; + # my $fd = shift; + my $filename = shift; + my $sitepath = shift; + my $filepath = shift; + my $data = shift; + my $ds = URI->new($data); + my $mimetype= $ds->media_type(); + my $mt = MIME::Types->new(); + my $sfx = $mt->type($mimetype); + my $ext = $sfx->{MT_extensions}; + my $nfsfx = ""; + if (scalar(@$ext) > 0){ + $nfsfx = @$ext[0]; + } + if (! -d $sitepath.'/'.$filepath){ + make_path($sitepath.'/'.$filepath); + } + open (NF,">".$sitepath.'/'.$filepath.'/'.$filename.'.'.$nfsfx); + binmode(1); + print NF $ds->data(); + close(NF); + if (-e $sitepath.'/'.$filepath.'/'.$filename.'.'.$nfsfx){ + return $filepath.'/'.$filename.'.'.$nfsfx; + } + return ""; +} + +1; \ No newline at end of file diff --git a/backoffice/api/lib/sendemail.pm b/backoffice/api/lib/sendemail.pm new file mode 100644 index 00000000..6c2c6d0b --- /dev/null +++ b/backoffice/api/lib/sendemail.pm @@ -0,0 +1,130 @@ +package sendemail; + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use lib ('./'); +use Data::Dumper; +use File::Basename qw/dirname basename/; +use dksdb; + +sub new { + my $class = shift; + my $self = bless {}, $class; + $self->{server} = "mail.your-server.de"; + $self->{port} = "587"; + $self->{user} = 'webmaster@solana-architecture.lu'; + $self->{password} = "FLxCtIQs720K8n79"; + $self->{from} = 'webmaster@solana-architecture.lu'; + return $self; +} + +sub sendemail(){ + my $self = shift; + my $template = shift; + my $iduser = shift; + my $sendto = shift; + my $data = shift; + my $attach = shift; + my $body = ""; + my $subject = ""; + my $maildata = (); + my $db = dksdb->new(); + my $send = -1; + my $tmpl = $db->dbquerysorted("select *,ml.mailtemplate from mailtemplates mt join maillayouts ml on (mt.id_maillayout=ml.id) where templatename='".$template."';"); + if (keys(%{$tmpl}) > 0){ + $tmpl = $tmpl->{0}; + } + # open (LOG,">>tmp/sendmail.log"); + # print LOG $ENV{SCRIPT_FILENAME}; + # print LOG "SEND EMAIL:".Dumper($data)."\n"; + # close(LOG); + my $datasql = $tmpl->{'emaildatasql'}; + $data->{id} = $iduser; + foreach my $key (keys(%{$data})){ + my $srch = '%%'.lc($key).'%%'; + my $repl = $data->{$key}; + $datasql =~ s/$srch/$repl/g; + } + # open (LOG,">>tmp/sendmail.log"); + # print LOG "TEMPLATE DATA:".$datasql."\n"; + # close(LOG); + $maildata = $db->dbquerysorted($datasql); + + $body = $tmpl->{'emailtext'}; + $subject = $tmpl->{'mailsubject'}; + foreach my $key (keys(%{$maildata->{0}})){ + $data->{$key} = $maildata->{0}->{$key}; + } + foreach my $key (keys(%{$data})){ + my $srch = '%%'.lc($key).'%%'; + my $repl = $data->{$key}; + $body =~ s/$srch/$repl/g; + $subject =~ s/$srch/$repl/g; + } + my $bodytmpl = $tmpl->{mailtemplate}; + $bodytmpl =~ s/%%BODYCONTENT%%/$body/; + my $siteurl = $ENV{'REQUEST_SCHEME'}.'://'.$ENV{"HTTP_HOST"}; + $bodytmpl =~ s/%%siteurl%%/$siteurl/g; + $bodytmpl =~ s/%%SITEURL%%/$siteurl/g; + $bodytmpl =~ s/\r//g; + #$bodytmpl =~ s/"/\\\"/g; + #PROD REPLACE all not replaced DATA + #$bodytmpl =~ s/%%\w+%%//g; + #$sendto = 'ksaffran@dks.lu'; + # open (LOG,">>tmp/sendmail.log"); + # print LOG "SUBJECT:".$subject."\n"; + # print LOG "BODY TEXT:".$bodytmpl."\n"; + # close(LOG); + if (($bodytmpl ne "") && ($subject ne "") && ($sendto =~ /.+\@.+\..+/)){ + + + my $binsemail = dirname($ENV{'SCRIPT_FILENAME'}).'/sendEmail'; + my $f = dirname($ENV{SCRIPT_FILENAME}).'/tmp/mailbody_'.$sendto.'.txt'; + $f =~ s/\@/_/g; + if (! -e $binsemail){ + $binsemail = dirname($ENV{'SCRIPT_FILENAME'}).'/api/sendEmail'; + $f = dirname($ENV{SCRIPT_FILENAME}).'/api/tmp/mailbody_'.$sendto.'.txt'; + $f =~ s/\@/_/g; + if (! -e $binsemail){ + return 256; + } + } + + my $cmd= 'perl "'.$binsemail.'" -f '.$tmpl->{mailfrom}.' '; + $cmd .= ' -s "'.$self->{server}.':'.$self->{port}.'" -xu "'.$self->{user}.'" -xp "'.$self->{password}.'" -q '; + $cmd .= '-o tls=auto '; + $cmd .= '-o message-content-type=html '; + $cmd .= '-o message-charset=ISO-8859-1 '; + $cmd .= '-o message-file='.$f.' '; + $cmd .= '-t "'.$sendto.'" '; + $cmd .= '-u "'.$subject.'" '; + # open (LOG,">>sendmail.log"); + # print LOG $cmd."\n"; + # # print LOG "BODY TEXT:".$bodytmpl."\n"; + # close(LOG); + open(EML,">".$f); + print EML $bodytmpl; + close(EML); + # $cmd .= '-m "'.$bodytmpl.'" '; + if ($attach != undef){ + $cmd .= " -a"; + + foreach my $a (@{$attach}){ + $cmd .= " ".$a." "; + } + } + # open (LOG,">>tmp/sendmail.log"); + # print LOG "SEND EMAIL CMD:".$cmd."\n"; + # close(LOG); + # $cmd =~ s/'/''/g; + $send = system($cmd); + # open (LOG,">>tmp/sendmail.log"); + # print LOG "CMD RETURN NUM:".$send."\n"; + # close(LOG); + unlink($f); + } + return $send; +} + +1; \ No newline at end of file diff --git a/backoffice/api/lib/session.pm b/backoffice/api/lib/session.pm new file mode 100644 index 00000000..3b64d8da --- /dev/null +++ b/backoffice/api/lib/session.pm @@ -0,0 +1,241 @@ +package session; + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use lib ('./'); +use File::Basename; +use Digest::SHA qw(sha256_hex); + +use dksdb; +use sendemail; +# use Data::Dumper; + +sub new { + my $class = shift; + my $self = bless {}, $class; + $self->{db} = dksdb->new(); + return $self; +} + +sub checklogin(){ + my $self = shift; + my $login = shift; + my $password = shift; + # open FILE,">>tmp/sql.log"; + # print FILE "pwd: $password\n"; + # close(FILE); + my $pwd = sha256_hex($password); + my $ret->{messagetype} ='w3-red'; + # my $newsid = undef; + $login = lc($login); + $login =~ s/^\s+//; + $login =~ s/\s+$//; + + $ret->{message} = "Mot de passe ou Login pas inconnue!"; + $ret->{messagetype} = "w3-red"; + $ret->{sid} = undef; + my $user = $self->{db}->dbquerysorted("select id from users where lower(username)=lower('".$self->{db}->securetext($login)."') and userpassword = '".$pwd."' and \"blocked\" is null;"); + # open FILE,">>tmp/sql.log"; + # print FILE "select id from users where username= '".$self->{db}->securetext($login)."' and userpassword = '".$pwd."' and \"blocked\" is null;\n"; + # close FILE; + if (keys(%{$user}) > 0){ + $ret->{sid} = $self->randomstring(40); + $self->{db}->dbexec("DELETE FROM sessions where id_user=".$user->{0}->{id}." and remote_addr='".$ENV{REMOTE_ADDR}."' and user_agent='".$ENV{HTTP_USER_AGENT}."';"); + my $r = $self->{db}->dbexec("INSERT INTO sessions (id_user,idsession,remote_addr,user_agent) VALUES (".$user->{0}->{id}.", '".$ret->{sid}."', '".$ENV{REMOTE_ADDR}."', '".$ENV{HTTP_USER_AGENT}."');"); + } + return $ret; +} + +sub savepassword(){ + my $self = shift; + my $iduser = shift; + my $newpwd = shift; + my $pwd = sha256_hex($newpwd); + $self->{db}->dbexec("UPDATE users SET userpassword = '".$pwd."' WHERE id=".$iduser.";"); + return 1; +} + +sub passwordforgotten(){ + my $self = shift; + my $email = shift; + my $ret->{messagetype} ='w3-red'; + $ret->{message} = "Onbekannt E-mail!"; + my $sql = "select id,userpassword from users where username='".$self->{db}->securetext($email)."';"; + my $ex = $self->{db}->dbquerysorted($sql); + if (keys(%{$ex}) > 0){ + my $newpwd = $self->randomstring(12); + my $pwd = sha256_hex($newpwd); + $self->{db}->dbexec("UPDATE users SET userpassword = '".$pwd."' WHERE id=".$ex->{0}->{id}.";"); + my $data->{newpassword} = $newpwd; + my $eml = sendemail->new(); + my $mret = $eml->sendemail('user_forgotpasswd',$ex->{0}->{id},$email,$data,undef); + if ($mret != 0){ + $ret->{messagetype} ='w3-red'; + $ret->{message} = "Den Moment ass et leider nët méglech d'Passwuert autmatesch zreckzesetzen,
wend dech w.e.g. via E-Mail un webmaster\@fld.lu!"; + return $ret; + } + $ret->{message} = "Mir hun dir eng E-Mail, matt engem neien Passwuert gescheckt!"; + $ret->{messagetype} = "w3-green"; + } + return $ret; +} + +sub registeruser(){ + my $self = shift; + my $data = shift; + my $ret->{messagetype} ='w3-red'; + $ret->{message} = "Une erreur c'est produite, essayer plus tard!"; + $ret->{page} = "message.tt"; + my @reqfields = ("companies_company","terms","users_email","members_phone","members_surname","members_prename","members_job"); + my $reqf = 1; + foreach my $rf (@reqfields){ + if (!exists($data->{$rf})){ + $reqf = 0; + } + } + if ($reqf == 0){ + $ret->{message} = "Rempissez tous les champs s.v.p.!"; + $ret->{page} = "register.tt"; + return $ret; + } + foreach my $d (%{$data}){ + $data->{$d} = $self->{db}->securetext($data->{$d}); + } + + my $user = $self->{db}->dbquerysorted("select id from users where username='".$data->{users_email}."';"); + if (keys(%{$user}) > 0){ + $ret->{page} = "register.tt"; + $ret->{message} = "Ily existe déjà un utilisateur avec la mème adresse e-mail!"; + return $ret; + } + + my $newcode = $self->randomstring(6); + + my $newuserid = $self->{db}->dbquerysorted("INSERT INTO users (username,vcode) VALUES ('".$data->{users_email}."','".$newcode."') returning id;"); + $self->{db}->dbexec("INSERT INTO useringroups (id_user,id_group) VALUES ('".$newuserid->{0}->{id}."',(select id from usergroups where isdefault=true));"); + my $company = $self->{db}->dbquerysorted("select id from companies where link=unaccent_string('".$data->{companies_company}."');"); + if (keys(%{$company}) == 0 ){ + $company = $self->{db}->dbquerysorted("INSERT INTO companies (company,link) VALUES ('".$data->{companies_company}."',unaccent_string('".$data->{companies_company}."')) returning id;"); + $self->{db}->dbexec("INSERT INTO useringroups (id_user,id_group) VALUES ('".$newuserid->{0}->{id}."',(select id from usergroups where usergroup='company'));"); + } + $self->{db}->dbexec("INSERT INTO members (surname, prename, phone, id_user, id_company, job) VALUES('".$data->{members_surname}."', '".$data->{members_prename}."', '".$data->{members_phone}."', ".$newuserid->{0}->{id}.", ".$company->{0}->{id}.", '".$data->{members_job}."');"); + my $maildata->{vcode} = $newcode; + my $eml = sendemail->new(); + my $mret = $eml->sendemail('user_verification',$newuserid->{0}->{id},$data->{users_email},$maildata,undef); + if ($mret == 0){ + $ret->{message} = "Merci,
nous vous avons envoyé un email,avec un code de vérification!
Entrez ce code ci dessous, pour valider votre compte!
en cas de problèmes envoyer un email à info\@solana-architecture.lu"; + $ret->{messagetype} = "w3-green"; + $ret->{page} = "validationcode.tt"; + } else { + $self->{db}->dbexec("UPDATE users set username='".$data->{users_email}."',vcode=null where id=".$newuserid->{0}->{id}." ;"); + $ret->{message} = "nous ne pouvons pas envoyer un email à '".$data->{users_email}."' ! Si cette email n'existe pas, régisterez-vous avec un email existant!
en cas de problèemes envoyer un email à info\@solana-architecture.lu"; + $ret->{messagetype} = "w3-red"; + $ret->{page} = "register.tt"; + } + #$self->{db}->dbexec("insert into appaccess (id_user) values (".$newuserid->{0}->{id}.");"); + # $ret->{messagetype} = "w3-green"; + + return $ret; +} + +sub validateaccount(){ + my $self = shift; + my $data = shift; + foreach my $d (%{$data}){ + $data->{$d} = $self->{db}->securetext($data->{$d}); + } + + my $ret->{messagetype} ='w3-red'; + my $vcodedata = $self->{db}->dbquerysorted("select id,vcode,username from users where vcode='".$data->{vcode}."';"); + if (keys(%{$vcodedata}) == 0){ + $ret->{message} = "code inconnue!"; + $ret->{page} = "validationcode.tt"; + } + my $newpwd = $self->randomstring(12); + my $pwd = sha256_hex($newpwd); + my $maildata->{password} = $newpwd; + my $eml = sendemail->new(); + my $newuserid = $self->{db}->dbquerysorted("UPDATE users set userpassword='".$pwd."',vcode=null,regcode=null where id=".$vcodedata->{0}->{id}." returning id,username;"); + my $mret = $eml->sendemail('user_registration',$vcodedata->{0}->{id},$vcodedata->{0}->{username},$maildata,undef); + if ($mret == 0){ + $ret->{message} = "Merci,
Nous vous avons envoyé un email avec les données nécessaires pour se connecter!"; + $ret->{messagetype} = "w3-green"; + $ret->{page} = "message.tt"; + } else { + $ret->{message} = "NOus n'avons pas pu vous envoyé un email à '".$newuserid->{0}->{username}."'! Si l'addresse email n'existe pas, essayer avec un email existant!"; + $ret->{page} = "message.tt"; + } + return $ret; +} + +sub getsession($){ + my $self = shift; + my $sid = shift; + my $sql ="select se.idsession,us.id,us.username,string_agg(distinct(aug.usergroup),',') as usergroups from sessions se +join users us on (us.id=se.id_user) +left join useringroups uig on (us.id=uig.id_user) +left join usergroups aug on (aug.id=uig.id_group) +where se.idsession= '".$self->{db}->securetext($sid)."' +and se.remote_addr= '".$ENV{REMOTE_ADDR}."' +and se.user_agent='".$ENV{HTTP_USER_AGENT}."' and us.blocked is null group by se.id,us.id;"; + my $res= $self->{db}->dbquerysorted($sql); + my $ret = undef; + if (keys(%{$res}) > 0){ + return $res->{0}; + } + return $ret; +} + +sub deletesession(){ + my $self = shift; + my $sid = shift; + $self->{db}->dbexec("DELETE FROM sessions where idsession='".$self->{db}->securetext($sid)."';"); +} + +sub randomstring(){ + my $self = shift; + my $num = shift; + my @alphanumeric = ('a'..'z', 'A'..'Z', 0..9); + my $randstring = join '', map $alphanumeric[rand @alphanumeric], 0..$num; + return $randstring; +} + + +# sub deleteprofile(){ +# my $self = shift; +# my $data = shift; +# my $ret->{message} = "mot de passe ou profile inconnue!"; +# $ret->{messagetype} = "danger"; +# if ($data->{id_user} eq ''){ +# $ret->{sid} = undef; +# return $ret; +# } +# my $pwd = sha256_hex($data->{password}); +# my $user = $self->{db}->dbquerysorted("select id from users where id= '".$data->{id_user}."' and userpassword = '".$pwd."';"); +# if (keys(%{$user}) > 0){ +# $self->admindeleteuser($data->{id_user}); +# my $ret->{'message'} = "Votre profile a été supprimé!"; +# $ret->{'messagetype'} = "info"; +# $ret->{sid} = undef; +# } +# return $ret; + +# } + +# sub admindeleteuser(){ +# my $self = shift; +# my $id_user = shift; +# my @dl = ("DELETE FROM public.useringroups WHERE id_uset=".$id_user.";", +# "DELETE FROM public.userclients WHERE id_user=".$id_user.";", +# "DELETE FROM public.appaccess WHERE id_user=".$id_user.";", +# "DELETE FROM public.modulepreferences WHERE id_user=".$id_user.";",, +# "DELETE FROM public.sessions WHERE id_user=".$id_user.";", +# "delete from users where id=".$id_user.";"); +# foreach my $s (@dl){ +# $self->{db}->dbexec($s); +# } +# return 1; +# } + +1; \ No newline at end of file diff --git a/backoffice/api/sendEmail b/backoffice/api/sendEmail new file mode 100644 index 00000000..c639439c --- /dev/null +++ b/backoffice/api/sendEmail @@ -0,0 +1,2235 @@ +#!/usr/bin/perl -w +############################################################################## +## sendEmail +## Written by: Brandon Zehm +## +## License: +## sendEmail (hereafter referred to as "program") is free software; +## you can redistribute it and/or modify it under the terms of the GNU General +## Public License as published by the Free Software Foundation; either version +## 2 of the License, or (at your option) any later version. +## When redistributing modified versions of this source code it is recommended +## that that this disclaimer and the above coder's names are included in the +## modified code. +## +## Disclaimer: +## This program is provided with no warranty of any kind, either expressed or +## implied. It is the responsibility of the user (you) to fully research and +## comprehend the usage of this program. As with any tool, it can be misused, +## either intentionally (you're a vandal) or unintentionally (you're a moron). +## THE AUTHOR(S) IS(ARE) NOT RESPONSIBLE FOR ANYTHING YOU DO WITH THIS PROGRAM +## or anything that happens because of your use (or misuse) of this program, +## including but not limited to anything you, your lawyers, or anyone else +## can dream up. And now, a relevant quote directly from the GPL: +## +## NO WARRANTY +## +## 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +## FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +## OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +## PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +## OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +## TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +## PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +## REPAIR OR CORRECTION. +## +############################################################################## +use strict; +use IO::Socket; + + +######################## +## Global Variables ## +######################## + +my %conf = ( + ## General + "programName" => $0, ## The name of this program + "version" => '1.56', ## The version of this program + "authorName" => 'Brandon Zehm', ## Author's Name + "authorEmail" => 'caspian@dotconf.net', ## Author's Email Address + "timezone" => '+0000', ## We always use +0000 for the time zone + "hostname" => 'changeme', ## Used in printmsg() for all output (is updated later in the script). + "debug" => 0, ## Default debug level + "error" => '', ## Error messages will often be stored here + + ## Logging + "stdout" => 1, + "logging" => 0, ## If this is true the printmsg function prints to the log file + "logFile" => '', ## If this is specified (form the command line via -l) this file will be used for logging. + + ## Network + "server" => 'localhost', ## Default SMTP server + "port" => 25, ## Default port + "bindaddr" => '', ## Default local bind address + "alarm" => '', ## Default timeout for connects and reads, this gets set from $opt{'timeout'} + "tls_client" => 0, ## If TLS is supported by the client (us) + "tls_server" => 0, ## If TLS is supported by the remote SMTP server + + ## Email + "delimiter" => "----MIME delimiter for sendEmail-" ## MIME Delimiter + . rand(1000000), ## Add some randomness to the delimiter + "Message-ID" => rand(1000000) . "-sendEmail", ## Message-ID for email header + +); + + +## This hash stores the options passed on the command line via the -o option. +my %opt = ( + ## Addressing + "reply-to" => '', ## Reply-To field + + ## Message + "message-file" => '', ## File to read message body from + "message-header" => '', ## Additional email header line(s) + "message-format" => 'normal', ## If "raw" is specified the message is sent unmodified + "message-charset" => 'iso-8859-1', ## Message character-set + "message-content-type" => 'auto', ## auto, text, html or an actual string to put into the content-type header. + + ## Network + "timeout" => 60, ## Default timeout for connects and reads, this is copied to $conf{'alarm'} later. + "fqdn" => 'changeme', ## FQDN of this machine, used during SMTP communication (is updated later in the script). + + ## eSMTP + "username" => '', ## Username used in SMTP Auth + "password" => '', ## Password used in SMTP Auth + "tls" => 'auto', ## Enable or disable TLS support. Options: auto, yes, no + +); + +## More variables used later in the program +my $SERVER; +my $CRLF = "\015\012"; +my $subject = ''; +my $header = ''; +my $message = ''; +my $from = ''; +my @to = (); +my @cc = (); +my @bcc = (); +my @attachments = (); +my @attachments_names = (); + +## For printing colors to the console +my ${colorRed} = "\033[31;1m"; +my ${colorGreen} = "\033[32;1m"; +my ${colorCyan} = "\033[36;1m"; +my ${colorWhite} = "\033[37;1m"; +my ${colorNormal} = "\033[m"; +my ${colorBold} = "\033[1m"; +my ${colorNoBold} = "\033[0m"; + +## Don't use shell escape codes on Windows systems +if ($^O =~ /win/i) { + ${colorRed} = ${colorGreen} = ${colorCyan} = ${colorWhite} = ${colorNormal} = ${colorBold} = ${colorNoBold} = ""; +} + +## Load IO::Socket::SSL if it's available +eval { require IO::Socket::SSL; }; +if ($@) { $conf{'tls_client'} = 0; } +else { $conf{'tls_client'} = 1; } + + + + + + +############################# +## ## +## FUNCTIONS ## +## ## +############################# + + + + + +############################################################################################### +## Function: initialize () +## +## Does all the script startup jibberish. +## +############################################################################################### +sub initialize { + + ## Set STDOUT to flush immediatly after each print + $| = 1; + + ## Intercept signals + $SIG{'QUIT'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; + $SIG{'INT'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; + $SIG{'KILL'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; + $SIG{'TERM'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; + + ## ALARM and HUP signals are not supported in Win32 + unless ($^O =~ /win/i) { + $SIG{'HUP'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; + $SIG{'ALRM'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; + } + + ## Fixup $conf{'programName'} + $conf{'programName'} =~ s/(.)*[\/,\\]//; + $0 = $conf{'programName'} . " " . join(" ", @ARGV); + + ## Fixup $conf{'hostname'} and $opt{'fqdn'} + if ($opt{'fqdn'} eq 'changeme') { $opt{'fqdn'} = get_hostname(1); } + if ($conf{'hostname'} eq 'changeme') { $conf{'hostname'} = $opt{'fqdn'}; $conf{'hostname'} =~ s/\..*//; } + + return(1); +} + + + + + + + + + + + + + + + +############################################################################################### +## Function: processCommandLine () +## +## Processes command line storing important data in global vars (usually %conf) +## +############################################################################################### +sub processCommandLine { + + + ############################ + ## Process command line ## + ############################ + + my @ARGS = @ARGV; ## This is so later we can re-parse the command line args later if we need to + my $numargv = @ARGS; + help() unless ($numargv); + my $counter = 0; + + for ($counter = 0; $counter < $numargv; $counter++) { + + if ($ARGS[$counter] =~ /^-h$/i) { ## Help ## + help(); + } + + elsif ($ARGS[$counter] eq "") { ## Ignore null arguments + ## Do nothing + } + + elsif ($ARGS[$counter] =~ /^--help/) { ## Topical Help ## + $counter++; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + helpTopic($ARGS[$counter]); + } + else { + help(); + } + } + + elsif ($ARGS[$counter] =~ /^-o$/i) { ## Options specified with -o ## + $counter++; + ## Loop through each option passed after the -o + while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + + if ($ARGS[$counter] !~ /(\S+)=(\S.*)/) { + printmsg("WARNING => Name/Value pair [$ARGS[$counter]] is not properly formatted", 0); + printmsg("WARNING => Arguments proceeding -o should be in the form of \"name=value\"", 0); + } + else { + if (exists($opt{$1})) { + if ($1 eq 'message-header') { + $opt{$1} .= $2 . $CRLF; + } + else { + $opt{$1} = $2; + } + printmsg("DEBUG => Assigned \$opt{} key/value: $1 => $2", 3); + } + else { + printmsg("WARNING => Name/Value pair [$ARGS[$counter]] will be ignored: unknown key [$1]", 0); + printmsg("HINT => Try the --help option to find valid command line arguments", 1); + } + } + $counter++; + } $counter--; + } + + elsif ($ARGS[$counter] =~ /^-f$/) { ## From ## + $counter++; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $from = $ARGS[$counter]; } + else { printmsg("WARNING => The argument after -f was not an email address!", 0); $counter--; } + } + + elsif ($ARGS[$counter] =~ /^-t$/) { ## To ## + $counter++; + while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { + if ($ARGS[$counter] =~ /[;,]/) { + push (@to, split(/[;,]/, $ARGS[$counter])); + } + else { + push (@to,$ARGS[$counter]); + } + $counter++; + } $counter--; + } + + elsif ($ARGS[$counter] =~ /^-cc$/) { ## Cc ## + $counter++; + while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { + if ($ARGS[$counter] =~ /[;,]/) { + push (@cc, split(/[;,]/, $ARGS[$counter])); + } + else { + push (@cc,$ARGS[$counter]); + } + $counter++; + } $counter--; + } + + elsif ($ARGS[$counter] =~ /^-bcc$/) { ## Bcc ## + $counter++; + while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { + if ($ARGS[$counter] =~ /[;,]/) { + push (@bcc, split(/[;,]/, $ARGS[$counter])); + } + else { + push (@bcc,$ARGS[$counter]); + } + $counter++; + } $counter--; + } + + elsif ($ARGS[$counter] =~ /^-m$/) { ## Message ## + $counter++; + $message = ""; + while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + if ($message) { $message .= " "; } + $message .= $ARGS[$counter]; + $counter++; + } $counter--; + + ## Replace '\n' with $CRLF. + ## This allows newlines with messages sent on the command line + $message =~ s/\\n/$CRLF/g; + } + + elsif ($ARGS[$counter] =~ /^-u$/) { ## Subject ## + $counter++; + $subject = ""; + while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + if ($subject) { $subject .= " "; } + $subject .= $ARGS[$counter]; + $counter++; + } $counter--; + } + + elsif ($ARGS[$counter] =~ /^-s$/) { ## Server ## + $counter++; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + $conf{'server'} = $ARGS[$counter]; + if ($conf{'server'} =~ /:/) { ## Port ## + ($conf{'server'},$conf{'port'}) = split(":",$conf{'server'}); + } + } + else { printmsg("WARNING - The argument after -s was not the server!", 0); $counter--; } + } + + elsif ($ARGS[$counter] =~ /^-b$/) { ## Bind Address ## + $counter++; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + $conf{'bindaddr'} = $ARGS[$counter]; + } + else { printmsg("WARNING - The argument after -b was not the bindaddr!", 0); $counter--; } + } + + elsif ($ARGS[$counter] =~ /^-a$/) { ## Attachments ## + $counter++; + while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { + push (@attachments,$ARGS[$counter]); + $counter++; + } $counter--; + } + + elsif ($ARGS[$counter] =~ /^-xu$/) { ## AuthSMTP Username ## + $counter++; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + $opt{'username'} = $ARGS[$counter]; + } + else { + printmsg("WARNING => The argument after -xu was not valid username!", 0); + $counter--; + } + } + + elsif ($ARGS[$counter] =~ /^-xp$/) { ## AuthSMTP Password ## + $counter++; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { + $opt{'password'} = $ARGS[$counter]; + } + else { + printmsg("WARNING => The argument after -xp was not valid password!", 0); + $counter--; + } + } + + elsif ($ARGS[$counter] =~ /^-l$/) { ## Logging ## + $counter++; + $conf{'logging'} = 1; + if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $conf{'logFile'} = $ARGS[$counter]; } + else { printmsg("WARNING - The argument after -l was not the log file!", 0); $counter--; } + } + + elsif ($ARGS[$counter] =~ s/^-v+//i) { ## Verbosity ## + my $tmp = (length($&) - 1); + $conf{'debug'} += $tmp; + } + + elsif ($ARGS[$counter] =~ /^-q$/) { ## Quiet ## + $conf{'stdout'} = 0; + } + + else { + printmsg("Error: \"$ARGS[$counter]\" is not a recognized option!", 0); + help(); + } + + } + + + + + + + + + ################################################### + ## Verify required variables are set correctly ## + ################################################### + + ## Make sure we have something in $conf{hostname} and $opt{fqdn} + if ($opt{'fqdn'} =~ /\./) { + $conf{'hostname'} = $opt{'fqdn'}; + $conf{'hostname'} =~ s/\..*//; + } + + if (!$conf{'server'}) { $conf{'server'} = 'localhost'; } + if (!$conf{'port'}) { $conf{'port'} = 25; } + if (!$from) { + quit("ERROR => You must specify a 'from' field! Try --help.", 1); + } + if ( ((scalar(@to)) + (scalar(@cc)) + (scalar(@bcc))) <= 0) { + quit("ERROR => You must specify at least one recipient via -t, -cc, or -bcc", 1); + } + + ## Make sure email addresses look OK. + foreach my $addr (@to, @cc, @bcc, $from, $opt{'reply-to'}) { + if ($addr) { + if (!returnAddressParts($addr)) { + printmsg("ERROR => Can't use improperly formatted email address: $addr", 0); + printmsg("HINT => Try viewing the extended help on addressing with \"--help addressing\"", 1); + quit("", 1); + } + } + } + + ## Make sure all attachments exist. + foreach my $file (@attachments) { + if ( (! -f $file) or (! -r $file) ) { + printmsg("ERROR => The attachment [$file] doesn't exist!", 0); + printmsg("HINT => Try specifying the full path to the file or reading extended help with \"--help message\"", 1); + quit("", 1); + } + } + + if ($conf{'logging'} and (!$conf{'logFile'})) { + quit("ERROR => You used -l to enable logging but didn't specify a log file!", 1); + } + + if ( $opt{'username'} ) { + if (!$opt{'password'}) { + ## Prompt for a password since one wasn't specified with the -xp option. + $SIG{'ALRM'} = sub { quit("ERROR => Timeout waiting for password inpupt", 1); }; + alarm(60) if ($^O !~ /win/i); ## alarm() doesn't work in win32 + print "Password: "; + $opt{'password'} = ; chomp $opt{'password'}; + if (!$opt{'password'}) { + quit("ERROR => A username for SMTP authentication was specified, but no password!", 1); + } + } + } + + ## Validate the TLS setting + $opt{'tls'} = lc($opt{'tls'}); + if ($opt{'tls'} !~ /^(auto|yes|no)$/) { + quit("ERROR => Invalid TLS setting ($opt{'tls'}). Must be one of auto, yes, or no.", 1); + } + + ## If TLS is set to "yes", make sure sendEmail loaded the libraries needed. + if ($opt{'tls'} eq 'yes' and $conf{'tls_client'} == 0) { + quit("ERROR => No TLS support! SendEmail can't load required libraries. (try installing Net::SSLeay and IO::Socket::SSL)", 1); + } + + ## Return 0 errors + return(0); +} + + + + + + + + + + + + + + + + +## getline($socketRef) +sub getline { + my ($socketRef) = @_; + local ($/) = "\r\n"; + return $$socketRef->getline; +} + + + + +## Receive a (multiline?) SMTP response from ($socketRef) +sub getResponse { + my ($socketRef) = @_; + my ($tmp, $reply); + local ($/) = "\r\n"; + return undef unless defined($tmp = getline($socketRef)); + return("getResponse() socket is not open") unless ($$socketRef->opened); + ## Keep reading lines if it's a multi-line response + while ($tmp =~ /^\d{3}-/o) { + $reply .= $tmp; + return undef unless defined($tmp = getline($socketRef)); + } + $reply .= $tmp; + $reply =~ s/\r?\n$//o; + return $reply; +} + + + + +############################################################################################### +## Function: SMTPchat ( [string $command] ) +## +## Description: Sends $command to the SMTP server (on SERVER) and awaits a successful +## reply form the server. If the server returns an error, or does not reply +## within $conf{'alarm'} seconds an error is generated. +## NOTE: $command is optional, if no command is specified then nothing will +## be sent to the server, but a valid response is still required from the server. +## +## Input: [$command] A (optional) valid SMTP command (ex. "HELO") +## +## +## Output: Returns zero on success, or non-zero on error. +## Error messages will be stored in $conf{'error'} +## A copy of the last SMTP response is stored in the global variable +## $conf{'SMTPchat_response'} +## +## +## Example: SMTPchat ("HELO mail.isp.net"); +############################################################################################### +sub SMTPchat { + my ($command) = @_; + + printmsg("INFO => Sending: \t$command", 1) if ($command); + + ## Send our command + print $SERVER "$command$CRLF" if ($command); + + ## Read a response from the server + $SIG{'ALRM'} = sub { $conf{'error'} = "alarm"; $SERVER->close(); }; + alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32; + my $result = $conf{'SMTPchat_response'} = getResponse(\$SERVER); + alarm(0) if ($^O !~ /win/i); ## alarm() doesn't work in win32; + + ## Generate an alert if we timed out + if ($conf{'error'} eq "alarm") { + $conf{'error'} = "ERROR => Timeout while reading from $conf{'server'}:$conf{'port'} There was no response after $conf{'alarm'} seconds."; + return(1); + } + + ## Make sure the server actually responded + if (!$result) { + $conf{'error'} = "ERROR => $conf{'server'}:$conf{'port'} returned a zero byte response to our query."; + return(2); + } + + ## Validate the response + if (evalSMTPresponse($result)) { + ## conf{'error'} will already be set here + return(2); + } + + ## Print the success messsage + printmsg($conf{'error'}, 1); + + ## Return Success + return(0); +} + + + + + + + + + + + + +############################################################################################### +## Function: evalSMTPresponse (string $message ) +## +## Description: Searches $message for either an SMTP success or error code, and returns +## 0 on success, and the actual error code on error. +## +## +## Input: $message Data received from a SMTP server (ex. "220 +## +## +## Output: Returns zero on success, or non-zero on error. +## Error messages will be stored in $conf{'error'} +## +## +## Example: SMTPchat ("HELO mail.isp.net"); +############################################################################################### +sub evalSMTPresponse { + my ($message) = @_; + + ## Validate input + if (!$message) { + $conf{'error'} = "ERROR => No message was passed to evalSMTPresponse(). What happened?"; + return(1) + } + + printmsg("DEBUG => evalSMTPresponse() - Checking for SMTP success or error status in the message: $message ", 3); + + ## Look for a SMTP success code + if ($message =~ /^([23]\d\d)/) { + printmsg("DEBUG => evalSMTPresponse() - Found SMTP success code: $1", 2); + $conf{'error'} = "SUCCESS => Received: \t$message"; + return(0); + } + + ## Look for a SMTP error code + if ($message =~ /^([45]\d\d)/) { + printmsg("DEBUG => evalSMTPresponse() - Found SMTP error code: $1", 2); + $conf{'error'} = "ERROR => Received: \t$message"; + return($1); + } + + ## If no SMTP codes were found return an error of 1 + $conf{'error'} = "ERROR => Received a message with no success or error code. The message received was: $message"; + return(2); + +} + + + + + + + + + + +######################################################### +# SUB: &return_month(0,1,etc) +# returns the name of the month that corrosponds +# with the number. returns 0 on error. +######################################################### +sub return_month { + my $x = $_[0]; + if ($x == 0) { return 'Jan'; } + if ($x == 1) { return 'Feb'; } + if ($x == 2) { return 'Mar'; } + if ($x == 3) { return 'Apr'; } + if ($x == 4) { return 'May'; } + if ($x == 5) { return 'Jun'; } + if ($x == 6) { return 'Jul'; } + if ($x == 7) { return 'Aug'; } + if ($x == 8) { return 'Sep'; } + if ($x == 9) { return 'Oct'; } + if ($x == 10) { return 'Nov'; } + if ($x == 11) { return 'Dec'; } + return (0); +} + + + + + + + + + + + + + + + + +######################################################### +# SUB: &return_day(0,1,etc) +# returns the name of the day that corrosponds +# with the number. returns 0 on error. +######################################################### +sub return_day { + my $x = $_[0]; + if ($x == 0) { return 'Sun'; } + if ($x == 1) { return 'Mon'; } + if ($x == 2) { return 'Tue'; } + if ($x == 3) { return 'Wed'; } + if ($x == 4) { return 'Thu'; } + if ($x == 5) { return 'Fri'; } + if ($x == 6) { return 'Sat'; } + return (0); +} + + + + + + + + + + + + + + + + +############################################################################################### +## Function: returnAddressParts(string $address) +## +## Description: Returns a two element array containing the "Name" and "Address" parts of +## an email address. +## +## Example: "Brandon Zehm " +## would return: ("Brandon Zehm", "caspian@dotconf.net"); +## +## "caspian@dotconf.net" +## would return: ("caspian@dotconf.net", "caspian@dotconf.net") +############################################################################################### +sub returnAddressParts { + my $input = $_[0]; + my $name = ""; + my $address = ""; + + ## Make sure to fail if it looks totally invalid + if ($input !~ /(\S+\@\S+)/) { + $conf{'error'} = "ERROR => The address [$input] doesn't look like a valid email address, ignoring it"; + return(undef()); + } + + ## Check 1, should find addresses like: "Brandon Zehm " + elsif ($input =~ /^\s*(\S(.*\S)?)\s*<(\S+\@\S+)>/o) { + ($name, $address) = ($1, $3); + } + + ## Otherwise if that failed, just get the address: + elsif ($input =~ /<(\S+\@\S+)>/o) { + $name = $address = $1; + } + + ## Or maybe it was formatted this way: caspian@dotconf.net + elsif ($input =~ /(\S+\@\S+)/o) { + $name = $address = $1; + } + + ## Something stupid happened, just return an error. + unless ($name and $address) { + printmsg("ERROR => Couldn't parse the address: $input", 0); + printmsg("HINT => If you think this should work, consider reporting this as a bug to $conf{'authorEmail'}", 1); + return(undef()); + } + + ## Make sure there aren't invalid characters in the address, and return it. + my $ctrl = '\000-\037'; + my $nonASCII = '\x80-\xff'; + if ($address =~ /[<> ,;:"'\[\]\\$ctrl$nonASCII]/) { + printmsg("WARNING => The address [$address] seems to contain invalid characters: continuing anyway", 0); + } + return($name, $address); +} + + + + + + + + + + + + + + + + +############################################################################################### +## Function: base64_encode(string $data, bool $chunk) +## +## Description: Returns $data as a base64 encoded string. +## If $chunk is true, the encoded data is returned in 76 character long lines +## with the final \CR\LF removed. +## +## Note: This is only used from the smtp auth section of code. +## At some point it would be nice to merge the code that encodes attachments and this. +############################################################################################### +sub base64_encode { + my $data = $_[0]; + my $chunk = $_[1]; + my $tmp = ''; + my $base64 = ''; + my $CRLF = "\r\n"; + + ################################### + ## Convert binary data to base64 ## + ################################### + while ($data =~ s/(.{45})//s) { ## Get 45 bytes from the binary string + $tmp = substr(pack('u', $&), 1); ## Convert the binary to uuencoded text + chop($tmp); + $tmp =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 + $base64 .= $tmp; + } + + ########################## + ## Encode the leftovers ## + ########################## + my $padding = ""; + if ( ($data) and (length($data) > 0) ) { + $padding = (3 - length($data) % 3) % 3; ## Set flag if binary data isn't divisible by 3 + $tmp = substr(pack('u', $data), 1); ## Convert the binary to uuencoded text + chop($tmp); + $tmp =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 + $base64 .= $tmp; + } + + ############################ + ## Fix padding at the end ## + ############################ + $data = ''; + $base64 =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set + if ($chunk) { + while ($base64 =~ s/(.{1,76})//s) { ## Put $CRLF after each 76 characters + $data .= "$1$CRLF"; + } + } + else { + $data = $base64; + } + + ## Remove any trailing CRLF's + $data =~ s/(\r|\n)*$//s; + return($data); +} + + + + + + + + + +######################################################### +# SUB: send_attachment("/path/filename") +# Sends the mime headers and base64 encoded file +# to the email server. +######################################################### +sub send_attachment { + my ($filename) = @_; ## Get filename passed + my (@fields, $y, $filename_name, $encoding, ## Local variables + @attachlines, $content_type); + my $bin = 1; + + @fields = split(/\/|\\/, $filename); ## Get the actual filename without the path + $filename_name = pop(@fields); + push @attachments_names, $filename_name; ## FIXME: This is only used later for putting in the log file + + ########################## + ## Autodetect Mime Type ## + ########################## + + @fields = split(/\./, $filename_name); + $encoding = $fields[$#fields]; + + if ($encoding =~ /txt|text|log|conf|^c$|cpp|^h$|inc|m3u/i) { $content_type = 'text/plain'; } + elsif ($encoding =~ /html|htm|shtml|shtm|asp|php|cfm/i) { $content_type = 'text/html'; } + elsif ($encoding =~ /sh$/i) { $content_type = 'application/x-sh'; } + elsif ($encoding =~ /tcl/i) { $content_type = 'application/x-tcl'; } + elsif ($encoding =~ /pl$/i) { $content_type = 'application/x-perl'; } + elsif ($encoding =~ /js$/i) { $content_type = 'application/x-javascript'; } + elsif ($encoding =~ /man/i) { $content_type = 'application/x-troff-man'; } + elsif ($encoding =~ /gif/i) { $content_type = 'image/gif'; } + elsif ($encoding =~ /jpg|jpeg|jpe|jfif|pjpeg|pjp/i) { $content_type = 'image/jpeg'; } + elsif ($encoding =~ /tif|tiff/i) { $content_type = 'image/tiff'; } + elsif ($encoding =~ /xpm/i) { $content_type = 'image/x-xpixmap'; } + elsif ($encoding =~ /bmp/i) { $content_type = 'image/x-MS-bmp'; } + elsif ($encoding =~ /pcd/i) { $content_type = 'image/x-photo-cd'; } + elsif ($encoding =~ /png/i) { $content_type = 'image/png'; } + elsif ($encoding =~ /aif|aiff/i) { $content_type = 'audio/x-aiff'; } + elsif ($encoding =~ /wav/i) { $content_type = 'audio/x-wav'; } + elsif ($encoding =~ /mp2|mp3|mpa/i) { $content_type = 'audio/x-mpeg'; } + elsif ($encoding =~ /ra$|ram/i) { $content_type = 'audio/x-pn-realaudio'; } + elsif ($encoding =~ /mpeg|mpg/i) { $content_type = 'video/mpeg'; } + elsif ($encoding =~ /mov|qt$/i) { $content_type = 'video/quicktime'; } + elsif ($encoding =~ /avi/i) { $content_type = 'video/x-msvideo'; } + elsif ($encoding =~ /zip/i) { $content_type = 'application/x-zip-compressed'; } + elsif ($encoding =~ /tar/i) { $content_type = 'application/x-tar'; } + elsif ($encoding =~ /jar/i) { $content_type = 'application/java-archive'; } + elsif ($encoding =~ /exe|bin/i) { $content_type = 'application/octet-stream'; } + elsif ($encoding =~ /ppt|pot|ppa|pps|pwz/i) { $content_type = 'application/vnd.ms-powerpoint'; } + elsif ($encoding =~ /mdb|mda|mde/i) { $content_type = 'application/vnd.ms-access'; } + elsif ($encoding =~ /xls|xlt|xlm|xld|xla|xlc|xlw|xll/i) { $content_type = 'application/vnd.ms-excel'; } + elsif ($encoding =~ /doc|dot/i) { $content_type = 'application/msword'; } + elsif ($encoding =~ /rtf/i) { $content_type = 'application/rtf'; } + elsif ($encoding =~ /pdf/i) { $content_type = 'application/pdf'; } + elsif ($encoding =~ /tex/i) { $content_type = 'application/x-tex'; } + elsif ($encoding =~ /latex/i) { $content_type = 'application/x-latex'; } + elsif ($encoding =~ /vcf/i) { $content_type = 'application/x-vcard'; } + else { $content_type = 'application/octet-stream'; } + + + ############################ + ## Process the attachment ## + ############################ + + ##################################### + ## Generate and print MIME headers ## + ##################################### + + $y = "$CRLF--$conf{'delimiter'}$CRLF"; + $y .= "Content-Type: $content_type;$CRLF"; + $y .= " name=\"$filename_name\"$CRLF"; + $y .= "Content-Transfer-Encoding: base64$CRLF"; + $y .= "Content-Disposition: attachment; filename=\"$filename_name\"$CRLF"; + $y .= "$CRLF"; + print $SERVER $y; + + + ########################################################### + ## Convert the file to base64 and print it to the server ## + ########################################################### + + open (FILETOATTACH, $filename) || do { + printmsg("ERROR => Opening the file [$filename] for attachment failed with the error: $!", 0); + return(1); + }; + binmode(FILETOATTACH); ## Hack to make Win32 work + + my $res = ""; + my $tmp = ""; + my $base64 = ""; + while () { ## Read a line from the (binary) file + $res .= $_; + + ################################### + ## Convert binary data to base64 ## + ################################### + while ($res =~ s/(.{45})//s) { ## Get 45 bytes from the binary string + $tmp = substr(pack('u', $&), 1); ## Convert the binary to uuencoded text + chop($tmp); + $tmp =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 + $base64 .= $tmp; + } + + ################################ + ## Print chunks to the server ## + ################################ + while ($base64 =~ s/(.{76})//s) { + print $SERVER "$1$CRLF"; + } + + } + + ################################### + ## Encode and send the leftovers ## + ################################### + my $padding = ""; + if ( ($res) and (length($res) >= 1) ) { + $padding = (3 - length($res) % 3) % 3; ## Set flag if binary data isn't divisible by 3 + $res = substr(pack('u', $res), 1); ## Convert the binary to uuencoded text + chop($res); + $res =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 + } + + ############################ + ## Fix padding at the end ## + ############################ + $res = $base64 . $res; ## Get left overs from above + $res =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set + if ($res) { + while ($res =~ s/(.{1,76})//s) { ## Send it to the email server. + print $SERVER "$1$CRLF"; + } + } + + close (FILETOATTACH) || do { + printmsg("ERROR - Closing the filehandle for file [$filename] failed with the error: $!", 0); + return(2); + }; + + ## Return 0 errors + return(0); + +} + + + + + + + + + +############################################################################################### +## Function: $string = get_hostname (boot $fqdn) +## +## Description: Tries really hard to returns the short (or FQDN) hostname of the current +## system. Uses techniques and code from the Sys-Hostname module. +## +## Input: $fqdn A true value (1) will cause this function to return a FQDN hostname +## rather than a short hostname. +## +## Output: Returns a string +############################################################################################### +sub get_hostname { + ## Assign incoming parameters to variables + my ( $fqdn ) = @_; + my $hostname = ""; + + ## STEP 1: Get short hostname + + ## Load Sys::Hostname if it's available + eval { require Sys::Hostname; }; + unless ($@) { + $hostname = Sys::Hostname::hostname(); + } + + ## If that didn't get us a hostname, try a few other things + else { + ## Windows systems + if ($^O !~ /win/i) { + if ($ENV{'COMPUTERNAME'}) { $hostname = $ENV{'COMPUTERNAME'}; } + if (!$hostname) { $hostname = gethostbyname('localhost'); } + if (!$hostname) { chomp($hostname = `hostname 2> NUL`) }; + } + + ## Unix systems + else { + local $ENV{PATH} = '/usr/bin:/bin:/usr/sbin:/sbin'; ## Paranoia + + ## Try the environment first (Help! What other variables could/should I be checking here?) + if ($ENV{'HOSTNAME'}) { $hostname = $ENV{'HOSTNAME'}; } + + ## Try the hostname command + eval { local $SIG{__DIE__}; local $SIG{CHLD}; $hostname = `hostname 2>/dev/null`; chomp($hostname); } || + + ## Try POSIX::uname(), which strictly can't be expected to be correct + eval { local $SIG{__DIE__}; require POSIX; $hostname = (POSIX::uname())[1]; } || + + ## Try the uname command + eval { local $SIG{__DIE__}; $hostname = `uname -n 2>/dev/null`; chomp($hostname); }; + + } + + ## If we can't find anything else, return "" + if (!$hostname) { + print "WARNING => No hostname could be determined, please specify one with -o fqdn=FQDN option!\n"; + return("unknown"); + } + } + + ## Return the short hostname + unless ($fqdn) { + $hostname =~ s/\..*//; + return(lc($hostname)); + } + + ## STEP 2: Determine the FQDN + + ## First, if we already have one return it. + if ($hostname =~ /\w\.\w/) { return(lc($hostname)); } + + ## Next try using + eval { $fqdn = (gethostbyname($hostname))[0]; }; + if ($fqdn) { return(lc($fqdn)); } + return(lc($hostname)); +} + + + + + + + + +############################################################################################### +## Function: printmsg (string $message, int $level) +## +## Description: Handles all messages - printing them to the screen only if the messages +## $level is >= the global debug level. If $conf{'logFile'} is defined it +## will also log the message to that file. +## +## Input: $message A message to be printed, logged, etc. +## $level The debug level of the message. If +## not defined 0 will be assumed. 0 is +## considered a normal message, 1 and +## higher is considered a debug message. +## +## Output: Prints to STDOUT +## +## Assumptions: $conf{'hostname'} should be the name of the computer we're running on. +## $conf{'stdout'} should be set to 1 if you want to print to stdout +## $conf{'logFile'} should be a full path to a log file if you want that +## $conf{'debug'} should be an integer between 0 and 10. +## +## Example: printmsg("WARNING: We believe in generic error messages... NOT!", 0); +############################################################################################### +sub printmsg { + ## Assign incoming parameters to variables + my ( $message, $level ) = @_; + + ## Make sure input is sane + $level = 0 if (!defined($level)); + $message =~ s/\s+$//sgo; + $message =~ s/\r?\n/, /sgo; + + ## Continue only if the debug level of the program is >= message debug level. + if ($conf{'debug'} >= $level) { + + ## Get the date in the format: Dec 3 11:14:04 + my ($sec, $min, $hour, $mday, $mon) = localtime(); + $mon = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')[$mon]; + my $date = sprintf("%s %02d %02d:%02d:%02d", $mon, $mday, $hour, $min, $sec); + + ## Print to STDOUT always if debugging is enabled, or if conf{stdout} is true. + if ( ($conf{'debug'} >= 1) or ($conf{'stdout'} == 1) ) { + print "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n"; + } + + ## Print to the log file if $conf{'logging'} is true + if ($conf{'logFile'}) { + if (openLogFile($conf{'logFile'})) { $conf{'logFile'} = ""; printmsg("ERROR => Opening the file [$conf{'logFile'}] for appending returned the error: $!", 1); } + print LOGFILE "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n"; + } + + } + + ## Return 0 errors + return(0); +} + + + + + + + + + + + + +############################################################################################### +## FUNCTION: +## openLogFile ( $filename ) +## +## +## DESCRIPTION: +## Opens the file $filename and attaches it to the filehandle "LOGFILE". Returns 0 on success +## and non-zero on failure. Error codes are listed below, and the error message gets set in +## global variable $!. +## +## +## Example: +## openFile ("/var/log/sendEmail.log"); +## +############################################################################################### +sub openLogFile { + ## Get the incoming filename + my $filename = $_[0]; + + ## Make sure our file exists, and if the file doesn't exist then create it + if ( ! -f $filename ) { + print STDERR "NOTICE: The log file [$filename] does not exist. Creating it now with mode [0600].\n" if ($conf{'stdout'}); + open (LOGFILE, ">>$filename"); + close LOGFILE; + chmod (0600, $filename); + } + + ## Now open the file and attach it to a filehandle + open (LOGFILE,">>$filename") or return (1); + + ## Put the file into non-buffering mode + select LOGFILE; + $| = 1; + select STDOUT; + + ## Return success + return(0); +} + + + + + + + + +############################################################################################### +## Function: read_file (string $filename) +## +## Description: Reads the contents of a file and returns a two part array: +## ($status, $file-contents) +## $status is 0 on success, non-zero on error. +## +## Example: ($status, $file) = read_file("/etc/passwd"); +############################################################################################### +sub read_file { + my ( $filename ) = @_; + + ## If the value specified is a file, load the file's contents + if ( (-e $filename and -r $filename) ) { + my $FILE; + if(!open($FILE, ' ' . $filename)) { + return((1, "")); + } + my $file = ''; + while (<$FILE>) { + $file .= $_; + } + ## Strip an ending \r\n + $file =~ s/\r?\n$//os; + } + return((1, "")); +} + + + + + + + + + +############################################################################################### +## Function: quit (string $message, int $errorLevel) +## +## Description: Exits the program, optionally printing $message. It +## returns an exit error level of $errorLevel to the +## system (0 means no errors, and is assumed if empty.) +## +## Example: quit("Exiting program normally", 0); +############################################################################################### +sub quit { + my ( $message, $errorLevel ) = @_; + $errorLevel = 0 if (!defined($errorLevel)); + + ## Print exit message + if ($message) { + printmsg($message, 0); + } + + ## Exit + exit($errorLevel); +} + + + + + + + + + + + + +############################################################################################### +## Function: help () +## +## Description: For all those newbies ;) +## Prints a help message and exits the program. +## +############################################################################################### +sub help { +exit(1) if (!$conf{'stdout'}); +print <${colorNoBold} + +Synopsis: $conf{'programName'} -f ADDRESS [options] + + ${colorRed}Required:${colorNormal} + -f ADDRESS from (sender) email address + * At least one recipient required via -t, -cc, or -bcc + * Message body required via -m, STDIN, or -o message-file=FILE + + ${colorGreen}Common:${colorNormal} + -t ADDRESS [ADDR ...] to email address(es) + -u SUBJECT message subject + -m MESSAGE message body + -s SERVER[:PORT] smtp mail relay, default is $conf{'server'}:$conf{'port'} + + ${colorGreen}Optional:${colorNormal} + -a FILE [FILE ...] file attachment(s) + -cc ADDRESS [ADDR ...] cc email address(es) + -bcc ADDRESS [ADDR ...] bcc email address(es) + -xu USERNAME username for SMTP authentication + -xp PASSWORD password for SMTP authentication + + ${colorGreen}Paranormal:${colorNormal} + -b BINDADDR[:PORT] local host bind address + -l LOGFILE log to the specified file + -v verbosity, use multiple times for greater effect + -q be quiet (i.e. no STDOUT output) + -o NAME=VALUE advanced options, for details try: --help misc + -o message-content-type= + -o message-file=FILE -o message-format=raw + -o message-header=HEADER -o message-charset=CHARSET + -o reply-to=ADDRESS -o timeout=SECONDS + -o username=USERNAME -o password=PASSWORD + -o tls= -o fqdn=FQDN + + + ${colorGreen}Help:${colorNormal} + --help the helpful overview you're reading now + --help addressing explain addressing and related options + --help message explain message body input and related options + --help networking explain -s, -b, etc + --help output explain logging and other output options + --help misc explain -o options, TLS, SMTP auth, and more + +EOM +exit(1); +} + + + + + + + + + +############################################################################################### +## Function: helpTopic ($topic) +## +## Description: For all those newbies ;) +## Prints a help message and exits the program. +## +############################################################################################### +sub helpTopic { + exit(1) if (!$conf{'stdout'}); + my ($topic) = @_; + + CASE: { + + + + +## ADDRESSING + ($topic eq 'addressing') && do { + print <" + Just Address: "john.doe\@gmail.com" + +The "Full Name" method is useful if you want a name, rather than a plain +email address, to be displayed in the recipient's From, To, or Cc fields +when they view the message. + + +${colorGreen}Multiple Recipients${colorNormal} +The -t, -cc, and -bcc options each accept multiple addresses. They may be +specified by separating them by either a white space, comma, or semi-colon +separated list. You may also specify the -t, -cc, and -bcc options multiple +times, each occurance will append the new recipients to the respective list. + +Examples: +(I used "-t" in these examples, but it can be "-cc" or "-bcc" as well) + + * Space separated list: + -t jane.doe\@yahoo.com "John Doe " + + * Semi-colon separated list: + -t "jane.doe\@yahoo.com; John Doe " + + * Comma separated list: + -t "jane.doe\@yahoo.com, John Doe " + + * Multiple -t, -cc, or -bcc options: + -t "jane.doe\@yahoo.com" -t "John Doe " + + +EOM + last CASE; + }; + + + + + + +## MESSAGE + ($topic eq 'message') && do { + print < + -o message-header=EMAIL HEADER + -o message-charset=CHARSET + -o message-format=raw + +-u SUBJECT + This option allows you to specify the subject for your email message. + It is not required (anymore) that the subject be quoted, although it + is recommended. The subject will be read until an argument starting + with a hyphen (-) is found. + Examples: + -u "Contact information while on vacation" + -u New Microsoft vulnerability discovered + +-m MESSAGE + This option is one of three methods that allow you to specify the message + body for your email. The message may be specified on the command line + with this -m option, read from a file with the -o message-file=FILE + option, or read from STDIN if neither of these options are present. + + It is not required (anymore) that the message be quoted, although it is + recommended. The message will be read until an argument starting with a + hyphen (-) is found. + Examples: + -m "See you in South Beach, Hawaii. -Todd" + -m Please ensure that you upgrade your systems right away + + Multi-line message bodies may be specified with the -m option by putting + a "\\n" into the message. Example: + -m "This is line 1.\\nAnd this is line 2." + + HTML messages are supported, simply begin your message with "" and + sendEmail will properly label the mime header so MUAs properly render + the message. It is currently not possible without "-o message-format=raw" + to send a message with both text and html parts with sendEmail. + +-o message-file=FILE + This option is one of three methods that allow you to specify the message + body for your email. To use this option simply specify a text file + containing the body of your email message. Examples: + -o message-file=/root/message.txt + -o message-file="C:\\Program Files\\output.txt" + +-o message-content-type= + This option allows you to specify the content-type of the email. If your + email message is an html message but is being displayed as a text message + just add "-o message-content-type=html" to the command line to force it + to display as an html message. This actually just changes the Content-Type: + header. Advanced users will be happy to know that if you specify anything + other than the three options listed above it will use that as the vaule + for the Content-Type header. + +-o message-header=EMAIL HEADER + This option allows you to specify additional email headers to be included. + To add more than one message header simply use this option on the command + line more than once. If you specify a message header that sendEmail would + normally generate the one you specified will be used in it's place. + Do not use this unless you know what you are doing! + Example: + To scare a Microsoft Outlook user you may want to try this: + -o message-header="X-Message-Flag: Message contains illegal content" + Example: + To request a read-receipt try this: + -o message-header="Disposition-Notification-To: " + Example: + To set the message priority try this: + -o message-header="X-Priority: 1" + Priority reference: 1=highest, 2=high, 3=normal, 4=low, 5=lowest + +-o message-charset=CHARSET + This option allows you to specify the character-set for the message body. + The default is iso-8859-1. + +-o message-format=raw + This option instructs sendEmail to assume the message (specified with -m, + read from STDIN, or read from the file specified in -o message-file=FILE) + is already a *complete* email message. SendEmail will not generate any + headers and will transmit the message as-is to the remote SMTP server. + Due to the nature of this option the following command line options will + be ignored when this one is used: + -u SUBJECT + -o message-header=EMAIL HEADER + -o message-charset=CHARSET + -a ATTACHMENT + + +${colorGreen}The Message Body${colorNormal} +The email message body may be specified in one of three ways: + 1) Via the -m MESSAGE command line option. + Example: + -m "This is the message body" + + 2) By putting the message body in a file and using the -o message-file=FILE + command line option. + Example: + -o message-file=/root/message.txt + + 3) By piping the message body to sendEmail when nither of the above command + line options were specified. + Example: + grep "ERROR" /var/log/messages | sendEmail -t you\@domain.com ... + +If the message body begins with "" then the message will be treated as +an HTML message and the MIME headers will be written so that a HTML capable +email client will display the message in it's HTML form. +Any of the above methods may be used with the -o message-format=raw option +to deliver an already complete email message. + + +EOM + last CASE; + }; + + + + + + +## MISC + ($topic eq 'misc') && do { + print < + -o timeout=SECONDS + -o fqdn=FQDN + +-a ATTACHMENT [ATTACHMENT ...] + This option allows you to attach any number of files to your email message. + To specify more than one attachment, simply separate each filename with a + space. Example: -a file1.txt file2.txt file3.txt + +-xu USERNAME + Alias for -o username=USERNAME + +-xp PASSWORD + Alias for -o password=PASSWORD + +-o username=USERNAME (synonym for -xu) + These options allow specification of a username to be used with SMTP + servers that require authentication. If a username is specified but a + password is not, you will be prompted to enter one at runtime. + +-o password=PASSWORD (synonym for -xp) + These options allow specification of a password to be used with SMTP + servers that require authentication. If a username is specified but a + password is not, you will be prompted to enter one at runtime. + +-o tls= + This option allows you to specify if TLS (SSL for SMTP) should be enabled + or disabled. The default, auto, will use TLS automatically if your perl + installation has the IO::Socket::SSL and Net::SSLeay modules available, + and if the remote SMTP server supports TLS. To require TLS for message + delivery set this to yes. To disable TLS support set this to no. A debug + level of one or higher will reveal details about the status of TLS. + +-o timeout=SECONDS + This option sets the timeout value in seconds used for all network reads, + writes, and a few other things. + +-o fqdn=FQDN + This option sets the Fully Qualified Domain Name used during the initial + SMTP greeting. Normally this is automatically detected, but in case you + need to manually set it for some reason or get a warning about detection + failing, you can use this to override the default. + + +EOM + last CASE; + }; + + + + + + +## NETWORKING + ($topic eq 'networking') && do { + print < + -o timeout=SECONDS + +-s SERVER[:PORT] + This option allows you to specify the SMTP server sendEmail should + connect to to deliver your email message to. If this option is not + specified sendEmail will try to connect to localhost:25 to deliver + the message. THIS IS MOST LIKELY NOT WHAT YOU WANT, AND WILL LIKELY + FAIL unless you have a email server (commonly known as an MTA) running + on your computer! + Typically you will need to specify your company or ISP's email server. + For example, if you use CableOne you will need to specify: + -s mail.cableone.net + If you have your own email server running on port 300 you would + probably use an option like this: + -s myserver.mydomain.com:300 + If you're a GMail user try: + -s smtp.gmail.com:587 -xu me\@gmail.com -xp PASSWD + +-b BINDADDR[:PORT] + This option allows you to specify the local IP address (and optional + tcp port number) for sendEmail to bind to when connecting to the remote + SMTP server. This useful for people who need to send an email from a + specific network interface or source address and are running sendEmail on + a firewall or other host with several network interfaces. + +-o tls= + This option allows you to specify if TLS (SSL for SMTP) should be enabled + or disabled. The default, auto, will use TLS automatically if your perl + installation has the IO::Socket::SSL and Net::SSLeay modules available, + and if the remote SMTP server supports TLS. To require TLS for message + delivery set this to yes. To disable TLS support set this to no. A debug + level of one or higher will reveal details about the status of TLS. + +-o timeout=SECONDS + This option sets the timeout value in seconds used for all network reads, + writes, and a few other things. + + +EOM + last CASE; + }; + + + + + + +## OUTPUT + ($topic eq 'output') && do { + print < The help topic specified is not valid!", 1); + }; + +exit(1); +} + + + + + + + + + + + + + + + + + + + + + + +############################# +## ## +## MAIN PROGRAM ## +## ## +############################# + + +## Initialize +initialize(); + +## Process Command Line +processCommandLine(); +$conf{'alarm'} = $opt{'timeout'}; + +## Abort program after $conf{'alarm'} seconds to avoid infinite hangs +alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32 + + + + +################################################### +## Read $message from STDIN if -m was not used ## +################################################### + +if (!($message)) { + ## Read message body from a file specified with -o message-file= + if ($opt{'message-file'}) { + if (! -e $opt{'message-file'}) { + printmsg("ERROR => Message body file specified [$opt{'message-file'}] does not exist!", 0); + printmsg("HINT => 1) check spelling of your file; 2) fully qualify the path; 3) doubble quote it", 1); + quit("", 1); + } + if (! -r $opt{'message-file'}) { + printmsg("ERROR => Message body file specified can not be read due to restricted permissions!", 0); + printmsg("HINT => Check permissions on file specified to ensure it can be read", 1); + quit("", 1); + } + if (!open(MFILE, "< " . $opt{'message-file'})) { + printmsg("ERROR => Error opening message body file [$opt{'message-file'}]: $!", 0); + quit("", 1); + } + while () { + $message .= $_; + } + close(MFILE); + } + + ## Read message body from STDIN + else { + alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32 + if ($conf{'stdout'}) { + print "Reading message body from STDIN because the '-m' option was not used.\n"; + print "If you are manually typing in a message:\n"; + print " - First line must be received within $conf{'alarm'} seconds.\n" if ($^O !~ /win/i); + print " - End manual input with a CTRL-D on its own line.\n\n" if ($^O !~ /win/i); + print " - End manual input with a CTRL-Z on its own line.\n\n" if ($^O =~ /win/i); + } + while () { ## Read STDIN into $message + $message .= $_; + alarm(0) if ($^O !~ /win/i); ## Disable the alarm since at least one line was received + } + printmsg("Message input complete.", 0); + } +} + +## Replace bare LF's with CRLF's (\012 should always have \015 with it) +$message =~ s/(\015)?(\012|$)/\015\012/g; + +## Replace bare CR's with CRLF's (\015 should always have \012 with it) +$message =~ s/(\015)(\012|$)?/\015\012/g; + +## Check message for bare periods and encode them +$message =~ s/(^|$CRLF)(\.{1})($CRLF|$)/$1.$2$3/g; + +## Get the current date for the email header +my ($sec,$min,$hour,$mday,$mon,$year,$day) = gmtime(); +$year += 1900; $mon = return_month($mon); $day = return_day($day); +my $date = sprintf("%s, %s %s %d %.2d:%.2d:%.2d %s",$day, $mday, $mon, $year, $hour, $min, $sec, $conf{'timezone'}); + + + + +################################## +## Connect to the SMTP server ## +################################## +printmsg("DEBUG => Connecting to $conf{'server'}:$conf{'port'}", 1); +$SIG{'ALRM'} = sub { + printmsg("ERROR => Timeout while connecting to $conf{'server'}:$conf{'port'} There was no response after $conf{'alarm'} seconds.", 0); + printmsg("HINT => Try specifying a different mail relay with the -s option.", 1); + quit("", 1); +}; +alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32; +$SERVER = IO::Socket::INET->new( PeerAddr => $conf{'server'}, + PeerPort => $conf{'port'}, + LocalAddr => $conf{'bindaddr'}, + Proto => 'tcp', + Autoflush => 1, + timeout => $conf{'alarm'}, +); +alarm(0) if ($^O !~ /win/i); ## alarm() doesn't work in win32; + +## Make sure we got connected +if ( (!$SERVER) or (!$SERVER->opened()) ) { + printmsg("ERROR => Connection attempt to $conf{'server'}:$conf{'port'} failed: $@", 0); + printmsg("HINT => Try specifying a different mail relay with the -s option.", 1); + quit("", 1); +} + +## Save our IP address for later +$conf{'ip'} = $SERVER->sockhost(); +printmsg("DEBUG => My IP address is: $conf{'ip'}", 1); + + + + + + + +######################### +## Do the SMTP Dance ## +######################### + +## Read initial greeting to make sure we're talking to a live SMTP server +if (SMTPchat()) { quit($conf{'error'}, 1); } + +## We're about to use $opt{'fqdn'}, make sure it isn't empty +if (!$opt{'fqdn'}) { + ## Ok, that means we couldn't get a hostname, how about using the IP address for the HELO instead + $opt{'fqdn'} = "[" . $conf{'ip'} . "]"; +} + +## EHLO +if (SMTPchat('EHLO ' . $opt{'fqdn'})) { + printmsg($conf{'error'}, 0); + printmsg("NOTICE => EHLO command failed, attempting HELO instead"); + if (SMTPchat('HELO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); } + if ( $opt{'username'} and $opt{'password'} ) { + printmsg("WARNING => The mail server does not support SMTP authentication!", 0); + } +} +else { + + ## Determin if the server supports TLS + if ($conf{'SMTPchat_response'} =~ /STARTTLS/) { + $conf{'tls_server'} = 1; + printmsg("DEBUG => The remote SMTP server supports TLS :)", 2); + } + else { + $conf{'tls_server'} = 0; + printmsg("DEBUG => The remote SMTP server does NOT support TLS :(", 2); + } + + ## Start TLS if possible + if ($conf{'tls_server'} == 1 and $conf{'tls_client'} == 1 and $opt{'tls'} =~ /^(yes|auto)$/) { + printmsg("DEBUG => Starting TLS", 2); + if (SMTPchat('STARTTLS')) { quit($conf{'error'}, 1); } + if (! IO::Socket::SSL->start_SSL($SERVER, SSL_version => 'SSLv23:!SSLv3:!SSLv2', , SSL_verify_mode => 0)) { + quit("ERROR => TLS setup failed: " . IO::Socket::SSL::errstr(), 1); + } + printmsg("DEBUG => TLS: Using cipher: ". $SERVER->get_cipher(), 3); + printmsg("DEBUG => TLS session initialized :)", 1); + + ## Restart our SMTP session + if (SMTPchat('EHLO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); } + } + elsif ($opt{'tls'} eq 'yes' and $conf{'tls_server'} == 0) { + quit("ERROR => TLS not possible! Remote SMTP server, $conf{'server'}, does not support it.", 1); + } + + + ## Do SMTP Auth if required + if ( $opt{'username'} and $opt{'password'} ) { + if ($conf{'SMTPchat_response'} !~ /AUTH\s/) { + printmsg("NOTICE => Authentication not supported by the remote SMTP server!", 0); + } + else { + my $auth_succeeded = 0; + my $mutual_method = 0; + + # ## SASL CRAM-MD5 authentication method + # if ($conf{'SMTPchat_response'} =~ /\bCRAM-MD5\b/i) { + # printmsg("DEBUG => SMTP-AUTH: Using CRAM-MD5 authentication method", 1); + # if (SMTPchat('AUTH CRAM-MD5')) { quit($conf{'error'}, 1); } + # + # ## FIXME!! + # + # printmsg("DEBUG => User authentication was successful", 1); + # } + + ## SASL LOGIN authentication method + if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bLOGIN\b/i) { + $mutual_method = 1; + printmsg("DEBUG => SMTP-AUTH: Using LOGIN authentication method", 1); + if (!SMTPchat('AUTH LOGIN')) { + if (!SMTPchat(base64_encode($opt{'username'}))) { + if (!SMTPchat(base64_encode($opt{'password'}))) { + $auth_succeeded = 1; + printmsg("DEBUG => User authentication was successful (Method: LOGIN)", 1); + } + } + } + if ($auth_succeeded == 0) { + printmsg("DEBUG => SMTP-AUTH: LOGIN authenticaion failed.", 1); + } + } + + ## SASL PLAIN authentication method + if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bPLAIN\b/i) { + $mutual_method = 1; + printmsg("DEBUG => SMTP-AUTH: Using PLAIN authentication method", 1); + if (SMTPchat('AUTH PLAIN ' . base64_encode("$opt{'username'}\0$opt{'username'}\0$opt{'password'}"))) { + printmsg("DEBUG => SMTP-AUTH: PLAIN authenticaion failed.", 1); + } + else { + $auth_succeeded = 1; + printmsg("DEBUG => User authentication was successful (Method: PLAIN)", 1); + } + } + + ## If none of the authentication methods supported by sendEmail were supported by the server, let the user know + if ($mutual_method == 0) { + printmsg("WARNING => SMTP-AUTH: No mutually supported authentication methods available", 0); + } + + ## If we didn't get authenticated, log an error message and exit + if ($auth_succeeded == 0) { + quit("ERROR => ERROR => SMTP-AUTH: Authentication to $conf{'server'}:$conf{'port'} failed.", 1); + } + } + } +} + +## MAIL FROM +if (SMTPchat('MAIL FROM:<' .(returnAddressParts($from))[1]. '>')) { quit($conf{'error'}, 1); } + +## RCPT TO +my $oneRcptAccepted = 0; +foreach my $rcpt (@to, @cc, @bcc) { + my ($name, $address) = returnAddressParts($rcpt); + if (SMTPchat('RCPT TO:<' . $address . '>')) { + printmsg("WARNING => The recipient <$address> was rejected by the mail server, error follows:", 0); + $conf{'error'} =~ s/^ERROR/WARNING/o; + printmsg($conf{'error'}, 0); + } + elsif ($oneRcptAccepted == 0) { + $oneRcptAccepted = 1; + } +} +## If no recipients were accepted we need to exit with an error. +if ($oneRcptAccepted == 0) { + quit("ERROR => Exiting. No recipients were accepted for delivery by the mail server.", 1); +} + +## DATA +if (SMTPchat('DATA')) { quit($conf{'error'}, 1); } + + +############################### +## Build and send the body ## +############################### +printmsg("INFO => Sending message body",1); + +## If the message-format is raw just send the message as-is. +if ($opt{'message-format'} =~ /^raw$/i) { + print $SERVER $message; +} + +## If the message-format isn't raw, then build and send the message, +else { + + ## Message-ID: + if ($opt{'message-header'} !~ /^Message-ID:/iom) { + $header .= 'Message-ID: <' . $conf{'Message-ID'} . '@' . $conf{'hostname'} . '>' . $CRLF; + } + + ## From: "Name" (the pointless test below is just to keep scoping correct) + if ($from and $opt{'message-header'} !~ /^From:/iom) { + my ($name, $address) = returnAddressParts($from); + $header .= 'From: "' . $name . '" <' . $address . '>' . $CRLF; + } + + ## Reply-To: + if ($opt{'reply-to'} and $opt{'message-header'} !~ /^Reply-To:/iom) { + my ($name, $address) = returnAddressParts($opt{'reply-to'}); + $header .= 'Reply-To: "' . $name . '" <' . $address . '>' . $CRLF; + } + + ## To: "Name" + if ($opt{'message-header'} =~ /^To:/iom) { + ## The user put the To: header in via -o message-header - dont do anything + } + elsif (scalar(@to) > 0) { + $header .= "To:"; + for (my $a = 0; $a < scalar(@to); $a++) { + my $msg = ""; + + my ($name, $address) = returnAddressParts($to[$a]); + $msg = " \"$name\" <$address>"; + + ## If we're not on the last address add a comma to the end of the line. + if (($a + 1) != scalar(@to)) { + $msg .= ","; + } + + $header .= $msg . $CRLF; + } + } + ## We always want a To: line so if the only recipients were bcc'd they don't see who it was sent to + else { + $header .= "To: \"Undisclosed Recipients\" <>$CRLF"; + } + + if (scalar(@cc) > 0 and $opt{'message-header'} !~ /^Cc:/iom) { + $header .= "Cc:"; + for (my $a = 0; $a < scalar(@cc); $a++) { + my $msg = ""; + + my ($name, $address) = returnAddressParts($cc[$a]); + $msg = " \"$name\" <$address>"; + + ## If we're not on the last address add a comma to the end of the line. + if (($a + 1) != scalar(@cc)) { + $msg .= ","; + } + + $header .= $msg . $CRLF; + } + } + + if ($opt{'message-header'} !~ /^Subject:/iom) { + $header .= 'Subject: ' . $subject . $CRLF; ## Subject + } + if ($opt{'message-header'} !~ /^Date:/iom) { + $header .= 'Date: ' . $date . $CRLF; ## Date + } + if ($opt{'message-header'} !~ /^X-Mailer:/iom) { + $header .= 'X-Mailer: sendEmail-'.$conf{'version'}.$CRLF; ## X-Mailer + } + ## I wonder if I should put this in by default? + # if ($opt{'message-header'} !~ /^X-Originating-IP:/iom) { + # $header .= 'X-Originating-IP: ['.$conf{'ip'}.']'.$CRLF; ## X-Originating-IP + # } + + ## Encode all messages with MIME. + if ($opt{'message-header'} !~ /^MIME-Version:/iom) { + $header .= "MIME-Version: 1.0$CRLF"; + } + if ($opt{'message-header'} !~ /^Content-Type:/iom) { + my $content_type = 'multipart/mixed'; + if (scalar(@attachments) == 0) { $content_type = 'multipart/related'; } + $header .= "Content-Type: $content_type; boundary=\"$conf{'delimiter'}\"$CRLF"; + } + + ## Send additional message header line(s) if specified + if ($opt{'message-header'}) { + $header .= $opt{'message-header'}; + } + + ## Send the message header to the server + print $SERVER $header . $CRLF; + + ## Start sending the message body to the server + print $SERVER "This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.$CRLF"; + print $SERVER "$CRLF"; + + + ## Send message body + print $SERVER "--$conf{'delimiter'}$CRLF"; + ## Send a message content-type header: + ## If the message contains HTML... + if ($opt{'message-content-type'} eq 'html' or ($opt{'message-content-type'} eq 'auto' and $message =~ /^\s*( 0) { + ## Disable the alarm so people on modems can send big attachments + alarm(0) if ($^O !~ /win/i); ## alarm() doesn't work in win32 + + ## Send the attachments + foreach my $filename (@attachments) { + ## This is check 2, we already checked this above, but just in case... + if ( ! -f $filename ) { + printmsg("ERROR => The file [$filename] doesn't exist! Email will be sent, but without that attachment.", 0); + } + elsif ( ! -r $filename ) { + printmsg("ERROR => Couldn't open the file [$filename] for reading: $! Email will be sent, but without that attachment.", 0); + } + else { + printmsg("DEBUG => Sending the attachment [$filename]", 1); + send_attachment($filename); + } + } + } + + + ## End the mime encoded message + print $SERVER "$CRLF--$conf{'delimiter'}--$CRLF"; +} + + +## Tell the server we are done sending the email +print $SERVER "$CRLF.$CRLF"; +if (SMTPchat()) { quit($conf{'error'}, 1); } + + + +#################### +# We are done!!! # +#################### + +## Disconnect from the server (don't SMTPchat(), it breaks when using TLS) +print $SERVER "QUIT$CRLF"; +close $SERVER; + + + + + + +####################################### +## Generate exit message/log entry ## +####################################### + +if ($conf{'debug'} or $conf{'logging'}) { + printmsg("Generating a detailed exit message", 3); + + ## Put the message together + my $output = "Email was sent successfully! From: <" . (returnAddressParts($from))[1] . "> "; + + if (scalar(@to) > 0) { + $output .= "To: "; + for ($a = 0; $a < scalar(@to); $a++) { + $output .= "<" . (returnAddressParts($to[$a]))[1] . "> "; + } + } + if (scalar(@cc) > 0) { + $output .= "Cc: "; + for ($a = 0; $a < scalar(@cc); $a++) { + $output .= "<" . (returnAddressParts($cc[$a]))[1] . "> "; + } + } + if (scalar(@bcc) > 0) { + $output .= "Bcc: "; + for ($a = 0; $a < scalar(@bcc); $a++) { + $output .= "<" . (returnAddressParts($bcc[$a]))[1] . "> "; + } + } + $output .= "Subject: [$subject] " if ($subject); + if (scalar(@attachments_names) > 0) { + $output .= "Attachment(s): "; + foreach(@attachments_names) { + $output .= "[$_] "; + } + } + $output .= "Server: [$conf{'server'}:$conf{'port'}]"; + + +###################### +# Exit the program # +###################### + + ## Print / Log the detailed message + quit($output, 0); +} +else { + ## Or the standard message + quit("Email was sent successfully!", 0); +} + diff --git a/backoffice/api/upload.cgi b/backoffice/api/upload.cgi new file mode 100644 index 00000000..47c62fbf --- /dev/null +++ b/backoffice/api/upload.cgi @@ -0,0 +1,242 @@ +#!/usr/bin/perl + +use strict; +use lib ('./lib/perl5'); +use lib ('./lib'); +use CGI; +use CGI::Cookie; +#use CGI::Carp qw/fatalsToBrowser/; +use File::Basename; +use File::Path qw/make_path/; +use JSON::PP; +use Image::Size; +use dksconfig qw/$sitecfg/; +use dksdb; + +use session; +use sendemail; +my $cgi = new CGI(); +my $scriptpath = $cgi->url(-absolute => 1); +my $p = (); +my @params = $cgi->param(); +if ($cgi->request_method() eq "POST"){ + foreach my $pe (@params){ + $p->{$pe} = $cgi->param($pe); + } +} +my $html->{result} = (); +$p->{sid} = $cgi->cookie($sitecfg->{cookiename}); +my $se = session->new(); +my $sess = $se->getsession($p->{sid}); +print $cgi->header(-type=>"application/json", -charset => "utf-8");s +if ($sess == undef){ + $html->{error} = "No Authorisation"; + print JSON::PP::encode_json($html); + exit(0); +} +my $utime = time(); +my $db = dksdb->new(); +my $basepath = dirname(dirname($ENV{SCRIPT_FILENAME})); +if ($p->{fn}){ + if ($p->{fn} eq "setsubmission"){ + select * from cdms where id_project= + #select * from vw_submission where id_project= and is_cdm= + if (exists($p->{xlsxfile}) && $p->{xlsxfile} ne ""){ + my $fh = $cgi->upload('file'); + open(NF,">".$basepath.'/'.$filepath); + binmode NF; + while (<$fh>) { + print NF; + } + close(NF); + } + if (exists($p->{pdffile}) && $p->{pdffile} ne ""){ + my $fh = $cgi->upload('file'); + open(NF,">".$basepath.'/'.$filepath); + binmode NF; + while (<$fh>) { + print NF; + } + close(NF); + } + if (exists($p->{otherfile}) && $p->{otherfile} ne ""){ + my $fh = $cgi->upload('file'); + open(NF,">".$basepath.'/'.$filepath); + binmode NF; + while (<$fh>) { + print NF; + } + close(NF); + } + + } + + # my $filepath = ""; + # my $suffix = substr($p->{file},rindex($p->{file},'.')+1); + # if ($p->{filetype} eq "download_document"){ + # $filepath = 'data/documents/'.$p->{file}; + # } + # if ($p->{filetype} eq "news_image"){ + # if ($suffix eq 'jpg' || $suffix eq 'png'){ + # $filepath = '../media/news/'.$p->{file}; + # $p->{relpath} = 'media/news/'.$p->{file}; + # } + # } + #mogrify -thumbnail x300 -background white *.png + #ls -1 members_sized/*.png | awk -F\/ '{print "composite -gravity southeast wm_fld.png members_sized/"$(NF)" members/"$(NF)}' | sh + # if ($p->{upload_filetype} eq "new_profile_photo"){ + # my $sql = "select link from members where id=".$p->{id_member}.";"; + # my $ml = $db->dbquerysorted($sql); + # $filepath = 'data/members/'.$ml->{0}->{link}.'/'.$ml->{0}->{link}.'_new.'.$suffix; + # } elsif ($p->{upload_filetype} eq "profile_photo"){ + # my $sql = "select link from members where id=".$p->{id_member}.";"; + # my $ml = $db->dbquerysorted($sql); + # $filepath = 'data/members/'.$ml->{0}->{link}.'/'.$ml->{0}->{link}.'_new.'.$suffix; + # } elsif ($p->{upload_filetype} eq "spillbou"){ + # $filepath = 'data/championnat/'.$p->{season}.'_'.$p->{playday}.'_'.$p->{team}.'.'.$suffix; + # } elsif ($p->{upload_filetype} eq "member_document"){ + # my $sql = "select link from members where id=".$p->{id_member}.";"; + # my $ml = $db->dbquerysorted($sql); + # $filepath = 'data/members/'.$ml->{0}->{link}.'/'.$p->{upload_file}.'_'.$utime.$suffix; + # } elsif ($p->{upload_filetype} eq "clublogo_new"){ + # my $sql = "select link from clubs where id=".$p->{id_club}.";"; + # my $cl = $db->dbquerysorted($sql); + # $filepath = 'data/clubs/'.$cl->{0}->{link}.'/'.$cl->{0}->{link}.'_new.'.$suffix; + # } elsif ($p->{upload_filetype} eq "clublogo"){ + # my $sql = "select link from clubs where id=".$p->{id_club}.";"; + # my $cl = $db->dbquerysorted($sql); + # $filepath = 'data/clubs/'.$cl->{0}->{link}.'/'.$cl->{0}->{link}.'.'.$suffix; + # }elsif ($p->{upload_filetype} eq "website_image"){ + # $basepath = $ENV{"DOCUMENT_ROOT"}; + # $filepath = 'images/'.$p->{folder}.'/'.$p->{upload_file}; +# if ($filepath ne ""){ +# $p->{path} = $basepath.'/'.$filepath; +# $p->{suffix} = $suffix; +# my $fh = $cgi->upload('file'); +# open(NF,">".$basepath.'/'.$filepath); +# binmode NF; +# while (<$fh>) { +# print NF; +# } +# close(NF); +# if ($p->{filetype} eq "download_document"){ +# if ($suffix eq 'png' || $suffix eq 'jpg' || $suffix eq 'pdf'){ +# system('convert -thumbnail x128 -background white -alpha remove "'.$basepath.'/'.$filepath.'" "'.$basepath.'/thumb/'.$filepath.'.thumb.png"'); +# } + +# if ($p->{row_id} eq ""){ +# my $dx = $db->dbquerysorted("select id from documents where filename='".basename($filepath)."' and folder='documents'"); +# if (keys(%{$dx}) == 0){ +# my $sql = "INSERT INTO documents (filename,folder,filetype) VALUES ('".basename($filepath)."','documents','".$suffix."');"; +# $db->dbexec($sql); +# } +# } +# } +# if ($p->{filetype} eq "news_image"){ +# my ($pwidth,$pheight) =imgsize($p->{path}); +# my $width=0; +# my $height =0; +# my $density = 72; +# if ($pwidth>700){ +# $width = 700; +# } elsif ($height > 380) { +# $height = 380; +# } +# if ($height == 0){ +# $height = ($pheight/$pwidth) * $width; +# } +# elsif ($width == 0) { +# $width = ($pwidth/$pheight) * $height; +# } +# if ($height > 380){ +# $height = 380; +# $width = ($width/$height) * $height; +# } +# system("mogrify -resize ".$width."x".$height." -density ".$density." ".$p->{path}); +# } + +# # if ($p->{upload_filetype} eq "new_profile_photo"){ +# # #convert! +# # $db->dbexec("update members set new_profile_photo='".$filepath."' where id=".$p->{id_member}.";"); +# # $html->{result}->{new_profile_photo}= $filepath; +# # } elsif ($p->{upload_filetype} eq "profile_photo"){ +# # #convert! +# # $db->dbexec("update members set profile_photo='".$filepath."' where id=".$p->{id_member}.";"); +# # $html->{result}->{new_profile_photo}= $filepath; +# # } elsif ($p->{upload_filetype} eq "spillbou"){ +# # $db->dbexec("update csresult set upload_team".$p->{team}."='".$filepath."' where id=;"); +# # } elsif ($p->{upload_filetype} eq "member_document"){ + +# # } elsif ($p->{upload_filetype} eq "clublogo_new"){ + +# # } elsif ($p->{upload_filetype} eq "clublogo"){ + +# # } elsif ($p->{upload_filetype} eq "website_image"){ + +# # } els +# } +# } +# if (exists($p->{gameresult})){ +# my $basepath = dirname(dirname($ENV{SCRIPT_FILENAME})); +# my $csdir = "data/championship/"; +# my $gmid = $p->{id}; +# my $cdata = $db->dbquerybykey("id","select * from vw_games where id=".$gmid); + +# if ($p->{result_teamhome} ne ""){ +# my $suffix = substr($p->{upload_teamhome},rindex($p->{upload_teamhome},'.')+1); +# my $fh = $cgi->upload('upload_teamhome'); +# my $filepath = $basepath.'/'.$csdir.'/'.$cdata->{$gmid}->{season}.'/'.$cdata->{$gmid}->{playday}.'_'.$cdata->{$gmid}->{link_teamhome}.'.'.$suffix; +# open(NF,">".$filepath); +# binmode NF; +# while (<$fh>) { +# print NF; +# } +# close(NF); +# $db->dbexec("UPDATE csgames set upload_teamhome='".basename($filepath)."',result_teamhome='".$p->{result_teamhome}."' WHERE id=".$gmid); + +# #transform document an make thumbnail +# } +# if ($p->{result_teamguest} ne ""){ +# my $suffix = substr($p->{upload_teamguest},rindex($p->{upload_teamguest},'.')+1); +# my $fh = $cgi->upload('upload_teamguest'); +# my $filepath = $basepath.'/'.$csdir.'/'.$cdata->{$gmid}->{season}.'/'.$cdata->{$gmid}->{playday}.'_'.$cdata->{$gmid}->{link_teamguest}.'.'.$suffix; +# open(NF,">".$filepath); +# binmode NF; +# while (<$fh>) { +# print NF; +# } +# close(NF); +# $db->dbexec("UPDATE csgames set upload_teamguest='".basename($filepath)."',result_teamguest='".$p->{result_teamguest}."' WHERE id=".$gmid); +# #transform document an make thumbnail +# } +# $cdata = $db->dbquerybykey("id","select * from vw_games where id=".$gmid); +# if ($cdata->{$gmid}->{result_teamhome} eq $cdata->{$gmid}->{result_teamguest}){ + +# my $pth = "null"; +# my $ptg = "null"; +# my @x = (); +# if (($sess->{usergroups} =~ /fld/ ) && ($p->{sets_teamhome} ne "") && ($p->{sets_teamgoust} ne "")){ +# push(@x,$p->{sets_teamhome}); +# push(@x,$p->{sets_teamguest}); +# } else { +# @x= split("-",$cdata->{$gmid}->{result_teamhome}); +# } +# if (int($x[0]) > int($x[1])){$pth = 3;$ptg=0;} else {$pth = 0;$ptg=3;} +# if (int($x[0]) == int($x[1])){$pth = 1; $ptg = 1;} +# $db->dbexec("update csgames set sets_teamhome=".$x[0].", sets_teamguest=".$x[1].",points_teamhome=".$pth.",points_teamguest=".$ptg." WHERE id=".$gmid.";"); +# if (($sess->{usergroups} =~ /fld/ ) && ($p->{validated} eq "1")) { +# $db->dbexec("update csgames set validated=true WHERE id=".$gmid.";"); +# } +# } else { +# $db->dbexec("update csgames set sets_teamhome=null, sets_teamguest=null,points_teamhome=null,points_teamguest=null,validated=null WHERE id=".$gmid.";"); +# } +# $db->dbexec("select * from setcsranking('".$cdata->{$gmid}->{id_season}."','".$cdata->{$gmid}->{id_division}."');"); +# } +my $js = JSON::PP->new(); +$js->allow_blessed(1); +# open(NF,">upload.txt"); +# print NF $js->utf8->encode($p); +# print NF $js->utf8->encode($sess); +# close(NF); +$html->{result}->{p} = $p; +print $js->utf8->encode($p); \ No newline at end of file diff --git a/backoffice/css/module.css b/backoffice/css/module.css new file mode 100644 index 00000000..ca08b42f --- /dev/null +++ b/backoffice/css/module.css @@ -0,0 +1,8 @@ +/* input::-webkit-calendar-picker-indicator{ + display: none; +} +And to hide the prompt: + +input[type="date"]::-webkit-input-placeholder{ + visibility: hidden !important; +} */ \ No newline at end of file diff --git a/backoffice/css/w3pro.css b/backoffice/css/w3pro.css new file mode 100644 index 00000000..30134899 --- /dev/null +++ b/backoffice/css/w3pro.css @@ -0,0 +1,378 @@ +/* W3PRO.CSS 4.13 June 2019 by Jan Egil and Borge Refsnes */ +html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} +/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ +html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} +article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} +audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} +audio:not([controls]){display:none;height:0}[hidden],template{display:none} +a{background-color:transparent}a:active,a:hover{outline-width:0} +abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} +b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} +small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} +code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} +button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} +button,input{overflow:visible}button,select{text-transform:none} +button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} +button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} +button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} +fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} +legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} +[type=checkbox],[type=radio]{padding:0} +[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} +[type=search]{-webkit-appearance:textfield;outline-offset:-2px} +[type=search]::-webkit-search-decoration{-webkit-appearance:none} +::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} +/* End extract */ +html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} +h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}.w3-serif{font-family:serif} +h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin: 0}.w3-wide{letter-spacing:4px} +hr{border:0;border-top:1px solid #eee;margin:20px 0} +.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} +.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} +.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} +.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} +.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} +.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} +.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} +.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} +.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} +.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} +.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} +.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} +.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} +.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} +.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} +.w3-input{padding:8px;display:block;border:1px solid #ccc;width:100%;background-color: #e8f0fe; } +.w3-select{padding:9px 0; display:block;width:100%;border:1px solid #ccc;background-color: #e8f0fe;} +.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} +.w3-dropdown-hover:hover .w3-dropdown-content{display:block; } +.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} +.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} +.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} +.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} +.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} +.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} +.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} +.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} +.w3-main,#main{transition:margin-left .4s} +.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} +.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} +.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} +.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} +.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} +.w3-bar .w3-button{white-space:normal} +.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} +.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} +.w3-responsive{display:block;overflow-x:auto} +.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, +.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} +.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} +.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} +.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} +.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} +@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} +.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} +.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} +@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} +.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} +.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} +.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} +.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} +.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} +.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} +.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} +@media (max-width:1205px){.w3-auto{max-width:95%}} +@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} +.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} +.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} +.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} +@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} +@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} +@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} +@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} +.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} +.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} +.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} +.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} +.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} +.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} +.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} +.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} +.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} +.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} +.w3-display-position{position:absolute} +.w3-circle{border-radius:50%} +.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} +.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} +.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} +.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} +.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} +.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} +.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16)} +.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} +.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} +.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} +.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} +.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} +.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} +.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} +.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} +.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} +.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} +.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} +.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} +.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} +.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} +.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} +.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} +.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} +.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} +.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} +.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} +.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} +.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} +.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} +.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} +.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} +.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} +.w3-left{float:left!important}.w3-right{float:right!important} +.w3-button:hover{color:#000!important;background-color:#ccc!important} +.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} +.w3-hover-none:hover{box-shadow:none!important} +/* DEFAULT COLORS */ +.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} +.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} +.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} +.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} +.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} +.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} +.w3-blue-grey,.w3-hover-blue-grey:hover{color:#fff!important;background-color:#607d8b!important} +.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} +.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} +.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} +.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} +.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} +.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} +.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} +.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} +.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} +.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} +.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} +.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} +.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} +.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} +.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} +.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} +.w3-grey,.w3-hover-grey:hover{color:#000!important;background-color:#9e9e9e!important} +.w3-light-grey,.w3-hover-light-grey:hover{color:#000!important;background-color:#f1f1f1!important} +.w3-dark-grey,.w3-hover-dark-grey:hover{color:#fff!important;background-color:#616161!important} +.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffe7e7!important}.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#e7ffe7!important} +.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffd7!important}.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#e7ffff!important} +.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} +.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} +.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} +.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} +.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} +.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} +.w3-text-blue-grey,.w3-hover-text-blue-grey:hover{color:#607d8b!important} +.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} +.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} +.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} +.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} +.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} +.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} +.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} +.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} +.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} +.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} +.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} +.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} +.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} +.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} +.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} +.w3-text-black,.w3-hover-text-black:hover{color:#000!important} +.w3-text-grey,.w3-hover-text-grey:hover{color:#757575!important} +.w3-text-light-grey,.w3-hover-text-light-grey:hover{color:#f1f1f1!important} +.w3-text-dark-grey,.w3-hover-text-dark-grey:hover{color:#3a3a3a!important} +.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} +.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} +.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} +.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} +.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} +.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} +.w3-border-blue-grey,.w3-hover-blue-grey:hover{border-color:#607d8b!important} +.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} +.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} +.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} +.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} +.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} +.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} +.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} +.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} +.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} +.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} +.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} +.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} +.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} +.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} +.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} +.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} +.w3-border-grey,.w3-hover-border-grey:hover{border-color:#9e9e9e!important} +.w3-border-light-grey,.w3-hover-border-light-grey:hover{border-color:#f1f1f1!important} +.w3-border-dark-grey,.w3-hover-border-dark-grey:hover{border-color:#616161!important} +.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} +.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffd7!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} +/* DEFAULT THEME */ +.w3-theme-l5 {color:#000 !important; background-color:#f6f8fc !important} +.w3-theme-l4 {color:#000 !important; background-color:#e1e9f6 !important} +.w3-theme-l3 {color:#000 !important; background-color:#c3d3ed !important} +.w3-theme-l2 {color:#000 !important; background-color:#a5bee4 !important} +.w3-theme-l1 {color:#fff !important; background-color:#88a8db !important} +.w3-theme-d1 {color:#fff !important; background-color:#5180cb !important} +.w3-theme-d2 {color:#fff !important; background-color:#3a6fc3 !important} +.w3-theme-d3 {color:#fff !important; background-color:#3361aa !important} +.w3-theme-d4 {color:#fff !important; background-color:#2c5392 !important} +.w3-theme-d5 {color:#fff !important; background-color:#24457a !important} + +.w3-theme-light {color:#000 !important; background-color:#f6f8fc !important} +.w3-theme-dark {color:#fff !important; background-color:#24457a !important} +.w3-theme-action {color:#fff !important; background-color:#24457a !important} + +.w3-theme {color:#fff !important; background-color:#6a92d3 !important} +.w3-text-theme {color:#6a92d3 !important} +.w3-border-theme {border-color:#6a92d3 !important} + +.w3-hover-theme:hover {color:#fff !important; background-color:#6a92d3 !important} +.w3-hover-text-theme:hover {color:#6a92d3 !important} +.w3-hover-border-theme:hover {border-color:#6a92d3 !important} + +.w3-label { color: rgb(153, 150, 150);} +#main {margin-left: 210px;} +@media (max-width:768px){ + #sidebar { display: none;} + #main { margin-left: 0px;} +} + +.w3-select { + display: block; + font-size: 16px; + font-family: sans-serif; + font-weight: normal; + color: #444; + line-height: 1.3; + padding: .6em 1.4em .5em .8em; + width: 100%; + max-width: 100%; + box-sizing: border-box; + margin: 0; + border-bottom: 1px solid #aaa; + box-shadow: 0 1px 0 1px rgba(0,0,0,.04); + /* border-radius: .5em; */ + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + background-color: #e8f0fe; + background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'), + linear-gradient(to bottom, #e8f0fe 0%,#e8f0fe 100%); + background-repeat: no-repeat, repeat; + background-position: right .7em top 50%, 0 0; + background-size: .65em auto, 100%; +} +.w3-select::-ms-expand { + display: none; +} +.w3-select:hover { + border-color: #888; +} +.w3-select:focus { + border-color: #aaa; + box-shadow: 0 0 1px 1px #6a92d3; + box-shadow: 0 0 0 1px -moz-mac-focusring; + color: #222; + outline: none; +} + + +.w3-select option { + font-weight:normal; +} + +.w3-table { + table-layout: fixed; +} + +.w3-text-line-through { text-decoration: line-through; } + +#snackbar { + visibility: hidden; + min-width: 250px; + margin-left: -125px; + background-color: #333; + color: #fff; + text-align: center; + border-radius: 2px; + padding: 16px; + position: fixed; + z-index: 1; + left: 50%; + bottom: 30px; + font-size: 17px; +} + +#snackbar.show { + visibility: visible; + -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; + animation: fadein 0.5s, fadeout 0.5s 2.5s; +} + +@-webkit-keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: 30px; opacity: 1;} +} + +@keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: 30px; opacity: 1;} +} + +@-webkit-keyframes fadeout { + from {bottom: 30px; opacity: 1;} + to {bottom: 0; opacity: 0;} +} + +@keyframes fadeout { + from {bottom: 30px; opacity: 1;} + to {bottom: 0; opacity: 0;} +} + +.tabulator-header-filter > input { + background-color: #e8f0fe; + border: 1px solid #ccc; + font-weight: normal; +} + +.w3-readonly { + pointer-events:none; + padding:8px;display:block;border:0px;width:100%;background-color: #fff; +} + +.right-side-bg { + background: url("../img/bg1.jpg"); + background-size: cover; + min-height: 100vh; +} + +/* .mceContentBody { + background: #e8f0fe; + color:#000; +} */ + +/* .tabulator-row-even { + background-color: #757575; +} */ \ No newline at end of file diff --git a/backoffice/data/.htaccess b/backoffice/data/.htaccess new file mode 100644 index 00000000..fd8b0c39 --- /dev/null +++ b/backoffice/data/.htaccess @@ -0,0 +1,5 @@ +RewriteEngine On +RewriteBase / +RewriteCond %{HTTP_COOKIE} !solana= [NC] +RewriteRule .* "%{REQUEST_SCHEME}://%{HTTP_HOST}/backoffice/login.html" [L] +# Require all denied \ No newline at end of file diff --git a/backoffice/img/bg1.jpg b/backoffice/img/bg1.jpg new file mode 100644 index 00000000..80c53260 Binary files /dev/null and b/backoffice/img/bg1.jpg differ diff --git a/backoffice/img/favicon/android-icon-144x144.png b/backoffice/img/favicon/android-icon-144x144.png new file mode 100644 index 00000000..f85274fc Binary files /dev/null and b/backoffice/img/favicon/android-icon-144x144.png differ diff --git a/backoffice/img/favicon/android-icon-192x192.png b/backoffice/img/favicon/android-icon-192x192.png new file mode 100644 index 00000000..60d2c849 Binary files /dev/null and b/backoffice/img/favicon/android-icon-192x192.png differ diff --git a/backoffice/img/favicon/android-icon-36x36.png b/backoffice/img/favicon/android-icon-36x36.png new file mode 100644 index 00000000..dc480dc0 Binary files /dev/null and b/backoffice/img/favicon/android-icon-36x36.png differ diff --git a/backoffice/img/favicon/android-icon-48x48.png b/backoffice/img/favicon/android-icon-48x48.png new file mode 100644 index 00000000..4f257d2f Binary files /dev/null and b/backoffice/img/favicon/android-icon-48x48.png differ diff --git a/backoffice/img/favicon/android-icon-72x72.png b/backoffice/img/favicon/android-icon-72x72.png new file mode 100644 index 00000000..faa3184b Binary files /dev/null and b/backoffice/img/favicon/android-icon-72x72.png differ diff --git a/backoffice/img/favicon/android-icon-96x96.png b/backoffice/img/favicon/android-icon-96x96.png new file mode 100644 index 00000000..6a9b1ad1 Binary files /dev/null and b/backoffice/img/favicon/android-icon-96x96.png differ diff --git a/backoffice/img/favicon/apple-icon-114x114.png b/backoffice/img/favicon/apple-icon-114x114.png new file mode 100644 index 00000000..2296a80e Binary files /dev/null and b/backoffice/img/favicon/apple-icon-114x114.png differ diff --git a/backoffice/img/favicon/apple-icon-120x120.png b/backoffice/img/favicon/apple-icon-120x120.png new file mode 100644 index 00000000..e4dc217f Binary files /dev/null and b/backoffice/img/favicon/apple-icon-120x120.png differ diff --git a/backoffice/img/favicon/apple-icon-144x144.png b/backoffice/img/favicon/apple-icon-144x144.png new file mode 100644 index 00000000..f85274fc Binary files /dev/null and b/backoffice/img/favicon/apple-icon-144x144.png differ diff --git a/backoffice/img/favicon/apple-icon-152x152.png b/backoffice/img/favicon/apple-icon-152x152.png new file mode 100644 index 00000000..766b8d24 Binary files /dev/null and b/backoffice/img/favicon/apple-icon-152x152.png differ diff --git a/backoffice/img/favicon/apple-icon-180x180.png b/backoffice/img/favicon/apple-icon-180x180.png new file mode 100644 index 00000000..8165f255 Binary files /dev/null and b/backoffice/img/favicon/apple-icon-180x180.png differ diff --git a/backoffice/img/favicon/apple-icon-57x57.png b/backoffice/img/favicon/apple-icon-57x57.png new file mode 100644 index 00000000..a0c0ac1b Binary files /dev/null and b/backoffice/img/favicon/apple-icon-57x57.png differ diff --git a/backoffice/img/favicon/apple-icon-60x60.png b/backoffice/img/favicon/apple-icon-60x60.png new file mode 100644 index 00000000..a3ce9476 Binary files /dev/null and b/backoffice/img/favicon/apple-icon-60x60.png differ diff --git a/backoffice/img/favicon/apple-icon-72x72.png b/backoffice/img/favicon/apple-icon-72x72.png new file mode 100644 index 00000000..faa3184b Binary files /dev/null and b/backoffice/img/favicon/apple-icon-72x72.png differ diff --git a/backoffice/img/favicon/apple-icon-76x76.png b/backoffice/img/favicon/apple-icon-76x76.png new file mode 100644 index 00000000..16e78ac8 Binary files /dev/null and b/backoffice/img/favicon/apple-icon-76x76.png differ diff --git a/backoffice/img/favicon/apple-icon-precomposed.png b/backoffice/img/favicon/apple-icon-precomposed.png new file mode 100644 index 00000000..887a048f Binary files /dev/null and b/backoffice/img/favicon/apple-icon-precomposed.png differ diff --git a/backoffice/img/favicon/apple-icon.png b/backoffice/img/favicon/apple-icon.png new file mode 100644 index 00000000..887a048f Binary files /dev/null and b/backoffice/img/favicon/apple-icon.png differ diff --git a/backoffice/img/favicon/browserconfig.xml b/backoffice/img/favicon/browserconfig.xml new file mode 100644 index 00000000..c5541482 --- /dev/null +++ b/backoffice/img/favicon/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/backoffice/img/favicon/favicon-16x16.png b/backoffice/img/favicon/favicon-16x16.png new file mode 100644 index 00000000..fede4cc7 Binary files /dev/null and b/backoffice/img/favicon/favicon-16x16.png differ diff --git a/backoffice/img/favicon/favicon-32x32.png b/backoffice/img/favicon/favicon-32x32.png new file mode 100644 index 00000000..5d0e386a Binary files /dev/null and b/backoffice/img/favicon/favicon-32x32.png differ diff --git a/backoffice/img/favicon/favicon-96x96.png b/backoffice/img/favicon/favicon-96x96.png new file mode 100644 index 00000000..6a9b1ad1 Binary files /dev/null and b/backoffice/img/favicon/favicon-96x96.png differ diff --git a/backoffice/img/favicon/favicon.ico b/backoffice/img/favicon/favicon.ico new file mode 100644 index 00000000..3596ef88 Binary files /dev/null and b/backoffice/img/favicon/favicon.ico differ diff --git a/backoffice/img/favicon/manifest.json b/backoffice/img/favicon/manifest.json new file mode 100644 index 00000000..013d4a6a --- /dev/null +++ b/backoffice/img/favicon/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/backoffice/img/favicon/ms-icon-144x144.png b/backoffice/img/favicon/ms-icon-144x144.png new file mode 100644 index 00000000..f85274fc Binary files /dev/null and b/backoffice/img/favicon/ms-icon-144x144.png differ diff --git a/backoffice/img/favicon/ms-icon-150x150.png b/backoffice/img/favicon/ms-icon-150x150.png new file mode 100644 index 00000000..f2b56145 Binary files /dev/null and b/backoffice/img/favicon/ms-icon-150x150.png differ diff --git a/backoffice/img/favicon/ms-icon-310x310.png b/backoffice/img/favicon/ms-icon-310x310.png new file mode 100644 index 00000000..d958930c Binary files /dev/null and b/backoffice/img/favicon/ms-icon-310x310.png differ diff --git a/backoffice/img/favicon/ms-icon-70x70.png b/backoffice/img/favicon/ms-icon-70x70.png new file mode 100644 index 00000000..94d68288 Binary files /dev/null and b/backoffice/img/favicon/ms-icon-70x70.png differ diff --git a/backoffice/img/icons/Agreement_01.svg b/backoffice/img/icons/Agreement_01.svg new file mode 100644 index 00000000..27a8a06e --- /dev/null +++ b/backoffice/img/icons/Agreement_01.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/Bill.svg b/backoffice/img/icons/Bill.svg new file mode 100644 index 00000000..b891cd29 --- /dev/null +++ b/backoffice/img/icons/Bill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/Document_Save.svg b/backoffice/img/icons/Document_Save.svg new file mode 100644 index 00000000..7262b20d --- /dev/null +++ b/backoffice/img/icons/Document_Save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/Floppy.svg b/backoffice/img/icons/Floppy.svg new file mode 100644 index 00000000..d85ccd9a --- /dev/null +++ b/backoffice/img/icons/Floppy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/Folder_Delete_01.svg b/backoffice/img/icons/Folder_Delete_01.svg new file mode 100644 index 00000000..c1b0566f --- /dev/null +++ b/backoffice/img/icons/Folder_Delete_01.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/Folder_Find_01.svg b/backoffice/img/icons/Folder_Find_01.svg new file mode 100644 index 00000000..7d4ef633 --- /dev/null +++ b/backoffice/img/icons/Folder_Find_01.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/Save.svg b/backoffice/img/icons/Save.svg new file mode 100644 index 00000000..ce6bfa35 --- /dev/null +++ b/backoffice/img/icons/Save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/access.svg b/backoffice/img/icons/access.svg new file mode 100644 index 00000000..ca4578c8 --- /dev/null +++ b/backoffice/img/icons/access.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/access_white.svg b/backoffice/img/icons/access_white.svg new file mode 100644 index 00000000..5ecb2b80 --- /dev/null +++ b/backoffice/img/icons/access_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/address.svg b/backoffice/img/icons/address.svg new file mode 100644 index 00000000..4d51035d --- /dev/null +++ b/backoffice/img/icons/address.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/address_white.svg b/backoffice/img/icons/address_white.svg new file mode 100644 index 00000000..a5de7d77 --- /dev/null +++ b/backoffice/img/icons/address_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/apps.svg b/backoffice/img/icons/apps.svg new file mode 100644 index 00000000..eef3c30c --- /dev/null +++ b/backoffice/img/icons/apps.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/apps_white.svg b/backoffice/img/icons/apps_white.svg new file mode 100644 index 00000000..f37f596f --- /dev/null +++ b/backoffice/img/icons/apps_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/archive.svg b/backoffice/img/icons/archive.svg new file mode 100644 index 00000000..5dc21eee --- /dev/null +++ b/backoffice/img/icons/archive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/archive_white.svg b/backoffice/img/icons/archive_white.svg new file mode 100644 index 00000000..2cff08a7 --- /dev/null +++ b/backoffice/img/icons/archive_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/calendar.svg b/backoffice/img/icons/calendar.svg new file mode 100644 index 00000000..6b51e035 --- /dev/null +++ b/backoffice/img/icons/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/calendar_white.svg b/backoffice/img/icons/calendar_white.svg new file mode 100644 index 00000000..c738ec5a --- /dev/null +++ b/backoffice/img/icons/calendar_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/club.svg b/backoffice/img/icons/club.svg new file mode 100644 index 00000000..8a941e33 --- /dev/null +++ b/backoffice/img/icons/club.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/club_white.svg b/backoffice/img/icons/club_white.svg new file mode 100644 index 00000000..5325dc0e --- /dev/null +++ b/backoffice/img/icons/club_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/clubs.svg b/backoffice/img/icons/clubs.svg new file mode 100644 index 00000000..0a7d497d --- /dev/null +++ b/backoffice/img/icons/clubs.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/clubs_white.svg b/backoffice/img/icons/clubs_white.svg new file mode 100644 index 00000000..7184d282 --- /dev/null +++ b/backoffice/img/icons/clubs_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/code.svg b/backoffice/img/icons/code.svg new file mode 100644 index 00000000..c3b4f286 --- /dev/null +++ b/backoffice/img/icons/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/company.svg b/backoffice/img/icons/company.svg new file mode 100644 index 00000000..4b65899c --- /dev/null +++ b/backoffice/img/icons/company.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/company_white.svg b/backoffice/img/icons/company_white.svg new file mode 100644 index 00000000..ed26c0f7 --- /dev/null +++ b/backoffice/img/icons/company_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/cube.svg b/backoffice/img/icons/cube.svg new file mode 100644 index 00000000..31b31290 --- /dev/null +++ b/backoffice/img/icons/cube.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/cube_white.svg b/backoffice/img/icons/cube_white.svg new file mode 100644 index 00000000..9a78edfa --- /dev/null +++ b/backoffice/img/icons/cube_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/cubelight.svg b/backoffice/img/icons/cubelight.svg new file mode 100644 index 00000000..ff786d47 --- /dev/null +++ b/backoffice/img/icons/cubelight.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/cubelight_white.svg b/backoffice/img/icons/cubelight_white.svg new file mode 100644 index 00000000..c7965b66 --- /dev/null +++ b/backoffice/img/icons/cubelight_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/dashboard.svg b/backoffice/img/icons/dashboard.svg new file mode 100644 index 00000000..c5f36ccb --- /dev/null +++ b/backoffice/img/icons/dashboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/dashboard_white.svg b/backoffice/img/icons/dashboard_white.svg new file mode 100644 index 00000000..c6a7dda2 --- /dev/null +++ b/backoffice/img/icons/dashboard_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/documents.svg b/backoffice/img/icons/documents.svg new file mode 100644 index 00000000..aaec4c6b --- /dev/null +++ b/backoffice/img/icons/documents.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/documents_white.svg b/backoffice/img/icons/documents_white.svg new file mode 100644 index 00000000..94d71a70 --- /dev/null +++ b/backoffice/img/icons/documents_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/download.svg b/backoffice/img/icons/download.svg new file mode 100644 index 00000000..eb209562 --- /dev/null +++ b/backoffice/img/icons/download.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/download_white.svg b/backoffice/img/icons/download_white.svg new file mode 100644 index 00000000..100cebfc --- /dev/null +++ b/backoffice/img/icons/download_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/duplicate.svg b/backoffice/img/icons/duplicate.svg new file mode 100644 index 00000000..cebaad86 --- /dev/null +++ b/backoffice/img/icons/duplicate.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/duplicate_white.svg b/backoffice/img/icons/duplicate_white.svg new file mode 100644 index 00000000..a643c740 --- /dev/null +++ b/backoffice/img/icons/duplicate_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/edit.svg b/backoffice/img/icons/edit.svg new file mode 100644 index 00000000..c0bb9ac9 --- /dev/null +++ b/backoffice/img/icons/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/edit_white.svg b/backoffice/img/icons/edit_white.svg new file mode 100644 index 00000000..bbbe89ff --- /dev/null +++ b/backoffice/img/icons/edit_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/excel.svg b/backoffice/img/icons/excel.svg new file mode 100644 index 00000000..6c14cc2b --- /dev/null +++ b/backoffice/img/icons/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/excel_white.svg b/backoffice/img/icons/excel_white.svg new file mode 100644 index 00000000..7693393e --- /dev/null +++ b/backoffice/img/icons/excel_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/file.svg b/backoffice/img/icons/file.svg new file mode 100644 index 00000000..6df51bd3 --- /dev/null +++ b/backoffice/img/icons/file.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/file/dir.png b/backoffice/img/icons/file/dir.png new file mode 100644 index 00000000..a2e3c0f7 Binary files /dev/null and b/backoffice/img/icons/file/dir.png differ diff --git a/backoffice/img/icons/file/doc.png b/backoffice/img/icons/file/doc.png new file mode 100644 index 00000000..8e431908 Binary files /dev/null and b/backoffice/img/icons/file/doc.png differ diff --git a/backoffice/img/icons/file/docx.png b/backoffice/img/icons/file/docx.png new file mode 100644 index 00000000..8e431908 Binary files /dev/null and b/backoffice/img/icons/file/docx.png differ diff --git a/backoffice/img/icons/file/file.png b/backoffice/img/icons/file/file.png new file mode 100644 index 00000000..c981a83a Binary files /dev/null and b/backoffice/img/icons/file/file.png differ diff --git a/backoffice/img/icons/file/folderup.svg b/backoffice/img/icons/file/folderup.svg new file mode 100644 index 00000000..7cb05130 --- /dev/null +++ b/backoffice/img/icons/file/folderup.svg @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/backoffice/img/icons/file/folderup_white.svg b/backoffice/img/icons/file/folderup_white.svg new file mode 100644 index 00000000..74fb0467 --- /dev/null +++ b/backoffice/img/icons/file/folderup_white.svg @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/backoffice/img/icons/file/jpg.png b/backoffice/img/icons/file/jpg.png new file mode 100644 index 00000000..3f2999cd Binary files /dev/null and b/backoffice/img/icons/file/jpg.png differ diff --git a/backoffice/img/icons/file/pdf.png b/backoffice/img/icons/file/pdf.png new file mode 100644 index 00000000..8614b6e6 Binary files /dev/null and b/backoffice/img/icons/file/pdf.png differ diff --git a/backoffice/img/icons/file/png.png b/backoffice/img/icons/file/png.png new file mode 100644 index 00000000..3f2999cd Binary files /dev/null and b/backoffice/img/icons/file/png.png differ diff --git a/backoffice/img/icons/file/txt.png b/backoffice/img/icons/file/txt.png new file mode 100644 index 00000000..2e41cee8 Binary files /dev/null and b/backoffice/img/icons/file/txt.png differ diff --git a/backoffice/img/icons/file/xls.png b/backoffice/img/icons/file/xls.png new file mode 100644 index 00000000..ceabc7d0 Binary files /dev/null and b/backoffice/img/icons/file/xls.png differ diff --git a/backoffice/img/icons/file/xlsx.png b/backoffice/img/icons/file/xlsx.png new file mode 100644 index 00000000..ceabc7d0 Binary files /dev/null and b/backoffice/img/icons/file/xlsx.png differ diff --git a/backoffice/img/icons/file_white.svg b/backoffice/img/icons/file_white.svg new file mode 100644 index 00000000..3e04bdaf --- /dev/null +++ b/backoffice/img/icons/file_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/folder.svg b/backoffice/img/icons/folder.svg new file mode 100644 index 00000000..f1f6e4ac --- /dev/null +++ b/backoffice/img/icons/folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/folder_add.svg b/backoffice/img/icons/folder_add.svg new file mode 100644 index 00000000..4f8b21e5 --- /dev/null +++ b/backoffice/img/icons/folder_add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/folder_add_white.svg b/backoffice/img/icons/folder_add_white.svg new file mode 100644 index 00000000..728bbaea --- /dev/null +++ b/backoffice/img/icons/folder_add_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/folder_white.svg b/backoffice/img/icons/folder_white.svg new file mode 100644 index 00000000..25baa239 --- /dev/null +++ b/backoffice/img/icons/folder_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/globe.svg b/backoffice/img/icons/globe.svg new file mode 100644 index 00000000..9af64dc0 --- /dev/null +++ b/backoffice/img/icons/globe.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/globe_white.svg b/backoffice/img/icons/globe_white.svg new file mode 100644 index 00000000..f0c2dea3 --- /dev/null +++ b/backoffice/img/icons/globe_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/group.svg b/backoffice/img/icons/group.svg new file mode 100644 index 00000000..a74f7ad8 --- /dev/null +++ b/backoffice/img/icons/group.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/group_white.svg b/backoffice/img/icons/group_white.svg new file mode 100644 index 00000000..72dd9db2 --- /dev/null +++ b/backoffice/img/icons/group_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/inbox.svg b/backoffice/img/icons/inbox.svg new file mode 100644 index 00000000..034e942d --- /dev/null +++ b/backoffice/img/icons/inbox.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/inbox_white.svg b/backoffice/img/icons/inbox_white.svg new file mode 100644 index 00000000..1d98ae9c --- /dev/null +++ b/backoffice/img/icons/inbox_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/library.svg b/backoffice/img/icons/library.svg new file mode 100644 index 00000000..0a917f34 --- /dev/null +++ b/backoffice/img/icons/library.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/library_white.svg b/backoffice/img/icons/library_white.svg new file mode 100644 index 00000000..3928fdd3 --- /dev/null +++ b/backoffice/img/icons/library_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/license.svg b/backoffice/img/icons/license.svg new file mode 100644 index 00000000..54f0fec7 --- /dev/null +++ b/backoffice/img/icons/license.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/license_white.svg b/backoffice/img/icons/license_white.svg new file mode 100644 index 00000000..2563f36f --- /dev/null +++ b/backoffice/img/icons/license_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/list.svg b/backoffice/img/icons/list.svg new file mode 100644 index 00000000..ea3f0e23 --- /dev/null +++ b/backoffice/img/icons/list.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/list_white.svg b/backoffice/img/icons/list_white.svg new file mode 100644 index 00000000..b57b7522 --- /dev/null +++ b/backoffice/img/icons/list_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/log.svg b/backoffice/img/icons/log.svg new file mode 100644 index 00000000..886dd3d8 --- /dev/null +++ b/backoffice/img/icons/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/log_white.svg b/backoffice/img/icons/log_white.svg new file mode 100644 index 00000000..3e443fa8 --- /dev/null +++ b/backoffice/img/icons/log_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/logout.svg b/backoffice/img/icons/logout.svg new file mode 100644 index 00000000..da64de0e --- /dev/null +++ b/backoffice/img/icons/logout.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/logout_white.svg b/backoffice/img/icons/logout_white.svg new file mode 100644 index 00000000..3feb1b78 --- /dev/null +++ b/backoffice/img/icons/logout_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/menu.svg b/backoffice/img/icons/menu.svg new file mode 100644 index 00000000..1a247ab7 --- /dev/null +++ b/backoffice/img/icons/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/menu_white.svg b/backoffice/img/icons/menu_white.svg new file mode 100644 index 00000000..222cc94b --- /dev/null +++ b/backoffice/img/icons/menu_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/newspaper.svg b/backoffice/img/icons/newspaper.svg new file mode 100644 index 00000000..420b45ad --- /dev/null +++ b/backoffice/img/icons/newspaper.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/newspaper_white.svg b/backoffice/img/icons/newspaper_white.svg new file mode 100644 index 00000000..472b3898 --- /dev/null +++ b/backoffice/img/icons/newspaper_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/numberlist.svg b/backoffice/img/icons/numberlist.svg new file mode 100644 index 00000000..33ac90cd --- /dev/null +++ b/backoffice/img/icons/numberlist.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/numberlist_white.svg b/backoffice/img/icons/numberlist_white.svg new file mode 100644 index 00000000..e98d4aaa --- /dev/null +++ b/backoffice/img/icons/numberlist_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/package.svg b/backoffice/img/icons/package.svg new file mode 100644 index 00000000..0b6a94d5 --- /dev/null +++ b/backoffice/img/icons/package.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/package_white.svg b/backoffice/img/icons/package_white.svg new file mode 100644 index 00000000..9a40a7bf --- /dev/null +++ b/backoffice/img/icons/package_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/pdf.svg b/backoffice/img/icons/pdf.svg new file mode 100644 index 00000000..43a58e1b --- /dev/null +++ b/backoffice/img/icons/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/pdf_white.svg b/backoffice/img/icons/pdf_white.svg new file mode 100644 index 00000000..94dbd956 --- /dev/null +++ b/backoffice/img/icons/pdf_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/pdfexport.svg b/backoffice/img/icons/pdfexport.svg new file mode 100644 index 00000000..49169f98 --- /dev/null +++ b/backoffice/img/icons/pdfexport.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/pdfexport_white.svg b/backoffice/img/icons/pdfexport_white.svg new file mode 100644 index 00000000..49169f98 --- /dev/null +++ b/backoffice/img/icons/pdfexport_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/pictures.svg b/backoffice/img/icons/pictures.svg new file mode 100644 index 00000000..97bb690b --- /dev/null +++ b/backoffice/img/icons/pictures.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/pictures_white.svg b/backoffice/img/icons/pictures_white.svg new file mode 100644 index 00000000..9142d7f0 --- /dev/null +++ b/backoffice/img/icons/pictures_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/plus.svg b/backoffice/img/icons/plus.svg new file mode 100644 index 00000000..08e85b5e --- /dev/null +++ b/backoffice/img/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/plus_white.svg b/backoffice/img/icons/plus_white.svg new file mode 100644 index 00000000..22714fee --- /dev/null +++ b/backoffice/img/icons/plus_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/projects.svg b/backoffice/img/icons/projects.svg new file mode 100644 index 00000000..1e8e1e7d --- /dev/null +++ b/backoffice/img/icons/projects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/projects_white.svg b/backoffice/img/icons/projects_white.svg new file mode 100644 index 00000000..ddbd36cf --- /dev/null +++ b/backoffice/img/icons/projects_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/remove.svg b/backoffice/img/icons/remove.svg new file mode 100644 index 00000000..e459477f --- /dev/null +++ b/backoffice/img/icons/remove.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/remove_white.svg b/backoffice/img/icons/remove_white.svg new file mode 100644 index 00000000..77e856e2 --- /dev/null +++ b/backoffice/img/icons/remove_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/squares.svg b/backoffice/img/icons/squares.svg new file mode 100644 index 00000000..8e084a05 --- /dev/null +++ b/backoffice/img/icons/squares.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/squares_white.svg b/backoffice/img/icons/squares_white.svg new file mode 100644 index 00000000..489f802c --- /dev/null +++ b/backoffice/img/icons/squares_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/target.svg b/backoffice/img/icons/target.svg new file mode 100644 index 00000000..72b5eb0f --- /dev/null +++ b/backoffice/img/icons/target.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/target_white.svg b/backoffice/img/icons/target_white.svg new file mode 100644 index 00000000..b02c6c44 --- /dev/null +++ b/backoffice/img/icons/target_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/upload.svg b/backoffice/img/icons/upload.svg new file mode 100644 index 00000000..20dfeb07 --- /dev/null +++ b/backoffice/img/icons/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backoffice/img/icons/upload_white.svg b/backoffice/img/icons/upload_white.svg new file mode 100644 index 00000000..367855a4 --- /dev/null +++ b/backoffice/img/icons/upload_white.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backoffice/img/icons/user.svg b/backoffice/img/icons/user.svg new file mode 100644 index 00000000..37b4e3dd --- /dev/null +++ b/backoffice/img/icons/user.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/icons/user_white.svg b/backoffice/img/icons/user_white.svg new file mode 100644 index 00000000..78d94ca8 --- /dev/null +++ b/backoffice/img/icons/user_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/backoffice/img/logo_512.png b/backoffice/img/logo_512.png new file mode 100644 index 00000000..b82da90b Binary files /dev/null and b/backoffice/img/logo_512.png differ diff --git a/backoffice/img/no-image-icon.png b/backoffice/img/no-image-icon.png new file mode 100644 index 00000000..1849ae7c Binary files /dev/null and b/backoffice/img/no-image-icon.png differ diff --git a/backoffice/img/no-news-img.png b/backoffice/img/no-news-img.png new file mode 100644 index 00000000..3411fda1 Binary files /dev/null and b/backoffice/img/no-news-img.png differ diff --git a/backoffice/img/toplogo.png b/backoffice/img/toplogo.png new file mode 100644 index 00000000..4f04687d Binary files /dev/null and b/backoffice/img/toplogo.png differ diff --git a/backoffice/img/toplogo.svg b/backoffice/img/toplogo.svg new file mode 100644 index 00000000..9e2ec89b --- /dev/null +++ b/backoffice/img/toplogo.svg @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backoffice/index.cgi b/backoffice/index.cgi new file mode 100644 index 00000000..91b7ce8f --- /dev/null +++ b/backoffice/index.cgi @@ -0,0 +1,184 @@ +#!/usr/bin/perl + +use strict; +use lib ('api/lib/perl5'); +use lib ('api/lib'); +use File::Basename qw/dirname basename/; +use Template; +# use Template::Constants qw( :debug ); +use CGI; +#use CGI::Carp qw(fatalsToBrowser); +use CGI::Cookie; +use Data::Dumper; +use JSON::PP; + +use dksconfig qw/$sitecfg/; +use session; + +my $skl = "skeleton/index.tt"; +my $cgi = new CGI(); +my $p=(); +my $cookie; +my $vars = $sitecfg; + +$vars->{filepath} = substr($cgi->url({-absolute=>1}),length($vars->{basepath})+1); +$vars->{baseurl} = $cgi->url({-base=>1}).$vars->{basepath}; + +if ($vars->{basepath} eq "/"){ + $vars->{siteurl} = $cgi->url({-base=>1}); +}else { + $vars->{siteurl} = $cgi->url({-base=>1}).dirname($vars->{basepath}); + $vars->{docroot} = $vars->{docroot}.dirname($vars->{basepath}); + #$vars->{sitepath} = dirname($vars->{basepath}); +} + + +if ($vars->{filepath} ne ""){ + $vars->{suffix} = substr($vars->{filepath},rindex($vars->{filepath},'.')); + $vars->{page} = $vars->{filepath}; + $vars->{page} =~ s/html$/tt/; +} +$vars->{abspath} = ""; + +my $sess = (); +my $se = session->new(); +$p->{sid} = $cgi->cookie($vars->{cookiename}); +if ($cgi->request_method() eq "GET"){ + my @params = $cgi->param(); + foreach my $pp (@params){ + $p->{$pp} = $cgi->param($pp); + } + +} + +if ($cgi->request_method() eq "POST"){ + + my @params = $cgi->param(); + foreach my $pp (@params){ + $p->{$pp} = $cgi->param($pp); + } + $vars->{hasposts} = $p; + if (exists($p->{'btnlogin'})){ + my $ret = $se->checklogin($p->{login},$p->{password}); + if ($ret->{sid} ne ""){ + $p->{sid} = $ret->{sid}; + $cookie = CGI::Cookie->new(-name=>$vars->{cookiename},-value=>$p->{sid},-httponly => 1); + }else { + $vars->{message} = $ret->{message}; + $vars->{messagetype} = $ret->{messagetype}; + $vars->{page} = "message.tt"; + } + } + if (exists($p->{'btnregister'})){ + + my $ret = $se->registeruser($p); + + $vars->{message} = $ret->{message}; + $vars->{messagetype} = $ret->{messagetype}; + $vars->{page} = $ret->{page}; + } + if (exists($p->{'btnforgotpassword'})){ + my $ret = $se->passwordforgotten($p->{email}); + $vars->{message} = $ret->{message}; + $vars->{messagetype} = $ret->{messagetype}; + $vars->{page} = "message.tt"; + } + if (exists($p->{'btnvalidateemail'})){ + my $ret = $se->validateaccount($p); + $vars->{message} = $ret->{message}; + $vars->{messagetype} = $ret->{messagetype}; + $vars->{page} = "message.tt"; + } + if (exists($p->{'btnresendcode'})){ + my $ret = $se->resendcode($p->{email}); + $vars->{message} = $ret->{message}; + $vars->{messagetype} = $ret->{messagetype}; + $vars->{page} = "message.tt"; + } + + if (exists($p->{logout})){ + $se->deletesession($p->{sid}); + $p->{sid} = ""; + $cookie = CGI::Cookie->new(-name=>$vars->{cookiename},-value=>"",-httponly => 1); + } + # if (exists($p->{btndeleteprofile})){ + # my $ret = $se->deleteprofile($p->{deleteprofile}); + # $vars->{message} = $ret->{message}; + # $vars->{messagetype} = $ret->{messagetype}; + # $vars->{page} = "message.tt"; + # if (exists($ret->{sid})){ + # $p->{sid} = ""; + # } + # $cookie = CGI::Cookie->new(-name=>$vars->{cookiename},-value=>"",-httponly => 1); + # } +} + +if ($p->{sid} ne ""){ + $sess = $se->getsession($p->{sid}); +} + + +#$vars->{beforex} = $vars->{page}; +if (!exists($sess->{id}) || (!exists($p->{sid})) || $p->{sid} eq ""){ + $skl = "skeleton/login.tt"; +} +# if ($vars->{page} eq "deleteprofile.tt") { +# $skl = "skeleton/login.tt"; +# } +# my ($appname) = $ENV{REQUEST_URI} =~ /.*\/module\/(\w+)\/.*/; +if ($p->{sid} ne ""){ + $vars->{session} = $sess; +} +#SESSION - End +# #BEGIN - Browser Blocking +# if (($ENV{HTTP_USER_AGENT} !~ /Chrome/) || ($ENV{HTTP_USER_AGENT} =~ /Edge/) || ($ENV{HTTP_USER_AGENT} =~ /Firefox/)){ +# $skl = "skeleton/browser.tt"; +# } +# #END - Browser Blocking +my $ctype = 'text/html'; +if ($vars->{suffix} eq ".js"){ + $ctype= "text/javascript"; +} elsif ($vars->{suffix} eq ".css"){ + $ctype = "text/css"; +} +print $cgi->header(-type=>$ctype, -charset=>"utf-8",-cookie => $cookie); +# print dirname($ENV{"SCRIPT_FILENAME"}); + +my $template = Template->new({INCLUDE_PATH => [dirname($ENV{"SCRIPT_FILENAME"}).'/tmpl']}); +#} + +my @lv = split(/\//,$vars->{filepath}); +my $absnum = scalar(@lv)-1; + +for (my $i=0;$i<$absnum;$i++){ + $vars->{abspath} .= "../"; +} + +# $vars->{page} = $vars->{page}; +if ($vars->{page} =~ /^module/){ + $vars->{module} = basename(dirname($vars->{page})); +} +$vars->{pagename} = basename($vars->{page}); +$vars->{pagename} =~ s/\.tt$//; +# my ($appname) = $ENV{REQUEST_URI} =~ /.*\/apps\/(\w+)\/.*/; +#$vars->{requri} = $ENV{REQUEST_URI}; + +#BEGIN - iFrame - Modules + +if ($skl ne "skeleton/login.tt" && $vars->{page} =~ /^module/){ + $skl = "skeleton/module.tt"; + if ($vars->{page} !~ /\.tt$/) { + $skl = "skeleton/module_file.tt"; + } + $vars->{params}= $p; +} + +#END - iFrame - Modules + +$template->process($skl,$vars) || die "Template process failed: ", $template->error(), "\n"; + +# if ($vars->{page} =~ /\.tt/){ +# print '
'.Dumper($p)."
";
+# } 
+
+
diff --git a/backoffice/js/.DS_Store b/backoffice/js/.DS_Store
new file mode 100644
index 00000000..5008ddfc
Binary files /dev/null and b/backoffice/js/.DS_Store differ
diff --git a/backoffice/js/admin.js b/backoffice/js/admin.js
new file mode 100644
index 00000000..2c7e9cb0
--- /dev/null
+++ b/backoffice/js/admin.js
@@ -0,0 +1,37 @@
+var admin = {
+  loadpage: function(modulepage,modulename){
+    //console.log(modulename);
+    //console.log("Load module:" + modulepage);
+    if (modulename){
+      document.getElementById("modulename").innerHTML = modulename;
+    }
+    document.getElementById("moduleframe").setAttribute('src',modulepage);
+  },
+  sidebarclick: function(modulepage,modulename){
+    admin.ladpage(modulepage,modulename);
+  },
+  logout: function(){
+    req.reqdata("POST",location.href,{"logout":"1"},admin.reloadpage);
+
+  },
+  reloadpage(page){
+    location.href=location.href;
+
+  }
+}
+
+function reload_page(){
+  location.href=location.href;
+}
+
+function closeSidebar(){
+  document.getElementById("sidebar").style.display = "none";
+  document.getElementById("main").style.margin = "0 0 0 0";
+  document.getElementById("modulename").style.setProperty("margin-left","0px");
+}
+
+function openSidebar(){
+  document.getElementById("sidebar").style.display = "block";
+  document.getElementById("main").style.setProperty("margin-left","210px");
+  document.getElementById("modulename").style.setProperty("margin-left","150px");
+}
\ No newline at end of file
diff --git a/backoffice/js/formsave.js b/backoffice/js/formsave.js
new file mode 100644
index 00000000..c59cc357
--- /dev/null
+++ b/backoffice/js/formsave.js
@@ -0,0 +1,192 @@
+function saveform(frmid,aftercallback){
+  var flds=getformcontent(frmid,null);
+  flds["fn"] ="saveform";
+  
+  //console.log(flds);
+  if (aftercallback){
+    req.reqdata("POST","index.cgi",flds,aftercallback);
+    formsaved({});
+  }
+  else {
+    req.reqdata("POST","index.cgi",flds,formsaved);
+  }
+  return false;
+}
+
+function formsaved(data){
+  var sb = document.getElementById("snackbar");
+  sb.className="show w3-green";
+  sb.innerHTML = 'Les données ont été sauvegarder!';
+  setTimeout(function(){ sb.className = sb.className.replace("show w3-green", ""); }, 3000);
+  return false;
+}
+
+function showsnackbar(xclass,xmessage){
+  var sb = document.getElementById("snackbar");
+  sb.className="show " + xclass;
+  sb.innerHTML = xmessage;
+  setTimeout(function(){ sb.className = sb.className.replace(sb.className, ""); }, 3000);
+  return false;
+}
+
+function getformcontent(frmid,dataflds){
+  var frm = document.getElementById("frm_" + frmid);
+  var flds = [];
+  if (dataflds){
+    flds = dataflds;
+  } 
+ 
+  for (var i = 0; i < frm.elements.length; i++) {
+    var field = frm.elements[i];
+    //console.log("field:" + field.id + " Name:" + field.getAttribute("name"));
+    if (field.tagName == "INPUT" || field.tagName == "SELECT" || field.tagName == "TEXTAREA"){
+      if (field.classList.contains("tagedit")){
+        var fvalue=field.value.trim();
+        var ndata = null;
+        if (fvalue != ""){
+          ndata =  fvalue.split(",");  
+        }
+       
+        flds[field.getAttribute("name")] = ndata;
+      }else if (field.tagName == "TEXTAREA" ){
+        if (field.classList.contains("richeditarea")){
+          flds[field.getAttribute("name")] = tinymce.get(field.id).getContent();
+        } else {
+          flds[field.getAttribute("name")] = field.innerHTML;
+        }
+        
+      }else if (field.type == "checkbox" ){
+        if (field.checked){
+          flds[field.getAttribute("name")] = "1";
+        } else {
+          flds[field.getAttribute("name")] = "";
+        }
+        
+      }
+      else {
+        if (field.tagName == "SELECT" && field.multiple == true){
+          var opts = field.selectedOptions;
+          var vals = [];
+          for (var o in opts){
+            if (opts[o].value){
+              vals.push(opts[o].value);
+            }
+          }
+          if (vals.length > 0) {
+            flds[field.getAttribute("name")] = vals;
+          } else {
+            flds[field.getAttribute("name")] = "";
+          }
+          
+        } else {
+          flds[field.getAttribute("name")] = field.value;
+        }
+        
+      }
+      
+    }
+  }
+  return flds;
+}
+
+function cleanform(frmname){
+  //console.log("Clean Form: " + frmname);
+  var frm = document.getElementById("frm_" + frmname);
+  
+  for (var f in frm){
+    //console.log(frm[f].id);
+    if (frm[f] && frm[f].id){
+    if (frm[f].tagName == 'INPUT'){
+      //console.log("is INPUT" + frm[f].id + " type:" + frm[f].type + " class:" + frm[f].classList);
+      if (frm[f].type == "checkbox"){
+        frm[f].checked = false;
+      } else if (frm[f].classList.contains("datefield")){
+          if (frm[f]._flatpickr){ frm[f]._flatpickr.clear(); }
+      } else if (frm[f].classList.contains("choices__input")){
+        if (choice[frmname][frm[f].id]){
+          choice[frmname][frm[f].id].removeActiveItems();
+        }
+      } else {
+        frm[f].value = "";
+      }
+    }
+    if (frm[f].tagName == 'SELECT'){
+      //console.log("is INPUT" + frm[f].id + " multiple:" + frm[f].multiple + " class:" + frm[f].classList);
+      if (frm[f].multiple == true){
+        if (frm[f].classList.contains("choices__input")){
+          choice[frmname][frm[f].id].removeActiveItems();
+        }
+      } else {
+        frm[f].value = "";
+      }
+      
+    }
+    if (frm[f].tagName == 'TEXTAREA'){
+      //console.log("is INPUT" + frm[f].id +  " class:" + frm[f].classList);
+      if (frm[f].classList.contains("richeditarea")){
+        tinymce.get(frm[f].id).setContent("");
+      } else {
+        frm[f].innerHTML = "";
+      } 
+    }
+  }
+  }
+  return false;
+}
+
+function fillformbydataclass(dataclass,data,readonly = false){
+  //console.log(data);
+  var frm = document.querySelectorAll('.data_'+ dataclass);
+  if (data){
+    for (var f in frm){
+      //console.log(frm[f]);
+      if (data[frm[f].id]){
+        //console.log(frm[f].id + " => " + data[frm[f].id]);
+        if (readonly){ frm[f].readonly = true;}
+        if (frm[f].tagName == 'INPUT'){
+          if (frm[f].type == "checkbox"){
+            if (data[frm[f].id] == "1"){
+              frm[f].checked = true;
+            } else {
+              frm[f].checked = false;
+            }
+          } else if (frm[f].classList.contains("datefield")){ 
+            frm[f]._flatpickr.setDate(data[frm[f].id]);
+          } else if (frm[f].classList.contains("choices__input")){  
+            if ((data[frm[f].id] != null) && (data[frm[f].id] != '[""]')){
+              choice[dataclass][frm[f].id].setValue(JSON.parse(data[frm[f].id])); 
+            }
+          } else {
+            frm[f].value=data[frm[f].id];
+          }
+        }
+        if (frm[f].tagName == 'SELECT'){
+          if (frm[f].classList.contains("choices__input")){
+            if (frm[f].multiple == true){
+              //console.log(data[frm[f].id]);
+              choice[dataclass][frm[f].id].setChoiceByValue(JSON.parse(data[frm[f].id]));
+            }else {
+              choice[dataclass][frm[f].id].setChoiceByValue(data[frm[f].id]);
+            }
+          } else {
+            frm[f].value=data[frm[f].id];
+          }
+          
+        }
+      }
+      
+    }
+  }
+}
+
+function fillselectlist(obj,data,vidcol,vvalcol){
+  var sellist = [];
+  obj.clearStore();
+  if (data){
+    for (var i in data){
+      sellist.push({value:data[i][vidcol],label:data[i][vvalcol]});
+    }
+  }
+  obj.setChoices(sellist, 'value', 'label', true);
+  return false;
+}
\ No newline at end of file
diff --git a/backoffice/js/module_global.js b/backoffice/js/module_global.js
new file mode 100644
index 00000000..ddc58c03
--- /dev/null
+++ b/backoffice/js/module_global.js
@@ -0,0 +1,37 @@
+document.addEventListener("DOMContentLoaded", function() {
+  //console.log( "Iframe "+ location.pathname.substring(location.pathname.lastIndexOf("/")) +" ready!" );
+  initpage();
+});
+
+
+function table_setbounds(tblmodule){
+  // var cols =  $("#tbl_"+tblmodule+" > tbody > tr:first-child").children();
+  //   var colnum = cols.length -1;
+  //   console.log("childnum:" + colnum);
+  //   for (var i=1;i<=colnum;i++){
+  //     wx = $("#tbl_"+tblmodule+" > tbody > tr:first-child > td:nth-child("+ i +")").width();
+  //     // wx = wx +3;
+  //     $("#tbl_"+tblmodule+"_head > thead > tr > th:nth-child("+ i +")").width(wx);
+  //   }
+}
+
+function sectionload(sectionid,callback){
+  var sec = document.querySelectorAll('section');
+  for (var i in sec){
+    sec[i].style.display = 'none';
+  }
+  if (callback){
+    callback;
+  }
+  document.getElementById(sectionid).style.display = 'block';
+}
+
+
+function emptyform(id){
+  // $("#" + id + "> input,select,textarea").each(
+    // function(){
+      // console.log("Set Empty Value On" + $(this).id);
+    // }
+
+  // );
+}
\ No newline at end of file
diff --git a/backoffice/js/request.js b/backoffice/js/request.js
new file mode 100644
index 00000000..58ae6aad
--- /dev/null
+++ b/backoffice/js/request.js
@@ -0,0 +1,118 @@
+var api = location.origin + location.pathname.substring(0,location.pathname.lastIndexOf('/')) + '/api/';
+if (location.pathname.indexOf('module') > 0){
+  api = location.origin + location.pathname.substring(0, location.pathname.indexOf('module')) + 'api/';
+}
+// console.log(api);
+var req = {
+    multipartform: function(url,frmdata,callback=null){
+    var ret = null;
+    var rdata = null;
+    var async = false;
+    if (callback){
+      async=true;
+    }
+    
+    var request = new XMLHttpRequest();
+    
+    //console.log(frmdata);
+    var sendurl = api + url;
+    //console.log("sending URL: " + "POST" + " => " +sendurl);
+    request.open("POST", sendurl, true);
+    request.onload = function(){
+      if (request.status >= 200 && request.status <= 400){
+        console.log("Status returned: " + request.status + "resp:" + request.getResponseHeader("Content-Type"));
+        if (request.getResponseHeader("Content-Type").indexOf('application/json') == 0){
+          var xparse = JSON.parse(request.responseText);
+          ret = xparse.result;
+          console.log(ret);
+        }
+        else {
+          ret = request.responseText;
+        }
+        if (async){
+          callback(ret);
+        }
+      } else {
+        alert("ServerERROR:" + request.status + "\n" + request.responseText);
+      }
+    };
+    request.onerror = function(){
+      alert("Connection ERROR!\n" + url);
+    };
+  
+      request.setRequestHeader('Content-Type','multipart/form-data; charset=UTF-8');
+      request.send(frmdata);
+    return ret;
+  },
+  reqdata: function(method,url,data,callback=null){
+    
+    var ret = null;
+    var rdata = null;
+    var async = false;
+    if (callback){
+      async=true;
+    }
+    
+    var request = new XMLHttpRequest();
+    if (typeof data == 'object'){
+      var xdata = [];
+      for (var i in data){
+         var value = '';
+         if (typeof(data[i]) == 'object'){
+           value = encodeURIComponent(JSON.stringify(data[i]));
+         } else {
+           value = encodeURIComponent(data[i]);
+         }
+         xdata.push(i + "=" + value);
+      }
+      rdata = xdata.join("&");
+    }else {
+      rdata = data;
+    }
+    //console.log("Data to send: " + decodeURIComponent(rdata));
+    var sendurl = api + url;
+    if (method.toUpperCase() == 'GET'){
+      sendurl = sendurl + '?' + rdata;
+    }
+    //console.log("sending URL: " + method + " => " +sendurl + '?' + rdata);
+    request.open(method.toUpperCase(), sendurl, true);
+    request.onload = function(){
+      if (request.status >= 200 && request.status <= 400){
+        // console.log("Status returned: " + request.status + "resp:" + request.getResponseHeader("Content-Type"));
+        if (request.getResponseHeader("Content-Type").indexOf('application/json') == 0){
+          if (request.responseText){
+            var xparse = JSON.parse(request.responseText);
+            ret = xparse.result;
+          } else {
+            ret = null;  
+          }
+          
+        }else {
+          ret = request.responseText;
+        }
+        callback(ret);
+      } else {
+        //console.log("ServerERROR: " + request.status + "\n" + request.responseText);
+        alert("ServerERROR:" + request.status + "\n" + request.responseText);
+      }
+    };
+    request.onerror = function(){
+      //console.log("ERROR: connection ERROR\n" + url);
+      alert("Connection ERROR!\n" + url);
+    };
+    if (method.toUpperCase() == 'POST'){
+      request.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');
+      request.send(rdata);
+    } else {
+      //request.withCredentials = true;
+      request.send();
+    }
+    return ret;
+  },
+  asyncNoEvent: function(data){
+    console.log("query done");
+    console.log(data);
+    console.log("done");
+  }
+  
+}
\ No newline at end of file
diff --git a/backoffice/tmpl/block/cgu.tt b/backoffice/tmpl/block/cgu.tt
new file mode 100644
index 00000000..51564c14
--- /dev/null
+++ b/backoffice/tmpl/block/cgu.tt
@@ -0,0 +1,216 @@
+
+

+1 : Objet

+

+Les présentes « conditions générales d'utilisation » +ont pour objet l'encadrement juridique des modalités de mise à +disposition des services du site solana-architecture.lu +et leur utilisation par « l'Utilisateur ».

+

+Les conditions générales d'utilisation doivent être acceptées par +tout Utilisateur souhaitant accéder au site. Elles constituent le +contrat entre le site et l'Utilisateur. L'accès au site par +l'Utilisateur signifie son acceptation des présentes conditions +générales d'utilisation.

+

+ En cas de non-acceptation des conditions générales d'utilisation + stipulées dans le présent contrat, l'Utilisateur se doit de + renoncer à l'accès des services proposés par le site.

+

Solana Architecture s.à r.l. se réserve le droit de modifier unilatéralement et + à tout moment le contenu des présentes conditions générales + d'utilisation.

+

+2 : Mentions légales

+

+L'édition du site solana-architecture.lu est +assurée par l'entreprise Solana Architecture s.à r.l. dont le +siège social est situé à 61, rue des Trévires - L- Luxembourg-Bonnevoie.

+

+Responsable de la publication Web est Martin Solana (Gérant).

+ + +

3 : +Définitions

+

+La présente clause a pour objet de définir les différents termes +essentiels du contrat :

+
  • + Utilisateur : ce terme désigne toute personne qui utilise le + site ou l'un des services proposés par le site.

    +
  • Contenu + utilisateur : ce sont les données transmises par l'Utilisateur + au sein du site.

    +
  • Membre : + l'Utilisateur qui est employé d'une entreprise qui aimerais transferer des soumissions proposé par Solana Architecture s.à r.l. et qui peut s'identifié au site.

    +
  • Identifiant + et mot de passe : c'est l'ensemble des informations nécessaires + à l'identification d'un Utilisateur sur le site. L'identifiant et + le mot de passe permettent à l'Utilisateur d'accéder à des + services réservés aux membres du site. Le mot de passe est + confidentiel.

    +

4 : +accès aux services

+

+Le site permet à l'Utilisateur un accès gratuit aux services +suivants :

+
  • + Modifier ses données personnelles est les données du club;

    +
  • Transférer et télécharger les documents nécessaires pour faire des soumissions;

    +
  • consulter les information et documents utiles et nécessaires pour faire des soumissions;

    +

+Le site est accessible gratuitement en tout lieu à tout Utilisateur +ayant un accès à Internet. Tous les frais supportés par +l'Utilisateur pour accéder au service (matériel informatique, +logiciels, connexion Internet, etc.) sont à sa charge.

+

+Selon le cas :

+

+L'Utilisateur non membre n'a pas accès aux services réservés aux +membres. Pour cela, il doit s'identifier à l'aide de son identifiant +et de son mot de passe.

+ +

+Le site met en œuvre tous les moyens mis à sa disposition pour +assurer un accès de qualité à ses services. L'obligation étant de +moyens, le site ne s'engage pas à atteindre ce résultat.

+

+Tout événement dû à un cas de force majeure ayant pour +conséquence un dysfonctionnement du réseau ou du serveur n'engage +pas la responsabilité de solana-architecture.lu.

+

+L'accès aux services du site peut à tout moment faire l'objet d'une +interruption, d'une suspension, d'une modification sans préavis pour +une maintenance ou pour tout autre cas. L'Utilisateur s'oblige à ne +réclamer aucune indemnisation suite à l'interruption, à la +suspension ou à la modification du présent contrat.

+

+L'Utilisateur a la possibilité de contacter le site par messagerie +électronique à l'adresse info [ at ] solana-architecture.lu. +

+

5 : +Propriété intellectuelle

+

+Les marques, logos, signes et tout autre contenu du site font l'objet +d'une protection par le Code de la propriété intellectuelle et plus +particulièrement par le droit d'auteur.

+

+L'Utilisateur sollicite l'autorisation préalable du site pour toute +reproduction, publication, copie des différents contenus.

+

+L'Utilisateur s'engage à une utilisation des contenus du site dans +un cadre strictement privé. Une utilisation des contenus à des fins +commerciales est strictement interdite.

+

+Tout contenu mis en ligne par l'Utilisateur est de sa seule +responsabilité. L'Utilisateur s'engage à ne pas mettre en ligne de +contenus pouvant porter atteinte aux intérêts de tierces personnes. +Tout recours en justice engagé par un tiers lésé contre le site +sera pris en charge par l'Utilisateur. +

+

+Le contenu de l'Utilisateur peut être à tout moment et pour +n'importe quelle raison supprimé ou modifié par le site. +L'Utilisateur ne reçoit aucune justification et notification +préalablement à la suppression ou à la modification du contenu +Utilisateur.

+

6 : +Données personnelles

+

+Les informations demandées à l'inscription au site sont +nécessaires et obligatoires pour la création du compte de +l'Utilisateur. En particulier, l'adresse électronique pourra être +utilisée par le site pour l'administration, la gestion et +l'animation du service.

+

+Le site assure à l'Utilisateur une collecte et un traitement +d'informations personnelles dans le respect de la vie privée +conformément à la loi européen des protection de données relative à +l'informatique, aux fichiers et aux libertés.

+

+L'Utilisateur dispose d'un droit d'accès, de rectification, de +suppression et d'opposition de ses données personnelles. +L'Utilisateur exerce ce droit via :

+
  • + Formulaire mis à disposition;

    + +
+

7 : +Responsabilité et force majeure

+

+Les sources des informations diffusées sur le site sont réputées +fiables. Toutefois, le site se réserve la faculté d'une +non-garantie de la fiabilité des sources. Les informations données +sur le site le sont à titre purement informatif. Ainsi, +l'Utilisateur assume seul l'entière responsabilité de l'utilisation +des informations et contenus du présent site.

+

+L'Utilisateur s'assure de garder son mot de passe secret. Toute +divulgation du mot de passe, quelle que soit sa forme, est interdite.

+

+L'Utilisateur assume les risques liés à l'utilisation de son +identifiant et mot de passe. Le site décline toute responsabilité.

+

+Tout usage du service par l'Utilisateur ayant directement ou +indirectement pour conséquence des dommages doit faire l'objet d'une +indemnisation au profit du site.

+

+Une garantie optimale de la sécurité et de la confidentialité des +données transmises n'est pas assurée par le site. Toutefois, le +site s'engage à mettre en œuvre tous les moyens nécessaires afin +de garantir au mieux la sécurité et la confidentialité des +données.

+

+La responsabilité du site ne peut être engagée en cas de force +majeure ou du fait imprévisible et insurmontable d'un tiers.

+

8 : +Liens hypertextes

+

+De nombreux liens hypertextes sortants sont présents sur le site, +cependant les pages web où mènent ces liens n'engagent en rien la +responsabilité de solana-architecture.lu qui n'a +pas le contrôle de ces liens.

+

+L'Utilisateur s'interdit donc à engager la responsabilité du site +concernant le contenu et les ressources relatives à ces liens +hypertextes sortants.

+

9 : +Évolution du contrat

+

+Le site se réserve à tout moment le droit de modifier les clauses +stipulées dans le présent contrat.

+

10 : +Durée

+

+La durée du présent contrat est indéterminée. Le contrat produit +ses effets à l'égard de l'Utilisateur à compter de l'utilisation +du service.

+

11 : +Droit applicable et juridiction compétente

+

+La législation luxembourgeoise s'applique au présent contrat. En cas +d'absence de résolution amiable d'un litige né entre les parties, +seuls les tribunaux luxembourgeoises +sont compétents.

+ +

12 : +Publication par l'Utilisateur

+

+Le site permet aux membres de télécharger des fichiers.

+

+Dans ses publications, le membre s'engage à respecter les règles +de la Netiquette et les règles de droit en vigueur.

+

+Le site exerce une modération sur les publications et se réserve le droit de +refuser leur mise en ligne, sans avoir à s'en justifier auprès du +membre.

+

+Le membre reste titulaire de l'intégralité de ses droits de +propriété intellectuelle. Mais en publiant une publication sur le +site, il cède à l' entreprise SOlana Architecture s.à r.l. le droit non exclusif et +gratuit de représenter, reproduire, adapter, modifier, diffuser et +distribuer sa publication, directement ou par un tiers autorisé, +dans le monde entier, sur tout support (numérique ou physique), pour +la durée de la propriété intellectuelle. Le Membre cède notamment +le droit d'utiliser sa publication sur internet et sur les réseaux +

+ \ No newline at end of file diff --git a/backoffice/tmpl/block/dlguploadfile.tt b/backoffice/tmpl/block/dlguploadfile.tt new file mode 100644 index 00000000..1126d3e1 --- /dev/null +++ b/backoffice/tmpl/block/dlguploadfile.tt @@ -0,0 +1,91 @@ +
+ +
+
+ × +

Fichier eroplueden

+
+
+
+ [% fieldhidden("table","upload",'','') %] + [% fieldhidden("row_id","upload","ident",'') %] + [% fieldhidden("filetype","upload",'','') %] + [% fieldfile("file","upload","Fichier auswielen",'','','') %] +
+
+
+ + [% formsavefilebutton('uploadfile','eroplueden','') %] +
+
+
+ \ No newline at end of file diff --git a/backoffice/tmpl/block/head.tt b/backoffice/tmpl/block/head.tt new file mode 100644 index 00000000..32d38589 --- /dev/null +++ b/backoffice/tmpl/block/head.tt @@ -0,0 +1,30 @@ + + + + + [% sitename %] - [% pagename %] + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backoffice/tmpl/block/snackbar.tt b/backoffice/tmpl/block/snackbar.tt new file mode 100644 index 00000000..464a1624 --- /dev/null +++ b/backoffice/tmpl/block/snackbar.tt @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/backoffice/tmpl/block/toolbars.tt b/backoffice/tmpl/block/toolbars.tt new file mode 100644 index 00000000..9c0073a2 --- /dev/null +++ b/backoffice/tmpl/block/toolbars.tt @@ -0,0 +1,9 @@ +[% MACRO tabletoolbar(ident) BLOCK -%] + +
+ + + + +
+[% END -%] \ No newline at end of file diff --git a/backoffice/tmpl/macro/fields.tt b/backoffice/tmpl/macro/fields.tt new file mode 100644 index 00000000..cb3d262f --- /dev/null +++ b/backoffice/tmpl/macro/fields.tt @@ -0,0 +1,118 @@ +[% MACRO fieldhidden(column,table,ident,value) BLOCK -%] + +[% END -%] +[% MACRO fieldeditbox(column,table,title,size,state,value,plhold) BLOCK -%] +
+ + 0 %][% state %][% END %]/> + +
+[% END -%] +[% MACRO fieldfile(column,table,title,size,state,value) BLOCK -%] +
+ + 0 %][% state %][% END %]/> + +
+[% END -%] +[% MACRO fieldpasswordbox(column,table,title,size,state,value) BLOCK -%] +
+ + 0 %][% state %][% END %]/> + +
+[% END -%] +[% MACRO fieldtagbox(column,table,title,size,state,value) BLOCK -%] +
+ + 0 %][% state %][% END %]/> + +
+[% END -%] +[% MACRO fieldcheckbox(column,table,title,size,state,value) BLOCK -%] +
+
+ 0 %][% state %][% END %]> + +
+[% END -%] + +[% MACRO fieldemailbox(column,table,title,size,state,value) BLOCK -%] +
+ + 0 %][% state %][% END %]/> +
+[% END -%] + +[% MACRO fieldselectbox(column,table,title,size,state,value) BLOCK -%] + [% IF state.length > 0 %] + [% fieldeditbox(column,table,title,size,state,value) %] + [% ELSE %] +
+ + + +
+ [% END %] +[% END -%] +[% MACRO fieldmultiselectbox(column,table,title,size,state,value) BLOCK -%] + [% IF state.length > 0 %] + [% fieldeditbox(column,table,title,size,state,value) %] + [% ELSE %] +
+ + + +
+ [% END %] +[% END -%] +[% MACRO fielddatebox(column,table,title,size,state,value) BLOCK -%] +
+ + 0 %][% state %][% END %]]/> + +
+[% END -%] +[% MACRO fieldtextarea(column,table,title,size,state,height,value) BLOCK -%] +
+ + +
+[% END -%] +[% MACRO fieldrichtextarea(column,table,title,size,state,height,value) BLOCK -%] +
+ + +
+[% END -%] + +[% MACRO formsavebutton(formname,btnname) BLOCK -%] +
+ +
+[% END -%] +[% MACRO formsavetextfilebutton(formname,btnname) BLOCK -%] +
+ +
+[% END -%] +[% MACRO formdlgsavebutton(formname,btnname,clbk) BLOCK -%] + +[% END -%] +[% MACRO formsavefilebutton(formname,btnname,container) BLOCK -%] +[% IF container.length > 0 %] +
+ [% END %] + +[% IF container.length > 0 %] +
+[% END %] +[% END -%] + + diff --git a/backoffice/tmpl/module/companies/index.js b/backoffice/tmpl/module/companies/index.js new file mode 100644 index 00000000..6bfb3538 --- /dev/null +++ b/backoffice/tmpl/module/companies/index.js @@ -0,0 +1,164 @@ +var tbl = null; +[% sid = session.id %] +[% IF (session.usergroups.search('admin') != 1) %] +[% qcompany = dksdb.query("select id_company from vw_userlist where id=$sid;") %] +[% company = qcompany.get_all() %] +var idcompany = "[% company.0.id_company %]"; +[% ELSE %] +var idcompany=null; +[% END %] +var choice = {"submissionaccess":{"projects":null,"cdm":null,"cdm_requests":null}}; +var tab_id_projects = null; +var tab_id_cdms = null; +var tab_id_cdm_requests = null; +function initpage(){ + [% IF (session.usergroups.search('admin') == 1) %] + // choice["submissionaccess"]["projects"] = new Choices('#projects',{ + // searchEnabled: false, + // itemSelectText: 'sélectionner...', + // removeItemButton: true, + // choices : [] + // }); + + // choice["submissionaccess"]["cdm"] = new Choices('#cdm',{ + // searchEnabled: false, + // itemSelectText: 'sélectionner...', + // removeItemButton: true, + // choices : [] + // }); + // [% END %] + // choice["submissionaccess"]["cdm_requests"] = new Choices('#cdm_requests',{ + // searchEnabled: false, + // itemSelectText: 'sélectionner...', + // removeItemButton: true, + // choices : [] + // }); + getprojects(); + getcdms(); + [% IF (session.usergroups.search('admin') == 1) %] + tbl = new Tabulator("#tbl_companies", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"entreprise", field:"company",headerFilter:"input"}, + {title:"address", field:"address",headerFilter:"input"}, + {title:"nbr. utilsateurs",field:"num_users"}, + {title:"pointeuese",field:"num_users"}, + ]}); + gettbldata(); + + [% ELSE %] + getcompanydata(idcompany); + [% END %] + + +} +[% IF (session.usergroups.search('admin') == 1) %] +function gettbldata(){ + req.reqdata("POST","db.cgi",{"get":"companylist"},loadtbldata); + +} + +function loadtbldata(data){ + if (data && data.sqldata){ + tbl.setData(data.sqldata); + } +} + +function edit(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + var uid = udata[0].id; + getcompanydata(uid); + document.getElementById("pnl_table").style.display = 'none'; + document.getElementById("pnl_company").style.display = 'block'; + } + +} + +function add(){ + cleanform('companies'); + viewtable(); +} + +function remove(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + if (confirm("Êtes vous sûre de supprimer l'entreprise selectionné?")){ + req.reqdata("POST","db.cgi",{"del":"1","ident_companies_id":udata[0].id},gettbldata); + } + } + +} +[% END %] + + +function getcompanydata(id){ + req.reqdata("POST","db.cgi",{"get":"companydata","filter":"id=" + id},fillformcompany); + req.reqdata("POST","db.cgi",{"get":"submissionaccess","filter":"id_company=" + id},fillsubmissionaccess); +} + + +function fillsubmissionaccess(data){ + console.log("FILL Submission Access!"); + console.log(data); + cleanform('submissionaccess'); + if (data && data.sqldata){ + //var frm = document.querySelectorAll('.data_submissionaccess'); + fillformbydataclass('submissionaccess',data.sqldata[0],false); + + } +} + +function fillformcompany(data){ + // console.log(data); + cleanform('companies'); + var isreadonly = false; + [% IF (session.usergroups.search('admin') != 1) %] + if (data.sqldata[0]["validated"] == 1){isreadonly = true;} + [% END %] + if (data && data.sqldata){ + fillformbydataclass('companies',data.sqldata[0],isreadonly); + } + if (isreadonly == true){ + document.getElementById("btnsave_companies").style.display = 'none'; + } +} + +function viewtable(){ + gettbldata(); + document.getElementById("pnl_table").style.display = 'block'; + document.getElementById("pnl_company").style.display = 'none'; +} + +[% IF (session.usergroups.search('admin') == 1) %] +function getprojects(){ + req.reqdata("POST","db.cgi",{"get":"projectlist"},fillprojectlist); +} + +function fillprojectlist(data){ + console.log("fill projectlist"); + fillselectlist(choice["submissionaccess"]["projects"],data.sqldata,'id','project'); + return false; +} + +[% END %] + +function getcdms(){ + req.reqdata("POST","db.cgi",{"get":"cdmlist"},fillcdmlist); +} + +function fillcdmlist(data){ + console.log("fill cdmlist"); + fillselectlist(choice["submissionaccess"]["cdm_requests"],data.sqldata,'id','cdm'); + //var cdmlist = []; + [% IF (session.usergroups.search('admin') == 1) %] + fillselectlist(choice["submissionaccess"]["cdm"],data.sqldata,'id','cdm'); + //choice["submissionaccess"]["cdm"].clearStore(); + [% END %] + + return false; +} \ No newline at end of file diff --git a/backoffice/tmpl/module/companies/index.tt b/backoffice/tmpl/module/companies/index.tt new file mode 100644 index 00000000..f27ad264 --- /dev/null +++ b/backoffice/tmpl/module/companies/index.tt @@ -0,0 +1,86 @@ +[% PROCESS macro/fields.tt %] +[% IF (session.usergroups.search('admin') == 1) %] +
+
+ + + + + + + +
+
+
+
+
+[% END %] +
+ +
+
+
+

données de l'entreprise

+
+
+ + [% fieldhidden('id','companies','ident') %] +
+ [% fieldeditbox('company','companies','Entreprise','w3-half','','','') %] + [% IF (session.usergroups.search('admin') == 1) %] + [% fieldcheckbox('validated','companies','validé','w3-half','','','') %] + [% END %] + [% fieldeditbox('address','companies','Adresse','','','','') %] + [% fieldeditbox('zip','companies','CP','w3-quarter','','','') %] + [% fieldeditbox('city','companies','Ville','w3-half','','','') %] + [% fieldeditbox('country','companies','Pays','w3-quarter','','','Luxembourg') %] +
+
+ [% fieldeditbox("tradetype",'companies',"Raison sociale",'w3-quarter','','','s.à r.l.') %] + [% fieldselectbox("site","companies","Siège",'w3-quarter','','') %] + [% fieldeditbox("nace",'companies',"Type d'entreprise",'w3-half','','','construction') %] +
+
+ [% fieldeditbox('autorisation','companies',"Auth. d'établ.",'w3-third','','','') %] + [% fieldeditbox('comregister','companies',"Reg. de Com.",'w3-third','','','B123456') %] + [% fieldeditbox('vatnumber','companies',"TVA Int.",'w3-third','','','LU12341234') %] + +
+
+ [% fieldeditbox('insurance_number','companies',"No. Assurance",'w3-half','','','') %] + [% fieldeditbox('insurance','companies',"Assureur",'w3-third','','','') %] +
+
+ + [% formsavebutton('companies','sauvegarder') %] +
+
+
+ +
+
+

accès soumissions

+
+
+ + [% fieldhidden('id','submissionaccess','ident') %] + [% fieldhidden('id_company','submissionaccess','') %] + [% %] +
+ [% dataeditable = "" %] + [% IF (session.usergroups.search('admin') != 1) %] + [% dataeditable = "readonly" %] + [% END %] + [% fieldmultiselectbox('projects','submissionaccess','Accès au projets','',dataeditable,'') %] + [% fieldmultiselectbox('cdm','submissionaccess','Accès au corps de métiers','',dataeditable,'') %] + [% fieldmultiselectbox('cdm_requests','submissionaccess',"Demande d'accès au corps de métiers",'','','') %] +
+ + [% formsavebutton('submissionaccess','sauvegarder') %] +
+ +
+
+
+
\ No newline at end of file diff --git a/backoffice/tmpl/module/dashboard/index.js b/backoffice/tmpl/module/dashboard/index.js new file mode 100644 index 00000000..12e871f9 --- /dev/null +++ b/backoffice/tmpl/module/dashboard/index.js @@ -0,0 +1,3 @@ +function initpage(){ + +} \ No newline at end of file diff --git a/backoffice/tmpl/module/dashboard/index.tt b/backoffice/tmpl/module/dashboard/index.tt new file mode 100644 index 00000000..a0c53771 --- /dev/null +++ b/backoffice/tmpl/module/dashboard/index.tt @@ -0,0 +1,19 @@ +[% appaccess = dksdb.prepare("select replace(ap.icon,'.svg','_white.svg') as icon,ap.app,ap.name,ug.usergroup from useringroups uig join apps ap on (uig.id_group=ap.id_usergroup) join usergroups ug on (uig.id_group=ug.id) where uig.id_user=? and ap.app != 'dashboard' order by ap.sort; ") %] + +
+
+
+
+
+
+ + [% FOREACH ap = appaccess.execute(session.id) %] +
+ +
+ [% END %] +
+
+
diff --git a/backoffice/tmpl/module/profile/dlgpassword.tt b/backoffice/tmpl/module/profile/dlgpassword.tt new file mode 100644 index 00000000..7c3d6e3c --- /dev/null +++ b/backoffice/tmpl/module/profile/dlgpassword.tt @@ -0,0 +1,81 @@ +
+ +
+
+ × +

changement mot de passe

+
+
+
+
+ Votre mot de passe doit avoir: +
    +
  • au moins 10 charatères
  • +
  • des lettres majuscule et minuscules
  • +
  • au moins 1 nombre
  • +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ \ No newline at end of file diff --git a/backoffice/tmpl/module/profile/dlgusername.tt b/backoffice/tmpl/module/profile/dlgusername.tt new file mode 100644 index 00000000..4879285b --- /dev/null +++ b/backoffice/tmpl/module/profile/dlgusername.tt @@ -0,0 +1,90 @@ + +
+
+
+ × +

Changement de l'email resp. login

+
+
+
+
+ [% fieldeditbox('newuseremail','onlydisplay','Nei E-Mail-Address','','','') %] +
+ +
+
+ + + +
+
+
+ \ No newline at end of file diff --git a/backoffice/tmpl/module/profile/index.js b/backoffice/tmpl/module/profile/index.js new file mode 100644 index 00000000..63fb0fa9 --- /dev/null +++ b/backoffice/tmpl/module/profile/index.js @@ -0,0 +1,64 @@ +var iduser="[% session.id %]"; + +function initpage(){ + flatpickr(".datefield",{altInput: true, + altFormat: "d.m.Y", + dateFormat: "Y-m-d", + "locale": "fr", + }); + loadmemberdata(iduser); + loadaccountdata(iduser); +} + +function loadaccountdata(id){ + req.reqdata("POST","db.cgi",{"get":"userdata","filter":"id="+id},fillformaccount); +} +function loadmemberdata(id){ + req.reqdata("POST","db.cgi",{"get":"memberdata","filter":"id_user="+id},fillformmember); +} +function fillformaccount(data){ + if (data && data.sqldata){ + var frm = document.querySelectorAll('.data_users'); + for (var f in frm){ + if (data.sqldata[0][frm[f].id]){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT')){ + if (frm[f].classList.contains("datefield")){ + frm[f]._flatpickr.setDate(data.sqldata[0][frm[f].id]); + } else { + frm[f].value=data.sqldata[0][frm[f].id]; + } + } + } + } + } +} + + +function fillformmember(data){ + // console.log(data); + if (data && data.sqldata){ + var frm = document.querySelectorAll('.data_members'); + for (var f in frm){ + // console.log(frm[f].tagName); + // console.log(frm[f].id); + if (data.sqldata[0][frm[f].id]){ + //console.log(frm[f].tagName + " " + frm[f].type + " " + frm[f].id); + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT')){ + if (frm[f].classList.contains("datefield")){ + frm[f]._flatpickr.setDate(data.sqldata[0][frm[f].id]); + } else { + frm[f].value=data.sqldata[0][frm[f].id]; + } + + } + } + + } + // if (document.getElementById("profile_photo").value != ""){ + // document.getElementById("img_profile_photo").src = '../../data/members/' + document.getElementById("profile_photo").value; + // } + // console.log(frm); + } +} + + diff --git a/backoffice/tmpl/module/profile/index.tt b/backoffice/tmpl/module/profile/index.tt new file mode 100644 index 00000000..41f21391 --- /dev/null +++ b/backoffice/tmpl/module/profile/index.tt @@ -0,0 +1,45 @@ + +[% PROCESS macro/fields.tt %] +
+
+
+

données d'accès

+
+
+ [% #fieldhidden('users','id','ident') %] + [% fieldeditbox('username','users','E-Mail (Login)','','readonly','') %] +
+ +
+
+
+ + +
+
+
+
+ +
+
+

données personelles

+
+
+ + [% fieldhidden('id','members','ident') %] + [% fieldeditbox('surname','members','Nom','w3-half','') %] + [% fieldeditbox('prename','members','Prénom','w3-half','') %] + [% fieldeditbox('job','members','Position','w3-half','') %] + [% fieldeditbox('phone','members','Téléphone','w3-half','') %] +
+ [% formsavebutton('members','sauvegarder') %] +
+
+
+
+ + [% #INCLUDE block/dlgcropper.tt %] + [% INCLUDE module/profile/dlgpassword.tt %] + [% INCLUDE module/profile/dlgusername.tt %] + + diff --git a/backoffice/tmpl/module/projects/cdm.js b/backoffice/tmpl/module/projects/cdm.js new file mode 100644 index 00000000..ad9a2a67 --- /dev/null +++ b/backoffice/tmpl/module/projects/cdm.js @@ -0,0 +1,93 @@ +var tbl= null; +//var sel_project = null; +var choice = {"cdm":{"project_ids":null}}; +function initpage(){ + choice["cdm"]["project_ids"] = new Choices('#project_ids',{ + searchEnabled: false, + itemSelectText: 'sélectionner...', + removeItemButton: true, + choices : [] + }); + tbl = new Tabulator("#tbl_cdm", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"corps de métier", field:"cdm",headerFilter:"input"}, + {title:"projets", field:"projectlist",headerFilter:"input"}, + + ], + }); + +gettbldata(); +getprojects(); +} + +function gettbldata(){ + req.reqdata("POST","db.cgi",{"get":"cdmlist"},loadtbldata); +} + +function loadtbldata(data){ + if (data && data.sqldata){ + tbl.setData(data.sqldata); + } +} + +function aftersavecdm(){ + gettbldata(); + document.getElementById('dlgcdm').style.display='none'; +} + +function add(){ + fillcdmform(null); +} + +function edit(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + var uid = udata[0].id; + req.reqdata("POST","db.cgi",{"get":"cdmlist","filter":"id=" + udata[0].id},fillcdmform); + } +} + +function fillcdmform(data){ + cleanform("cdm"); + if (data && data.sqldata){ + fillformbydataclass("cdm",data.sqldata[0],false); + } + + document.getElementById('dlgcdm').style.display='block'; +} + +function remove(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + if (confirm("êtes-vous sûre de supprimer le corps de métier sélectionné?")){ + req.reqdata("POST","db.cgi",{"del":"1","ident_cdm_id":udata[0].id},gettbldata); + } + } + +} + +function view_projects(){ + parent.admin.loadpage('module/[% module %]/index.html','Projets'); +} + +function view_cdm(){ + parent.admin.loadpage('module/[% module %]/cdm.html','Corps de métiers'); +} + + + +function getprojects(){ + req.reqdata("POST","db.cgi",{"get":"projectlist"},fillprojectlist); +} + +function fillprojectlist(data){ + fillselectlist(choice["cdm"]["project_ids"],data.sqldata,'id','project'); + + return false; +} + diff --git a/backoffice/tmpl/module/projects/cdm.tt b/backoffice/tmpl/module/projects/cdm.tt new file mode 100644 index 00000000..3f44a138 --- /dev/null +++ b/backoffice/tmpl/module/projects/cdm.tt @@ -0,0 +1,36 @@ +[% PROCESS macro/fields.tt %] +
+
+ + + + + +
+
+
+ + +
+
+
+ × +

Projet

+
+
+ +
+ [% fieldhidden("id","cdm","ident",'') %] + [% fieldeditbox("cdm","cdm","Corps de métier",'','','','') %] + [% fieldmultiselectbox('project_ids','cdm','Projets','','','') %] +
+
+
+ + [% formdlgsavebutton('cdm','sauvegarder','aftersavecdm') %] +
+
+
+ + \ No newline at end of file diff --git a/backoffice/tmpl/module/projects/index.js b/backoffice/tmpl/module/projects/index.js new file mode 100644 index 00000000..9384551b --- /dev/null +++ b/backoffice/tmpl/module/projects/index.js @@ -0,0 +1,105 @@ +var tbl= null; +function initpage(){ + tinymce.init({ + selector: '.richeditarea', + plugins: 'paste importcss searchreplace autolink autoresize directionality code visualblocks visualchars link charmap advlist lists textpattern noneditable charmap emoticons', + + menubar: false, + statusbar: false, + toolbar: 'undo redo | bold italic underline strikethrough | fontsizeselect | alignleft aligncenter alignright alignjustify | numlist bullist | forecolor removeformat | charmap', + toolbar_sticky: true, + min_height: 350, + language: 'de', + content_css: [ + '[% abspath %]css/w3pro.css' + ], + branding: false, + importcss_append: true, + contextmenu: "link", + }); + tbl = new Tabulator("#tbl_projects", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"projet", field:"project",headerFilter:"input"}, + {title:"description", field:"description",headerFilter:"input", formatter:"html"}, + {title:"corps de métiers", field:"cdmlist",headerFilter:"input"}, + ], + }); + +gettbldata(); +} + +function gettbldata(){ + req.reqdata("POST","db.cgi",{"get":"projectlist"},loadtbldata); +} + +function loadtbldata(data){ + if (data && data.sqldata){ + tbl.setData(data.sqldata); + } +} + +function aftersaveproject(){ + formsaved(); + gettbldata(); + document.getElementById('dlgprojects').style.display='none'; +} + +function add(){ + fillprojectform(null); +} + +function edit(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + var uid = udata[0].id; + req.reqdata("POST","db.cgi",{"get":"projectlist","filter":"id=" + udata[0].id},fillprojectform); + } +} + +function fillprojectform(data){ + var frm = document.querySelectorAll('.data_projects'); + for (var f in frm){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT') || (frm[f].tagName == 'TEXTAREA')){ + if (frm[f].classList.contains("richeditarea")){ + tinymce.get(frm[f].id).setContent(""); + }else { + frm[f].value=""; + } + } + } + if (data && data.sqldata){ + for (var f in frm){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT') || (frm[f].tagName == 'TEXTAREA')){ + if (frm[f].classList.contains("richeditarea")){ + tinymce.get(frm[f].id).setContent(data.sqldata[0][frm[f].id]); + }else { + frm[f].value=data.sqldata[0][frm[f].id]; + } + } + } + } + document.getElementById('dlgprojects').style.display='block'; +} + +function remove(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + if (confirm("êtes-vous sûre de supprimer le projet sélectionné?")){ + req.reqdata("POST","db.cgi",{"del":"1","ident_projects_id":udata[0].id},gettbldata); + } + } + +} + +function view_projects(){ + parent.admin.loadpage('module/[% module %]/index.html','Projets'); +} + +function view_cdm(){ + parent.admin.loadpage('module/[% module %]/cdm.html','Corps de métiers'); +} \ No newline at end of file diff --git a/backoffice/tmpl/module/projects/index.tt b/backoffice/tmpl/module/projects/index.tt new file mode 100644 index 00000000..10130f60 --- /dev/null +++ b/backoffice/tmpl/module/projects/index.tt @@ -0,0 +1,39 @@ +[% PROCESS macro/fields.tt %] +[% IF (session.usergroups.search('admin') == 1) %] +
+
+ + + + + + + + +
+
+
+[% END %] +
+[% IF (session.usergroups.search('admin') == 1) %] +
+
+
+ × +

Projet

+
+
+
+ [% fieldhidden("id","projects","ident",'') %] + [% fieldeditbox("project","projects","Projet",'','','','') %] + [% fieldrichtextarea("description","projects","Description",'','','300px','') %] +
+
+
+ + [% formdlgsavebutton('projects','sauvegarder','aftersaveproject') %] +
+
+
+[% END %] diff --git a/backoffice/tmpl/module/submissions/index.js b/backoffice/tmpl/module/submissions/index.js new file mode 100644 index 00000000..72d75224 --- /dev/null +++ b/backoffice/tmpl/module/submissions/index.js @@ -0,0 +1,105 @@ +var tbl= null; +function initpage(){ + tbl = new Tabulator("#tbl_submissions", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"projet", field:"project",headerFilter:"input",download:true}, + {title:"corps de métier", field:"cdm",headerFilter:"input",download:true}, + {title:"fichier", field:"file",headerFilter:"input",download:true}, + ], + }); + +//gettbldata(); +} + +function gettbldata(){ + [% IF (session.usergroups.search('admin') == 1) %] + req.reqdata("POST","db.cgi",{"get":"documentlist"},loadtbldata); + [% ELSE %] + req.reqdata("POST","db.cgi",{"get":"documentlist","filter":"userlist like '%\""+ session.id +"\"%"},loadtbldata); + [% END %] + +} + +function loadtbldata(data){ + if (data && data.sqldata){ + tbl.setData(data.sqldata); + } +} + +function add(){ + fillsubmissionform(null); +} + +function edit(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + var uid = udata[0].id; + req.reqdata("POST","db.cgi",{"get":"submissionlist","filter":"id=" + udata[0].id},fillsubmissionform); + } +} + +function fillsubmissionform(data){ + console.log(data); + var frm = document.querySelectorAll('.data_submissions'); + for (var f in frm){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT') || (frm[f].tagName == 'TEXTAREA')){ + frm[f].value=""; + } + } + if (data && data.sqldata){ + for (var f in frm){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT') || (frm[f].tagName == 'TEXTAREA')){ + frm[f].value=data.sqldata[0][frm[f].id]; + } + } + } + + document.getElementById('dlgsubmissions').style.display='block'; +} + +function remove(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + if (confirm("êtes-vous sûre de supprimer bordereau sélectionné?")){ + req.reqdata("POST","db.cgi",{"del":"1","ident_submissions_id":udata[0].id},gettbldata); + } + } + +} + + +// function xlsxexport(){ +// tbl.download("xlsx", "soumissions.xlsx", {sheetName:"soumissions"}); +// } + +// function pdfexport(){ +// tbl.download("pdf", "soumissions.pdf", { +// orientation:"landscape", + +// jsPDF:{ +// unit:"mm", +// }, +// autoTable(doc){ +// doc.text("Liste des soumissions sur solana-architecture.lu", 10, 10); +// // styles: { +// // fillColor: [100, 255, 255] +// // }, +// // columnStyles: { +// // id: {fillColor: 255} +// // }, +// return { +// margin: {top: 20}, +// } + +// }, +// documentProcessing:function(doc){ +// //carry out an action on the doc object +// } +// }); +// } + diff --git a/backoffice/tmpl/module/submissions/index.tt b/backoffice/tmpl/module/submissions/index.tt new file mode 100644 index 00000000..a234d40f --- /dev/null +++ b/backoffice/tmpl/module/submissions/index.tt @@ -0,0 +1,51 @@ +[% PROCESS macro/fields.tt %] +[% sid = session.id %] +[% company %] +[% IF (session.usergroups.search('admin') != 1) %] +[% qcompany = dksdb.query("select ul.id_company,cp.validated from vw_userlist ul join vw_companydata cp on (ul.id_company=cp.id) where ul.id=$sid;") %] +[% company = qcompany.get_all() %] +[% END %] +[% IF (session.usergroups.search('admin') == 1) || company.0.validated == "1" %] +
+
+ + + +
+
+ +
+ +
+
+
+ × +

Ajouter Soumission

+
+
+ [% fieldeditbox("project","display","Projet",'','','','') %] + [% fieldeditbox("cdm","display","Corps de Métier",'','','','') %] +
+ [% fieldhidden("id","submissions","ident",'') %] + [% fieldfile("filepdf","submissions","Fichier Bordereau (PDF)",'','','') %] + [% fieldfile("filexls","submissions","Fichier Bordereau (XLSX)",'','','') %] + [% fieldfile("fileother","submissions","Fichier",'','','') %] +
+
+
+ + +
+
+
+ +[% ELSE %] +
Votre Entreprise n'est pas validé, il se peux que vous données d'entreprise sont incomplètes!
+[% END %] diff --git a/backoffice/tmpl/module/upload/index.js b/backoffice/tmpl/module/upload/index.js new file mode 100644 index 00000000..f3db79c2 --- /dev/null +++ b/backoffice/tmpl/module/upload/index.js @@ -0,0 +1,45 @@ +var tbl= null; +function initpage(){ + tbl = new Tabulator("#tbl_upload", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"entreprise", field:"company",headerFilter:"input",download:true}, + {title:"projet", field:"project",headerFilter:"input",download:true}, + {title:"corps de métier", field:"cdm",headerFilter:"input",download:true}, + {title:"fichier", field:"file",headerFilter:"input",download:true}, + ], + }); + +gettbldata(); +} + +function gettbldata(){ + [% IF (session.usergroups.search('admin') == 1) %] + req.reqdata("POST","db.cgi",{"get":"documentlist"},loadtbldata); + [% ELSE %] + req.reqdata("POST","db.cgi",{"get":"documentlist","filter":"userlist like '%\""+ session.id +"\"%"},loadtbldata); + [% END %] + +} + +function loadtbldata(data){ + if (data && data.sqldata){ + tbl.setData(data.sqldata); + } +} + +function xlsxexport(){ + tbl.download("xlsx", "uploads.xlsx", {sheetName:"uploads"}); +} + +function add(){ + +} + +function remove(){ + +} \ No newline at end of file diff --git a/backoffice/tmpl/module/upload/index.tt b/backoffice/tmpl/module/upload/index.tt new file mode 100644 index 00000000..861c4e03 --- /dev/null +++ b/backoffice/tmpl/module/upload/index.tt @@ -0,0 +1,24 @@ +[% sid = session.id %] +[% company %] +[% IF (session.usergroups.search('admin') != 1) %] +[% qcompany = dksdb.query("select ul.id_company,cp.validated from vw_userlist ul join vw_companydata cp on (ul.id_company=cp.id) where ul.id=$sid;") %] +[% company = qcompany.get_all() %] +[% END %] +[% IF (session.usergroups.search('admin') == 1) || company.0.validated == "1" %] +
+
+ + +
+
+
+ +[% ELSE %] +
Votre Entreprise n'est pas validé, il se peux que vous données d'entreprise sont incomplètes!
+[% END %] \ No newline at end of file diff --git a/backoffice/tmpl/module/users/index.js b/backoffice/tmpl/module/users/index.js new file mode 100644 index 00000000..a9eaffe4 --- /dev/null +++ b/backoffice/tmpl/module/users/index.js @@ -0,0 +1,294 @@ +[% sid = session.id %] +[% IF (session.usergroups.search('admin') != 1) %] +[% qcompany = dksdb.query("select id_company from vw_userlist where id=$sid;") %] +[% company = qcompany.get_all() %] +[% END %] +var tbl= null; +var sel_usergroups = null; +var choice = {"members":{"id_company":null}}; + +function initpage(){ + [% IF (session.usergroups.search('admin') == 1) %] + sel_usergroups = new Choices('#usergroup_ids',{ + searchEnabled: false, + itemSelectText: 'auswielen...', + removeItemButton: true, + choices : [] + }); + choice["members"]["id_company"] = new Choices('#id_company',{ + searchEnabled: false, + itemSelectText: 'sélectionner...', + removeItemButton: true, + choices : [] + }); + tbl = new Tabulator("#tbl_users", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"Entreprise", field:"company",headerFilter:"input",download:true}, + {title:"Nom", field:"surname",headerFilter:"input",download:true}, + {title:"Prénom", field:"prename",headerFilter:"input",download:true}, + {title:"Accès",field:"group_ids",download:true,headerFilter:"input"}, + {title:"Position",field:"job",download:true}, + {title:"Téléphone",field:"phone",download:true}, + {title:"E-mail",field:"username",download:true}, + {title:"Bloqué", field:"blocked",download:true}, + ], +}); +getusergroups(); + [% ELSE %] + tbl = new Tabulator("#tbl_users", { + headerFilterPlaceholder:"filter...", + height: "95vh", + layout:"fitDataFill", + selectable:1, + responsiveLayout:"collapse", + columns:[ + {title:"Entreprise", field:"company",}, + {title:"Numm", field:"surname",headerFilter:"input"}, + {title:"Virnumm", field:"prename",headerFilter:"input"}, + {title:"Accès",field:"group_ids",download:true}, + {title:"Position",field:"job",download:true}, + {title:"Téléphone",field:"phone",download:true}, + {title:"E-mail",field:"username",download:true}, + ], +}); + [% END %] + gettbldata(); + getcompanies(); +} + +function gettbldata(){ + [% IF (session.usergroups.search('admin') == 1) %] + req.reqdata("POST","db.cgi",{"get":"userlist"},loadtbldata); + [% ELSE %] + req.reqdata("POST","db.cgi",{"get":"userlist","filter":"id_company=[% company.0.id_company %]"},loadtbldata); + [% END %] +} + +function loadtbldata(data){ + if (data && data.sqldata){ + tbl.setData(data.sqldata); + } +} + +// function xlsxexport(){ +// tbl.download("xlsx", "users.xlsx", {sheetName:"utilisateurs"}); +// } + +// function pdfexport(){ +// tbl.download("pdf", "users.pdf", { +// orientation:"landscape", + +// jsPDF:{ +// unit:"mm", +// }, +// autoTable(doc){ +// doc.text("Utilisateurs du site", 10, 10); +// // styles: { +// // fillColor: [100, 255, 255] +// // }, +// // columnStyles: { +// // id: {fillColor: 255} +// // }, +// return { +// margin: {top: 20}, +// } + +// }, +// documentProcessing:function(doc){ +// //carry out an action on the doc object +// } +// }); +// } +function edit(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + req.reqdata("POST","db.cgi",{"get":"userlist","filter":"id_member=" + udata[0].id_member},fillmemberform); + } +} + +function fillmemberform(data){ + cleanform("members"); + if (data && data.sqldata){ + fillformbydataclass("members",data.sqldata[0],false); + } + + document.getElementById('dlgmembers').style.display='block'; +} + +function getcompanies(){ + req.reqdata("POST","db.cgi",{"get":"companylist"},fillcompanylist); +} + +function fillcompanylist(data){ + fillselectlist(choice["members"]["id_company"],data.sqldata,'id','company'); + + return false; +} + + +function closedlgmembers(){ + gettbldata(); + document.getElementById('dlgmembers').style.display='none'; + return false; +} + +[% IF (session.usergroups.search('admin') == 1) %] +function remove(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + if (confirm("Êtes vous sûre de supprimer l'utilisateur selectionné?")){ + req.reqdata("POST","db.cgi",{"del":"1","ident_users_id":udata[0].id},gettbldata); + } + } + +} + function setusergroups(){ + var udata = tbl.getSelectedData(); + if (udata[0]){ + var uid = udata[0].id; + req.reqdata("POST","db.cgi",{"get":"userdata","filter":"id=" + uid},fillusergroupform); + } + } + function fillusergroupform(data){ + + if (data && data.sqldata){ + var frm = document.querySelectorAll('.data_users,.data_display,.data_useringroups'); + for (var f in frm){ + + if (data.sqldata[0][frm[f].id]){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT')) { + if (frm[f].id == "usergroup_ids"){ + + var argrp = data.sqldata[0][frm[f].id].split(","); + //console.log(argrp); + sel_usergroups.setChoiceByValue(argrp); + } else { + frm[f].value=data.sqldata[0][frm[f].id]; + } + } + } + } + document.getElementById('dlgusergroups').style.display='block'; + } + + } + + function getusergroups(){ + req.reqdata("POST","db.cgi",{"get":"usergroups"},fillusergroups); + return false; + } + function fillusergroups(data){ + var pug = []; + if (data && data.sqldata){ + for (var i in data.sqldata){ + pug.push({value:data.sqldata[i].id,label:data.sqldata[i].groupname}); + } + } + sel_usergroups.setChoices(pug, 'value', 'label', true); + + return false; + } + + function saveusergroupsform(){ + var flds = getformcontent("usergroups",{}); + //console.log(flds); + + req.reqdata("POST","db.cgi",{"fn":"setgroupaccess","params": flds.ident_users_id + ",'{" + flds.useringroups_usergroup_ids.join(',') +"}'"},ugsaved); + return false; + } + + function ugsaved(data){ + //console.log(data); + gettbldata(); + document.getElementById('dlgusergroups').style.display='none'; + formsaved(null); + } + + function setlogin(){ + var udata = tbl.getSelectedData(); + document.getElementById("nunmsg").innerHTML = '' + if (udata[0]){ + var uid = udata[0].id; + req.reqdata("POST","db.cgi",{"get":"userdata","filter":"id=" + uid},fillloginform); + } + } + function fillloginform(data){ + document.getElementById("blocked").checked = false; + document.getElementById("username").value = ""; + if (data && data.sqldata){ + var frm = document.querySelectorAll('.data_users,.data_display,.data_useringroups'); + for (var f in frm){ + + if (data.sqldata[0][frm[f].id]){ + if ((frm[f].tagName == 'INPUT') || (frm[f].tagName == 'SELECT')) { + + if (frm[f].type == "checkbox"){ + document.getElementById("blocked").checked = true; + } else { + frm[f].value=data.sqldata[0][frm[f].id]; + } + } + } + } + document.getElementById('dlglogin').style.display='block'; + } + } + + function setusername(){ + var flds = getformcontent("users",{}); + //console.log(flds); + flds["set"] = 1; + req.reqdata("POST","db.cgi",flds,loginsaved); + return false; + } + + function loginsaved(data){ + //console.log(data); + gettbldata(); + document.getElementById('dlglogin').style.display='none'; + formsaved(null); + } + function ugsaved(data){ + //console.log(data); + gettbldata(); + document.getElementById('dlgusergroups').style.display='none'; + formsaved(null); + } + + function sendnewpassword(){ + var flds = getformcontent("login",{}); + //console.log(flds); + return false; + } + + function checkemail(){ + var newusername = document.getElementById("username").value; + //console.log("username: " + newusername); + if (validateEmail(newusername)){ + req.reqdata("POST","db.cgi",{"get":"userdata","filter":"username='"+ newusername+ "' and id != " + document.getElementById("id").value},checkmailreturn); + }else { + document.getElementById("nunmsg").innerHTML= '
w.e.g. eng richteg email agin!
'; + } + return false; + } + + function checkmailreturn(data){ + //console.log(data); + if (data && data.sqldata.length > 0){ + document.getElementById("nunmsg").innerHTML= '
Et existéiert schon een aaneren Kont matt deser E-mail!
'; + } else { + setusername(); + } + return false; + } + + function validateEmail(email) { + var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); + } +[% END %] \ No newline at end of file diff --git a/backoffice/tmpl/module/users/index.tt b/backoffice/tmpl/module/users/index.tt new file mode 100644 index 00000000..8f925249 --- /dev/null +++ b/backoffice/tmpl/module/users/index.tt @@ -0,0 +1,98 @@ +[% PROCESS macro/fields.tt %] +[% IF (session.usergroups.search('admin') == 1) %] +
+
+ + + + + + + + + + + +
+
+[% END %] +[% #inputdisabled = 'readonly' %] +[% #IF (session.usergroups.search('fld') == 1) %] +[% #inputdisabled = '' %] +[% #END %] +
+
+
+
+ × +

Benotzer Accès

+
+
+ [% fieldeditbox("surname","display","Nom",'w3-half','readonly','') %] + [% fieldeditbox("prename","display","Prénom",'w3-half','readonly','') %] +
+ [% fieldhidden("id","users","ident",'') %] + [% fieldmultiselectbox("usergroup_ids","useringroups","Accès",'','','') %] +
+
+
+ + + +
+
+
+
+
+
+ × +

Benotzer E-Mail

+
+
+
+ [% fieldeditbox("surname","display","Nom",'w3-half','readonly','') %] + [% fieldeditbox("prename","display","Prénom",'w3-half','readonly','') %] +
+ [% fieldhidden("id","users","ident",'') %] + [% fieldeditbox("username","users","Login / E-Mail",'','','') %] + [% fieldcheckbox("blocked","users","compte blocké",'','','1') %] +
+
+
+ + + +
+
+
+
+
+
+ × +

Donnée de l'utilsateur

+
+
+
+ [% fieldhidden('id','members','ident') %] + [% fieldeditbox('surname','members','Nom','','') %] + [% fieldeditbox('prename','members','Prénom','','') %] + [% fieldeditbox('job','members','Position','','') %] + [% fieldeditbox('phone','members','Téléphone','','') %] + [% fieldselectbox('id_company','members','Entreprise','','','')%] +
+
+
+ + + [% formdlgsavebutton('members','sauvegarder','closedlgmembers();') %] +
+
+
+[% #IF (session.usergroups.search('admin') != 1) %] +[% #INCLUDE "module/$module/javascript.tt" %] +[% #END %] + diff --git a/backoffice/tmpl/module/users/javascript.tt b/backoffice/tmpl/module/users/javascript.tt new file mode 100644 index 00000000..ea759be4 --- /dev/null +++ b/backoffice/tmpl/module/users/javascript.tt @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/backoffice/tmpl/skeleton/index.tt b/backoffice/tmpl/skeleton/index.tt new file mode 100644 index 00000000..68f33a62 --- /dev/null +++ b/backoffice/tmpl/skeleton/index.tt @@ -0,0 +1,47 @@ +[% USE dksdb = DBI(dsn, dbuser, dbpassword) %] +[% appaccess = dksdb.prepare("select ap.icon,ap.app,ap.name,ug.usergroup from useringroups uig join apps ap on (uig.id_group=ap.id_usergroup) join usergroups ug on (uig.id_group=ug.id) where uig.id_user=? group by ap.id,ug.id order by ap.sort;")%] + + + [% INCLUDE block/head.tt %] + + + + +
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+
+ + + + + + diff --git a/backoffice/tmpl/skeleton/login.tt b/backoffice/tmpl/skeleton/login.tt new file mode 100644 index 00000000..16747bb7 --- /dev/null +++ b/backoffice/tmpl/skeleton/login.tt @@ -0,0 +1,33 @@ + + + +[% INCLUDE block/head.tt %] + + + +
+
+
+
+ logo +
+ [% IF pagename == 'register' && registration_enabled == '1' %] + [% INCLUDE skeleton/login/register.tt %] + [% ELSIF pagename == 'forgotpassword' %] + [% INCLUDE skeleton/login/forgotpassword.tt %] + [% ELSIF pagename == 'message' %] + [% INCLUDE skeleton/login/message.tt %] + [% ELSIF pagename == 'validationcode' %] + [% INCLUDE skeleton/login/validationcode.tt %] + [% ELSE %] + [% INCLUDE skeleton/login/login.tt %] + [% END %] +
+
+ logo +
+
+
+ + + diff --git a/backoffice/tmpl/skeleton/login/forgotpassword.tt b/backoffice/tmpl/skeleton/login/forgotpassword.tt new file mode 100644 index 00000000..bf4feb58 --- /dev/null +++ b/backoffice/tmpl/skeleton/login/forgotpassword.tt @@ -0,0 +1,20 @@ +
+

Mot de passe oublié?

+
Demander un nouveau mot de passe
+
+
+
+ + + + +
+ +
+ +
+ + +
\ No newline at end of file diff --git a/backoffice/tmpl/skeleton/login/login.tt b/backoffice/tmpl/skeleton/login/login.tt new file mode 100644 index 00000000..7262df22 --- /dev/null +++ b/backoffice/tmpl/skeleton/login/login.tt @@ -0,0 +1,25 @@ +
+

Accès Client

+
Bienvenue de retour
+
+
+
+ + +
+
+ + +
+
+ +
+ + [% IF registration_enabled == '1' %] +
+ Vous n'avez pas encore de compte?
se régistrer +
+ [% END %] +
\ No newline at end of file diff --git a/backoffice/tmpl/skeleton/login/message.tt b/backoffice/tmpl/skeleton/login/message.tt new file mode 100644 index 00000000..ba1006fc --- /dev/null +++ b/backoffice/tmpl/skeleton/login/message.tt @@ -0,0 +1,12 @@ +
[% message %]
+ + [% IF registration_enabled == '1' %] + + [% END %] + \ No newline at end of file diff --git a/backoffice/tmpl/skeleton/login/register.tt b/backoffice/tmpl/skeleton/login/register.tt new file mode 100644 index 00000000..7c42fb09 --- /dev/null +++ b/backoffice/tmpl/skeleton/login/register.tt @@ -0,0 +1,49 @@ +[% PROCESS macro/fields.tt %] +
+

Vous n'avez pas un compte?

+
régistrez-vous maintenant!
+
+ +
+
+ [% fieldeditbox('company','companies','Entreprise','w3-half','','','') %] +
+
+ [% fieldeditbox('surname','members','Nom','w3-third','','','') %] + [% fieldeditbox('prename','members','Prénom','w3-third','','','') %] + [% fieldeditbox('job','members','Position','w3-quarter','','','gérant') %] +
+
+ [% fieldeditbox('email','users','E-Mail','w3-half','','','') %] + [% fieldeditbox('phone','members','Téléphone','w3-half','','','') %] +
+ +
+ + +
+
+ +
+
+ vous avez déjà un compte? Se Connecter +
+
+ +
+ + +
+ × +

Conditions générales d'utilisation du site solana-architecture.lu

+
+
+ [% INCLUDE block/cgu.tt %] +
+
+ + +
+
+
\ No newline at end of file diff --git a/backoffice/tmpl/skeleton/login/validationcode.tt b/backoffice/tmpl/skeleton/login/validationcode.tt new file mode 100644 index 00000000..87b36bea --- /dev/null +++ b/backoffice/tmpl/skeleton/login/validationcode.tt @@ -0,0 +1,23 @@ + +
+

Validation de votre addressee E-Mail

+
 
+
+
[% message %]
+
+
+ + + + +
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/backoffice/tmpl/skeleton/module.tt b/backoffice/tmpl/skeleton/module.tt new file mode 100644 index 00000000..1b693998 --- /dev/null +++ b/backoffice/tmpl/skeleton/module.tt @@ -0,0 +1,57 @@ +[% USE dksdb = DBI(dsn, dbuser, dbpassword) %] + + + + + + + + + + [% adminname %] - [% module %] - [% pagename %] + + + + + + + + + + + + + + + +
+ [% IF session.id %] + [% INCLUDE $page %] +
+ + + + + + + + + + + + [% ELSE %] +
+

Du hues keen Accès op dëss Sait!

+
+ + [% END %] + [% INCLUDE block/snackbar.tt %] + + + + + + diff --git a/backoffice/tmpl/skeleton/module_file.tt b/backoffice/tmpl/skeleton/module_file.tt new file mode 100644 index 00000000..d7bc96a3 --- /dev/null +++ b/backoffice/tmpl/skeleton/module_file.tt @@ -0,0 +1,3 @@ +[% USE DBI %] +[% USE dksdb = DBI(dsn, dbuser, dbpassword) %] +[% INCLUDE $page %] diff --git a/backoffice/vendors/choices/base.css b/backoffice/vendors/choices/base.css new file mode 100644 index 00000000..2d090bd6 --- /dev/null +++ b/backoffice/vendors/choices/base.css @@ -0,0 +1,191 @@ +/*============================================= += Generic styling = +=============================================*/ +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +html, +body { + position: relative; + margin: 0; + width: 100%; + height: 100%; +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + font-size: 16px; + line-height: 1.4; + color: #FFFFFF; + background-color: #333; + overflow-x: hidden; +} + +label { + display: block; + margin-bottom: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; +} + +p { + margin-top: 0; +} + +hr { + display: block; + margin: 30px 0; + border: 0; + border-bottom: 1px solid #eaeaea; + height: 1px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 12px; + font-weight: 400; + line-height: 1.2; +} + +a, +a:visited, +a:focus { + color: #FFFFFF; + text-decoration: none; + font-weight: 600; +} + +.form-control { + display: block; + width: 100%; + background-color: #f9f9f9; + padding: 12px; + border: 1px solid #ddd; + border-radius: 2.5px; + font-size: 14px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + margin-bottom: 24px; +} + +h1, +.h1 { + font-size: 32px; +} + +h2, +.h2 { + font-size: 24px; +} + +h3, +.h3 { + font-size: 20px; +} + +h4, +.h4 { + font-size: 18px; +} + +h5, +.h5 { + font-size: 16px; +} + +h6, +.h6 { + font-size: 14px; +} + +p { + margin-bottom: 8px; +} + +label + p { + margin-top: -4px; +} + +.container { + display: block; + margin: auto; + max-width: 40em; + padding: 48px; +} + +@media (max-width: 620px) { + .container { + padding: 0; + } +} + +.section { + background-color: #FFFFFF; + padding: 24px; + color: #333; +} + +.section a, +.section a:visited, +.section a:focus { + color: #00bcd4; +} + +.logo { + display: block; + margin-bottom: 12px; +} + +.logo__img { + width: 100%; + height: auto; + display: inline-block; + max-width: 100%; + vertical-align: top; + padding: 6px 0; +} + +.visible-ie { + display: none; +} + +.push-bottom { + margin-bottom: 24px; +} + +.zero-bottom { + margin-bottom: 0; +} + +.zero-top { + margin-top: 0; +} + +.text-center { + text-align: center; +} + +.is-hidden { + display: none; +} + +[data-test-hook] { + margin-bottom: 24px; +} + +/*===== End of Section comment block ======*/ diff --git a/backoffice/vendors/choices/base.min.css b/backoffice/vendors/choices/base.min.css new file mode 100644 index 00000000..7992056a --- /dev/null +++ b/backoffice/vendors/choices/base.min.css @@ -0,0 +1 @@ +*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:"Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label,p{margin-bottom:8px}label{font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}label+p{margin-top:-4px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none}.push-bottom{margin-bottom:24px}.zero-bottom{margin-bottom:0}.zero-top{margin-top:0}.text-center{text-align:center}.is-hidden{display:none}[data-test-hook]{margin-bottom:24px} \ No newline at end of file diff --git a/backoffice/vendors/choices/choices.css b/backoffice/vendors/choices/choices.css new file mode 100644 index 00000000..e3bc0377 --- /dev/null +++ b/backoffice/vendors/choices/choices.css @@ -0,0 +1,368 @@ +/*=============================== += Choices = +===============================*/ +.choices { + position: relative; + margin-bottom: 24px; + font-size: 16px; +} + +.choices:focus { + outline: none; +} + +.choices:last-child { + margin-bottom: 0; +} + +.choices.is-disabled .choices__inner, +.choices.is-disabled .choices__input { + background-color: #EAEAEA; + cursor: not-allowed; + user-select: none; +} + +.choices.is-disabled .choices__item { + cursor: not-allowed; +} + +.choices[data-type*="select-one"] { + cursor: pointer; +} + +.choices[data-type*="select-one"] .choices__inner { + padding-bottom: 7.5px; + +} + +.choices[data-type*="select-one"] .choices__input { + display: block; + width: 100%; + padding: 10px; + border-bottom: 1px solid #DDDDDD; + background-color: #FFFFFF; + margin: 0; +} + +.choices[data-type*="select-one"] .choices__button { + background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==); + padding: 0; + background-size: 8px; + position: absolute; + top: 50%; + right: 0; + margin-top: -10px; + margin-right: 25px; + height: 20px; + width: 20px; + /* border-radius: 10em; */ + opacity: .5; +} + +.choices[data-type*="select-one"] .choices__button:hover, .choices[data-type*="select-one"] .choices__button:focus { + opacity: 1; +} + +.choices[data-type*="select-one"] .choices__button:focus { + box-shadow: 0px 0px 0px 2px #00BCD4; +} + +.choices[data-type*="select-one"]:after { + content: ""; + height: 0; + width: 0; + border-style: solid; + border-color: #333333 transparent transparent transparent; + border-width: 5px; + position: absolute; + right: 11.5px; + top: 50%; + margin-top: -2.5px; + pointer-events: none; +} + +.choices[data-type*="select-one"].is-open:after { + border-color: transparent transparent #333333 transparent; + margin-top: -7.5px; +} + +.choices[data-type*="select-one"][dir="rtl"]:after { + left: 11.5px; + right: auto; +} + +.choices[data-type*="select-one"][dir="rtl"] .choices__button { + right: auto; + left: 0; + margin-left: 25px; + margin-right: 0; +} + +.choices[data-type*="select-multiple"] .choices__inner, +.choices[data-type*="text"] .choices__inner { + cursor: text; +} + +.choices[data-type*="select-multiple"] .choices__button, +.choices[data-type*="text"] .choices__button { + position: relative; + display: inline-block; + margin-top: 0; + margin-right: -4px; + margin-bottom: 0; + margin-left: 8px; + padding-left: 16px; + border-left: 1px solid #008fa1; + background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==); + background-size: 8px; + width: 8px; + line-height: 1; + opacity: .75; + border-radius: 0; +} + +.choices[data-type*="select-multiple"] .choices__button:hover, .choices[data-type*="select-multiple"] .choices__button:focus, +.choices[data-type*="text"] .choices__button:hover, +.choices[data-type*="text"] .choices__button:focus { + opacity: 1; +} + +.choices__inner { + display: inline-block; + vertical-align: top; + width: 100%; + background-color: #e8f0fe; + padding: 7.5px 7.5px 3.75px; + border: 1px solid #ccc; + /* border-radius: 2.5px; */ + /* font-size: 14px; */ + min-height: 44px; + overflow: hidden; + /* font-weight: bold; */ +} + +.is-focused .choices__inner, +.is-open .choices__inner { + border-color: #b7b7b7; +} + +.is-open .choices__inner { + /* border-radius: 2.5px 2.5px 0 0; */ +} + +.is-flipped.is-open .choices__inner { + /* border-radius: 0 0 2.5px 2.5px; */ +} + +.choices__list { + margin: 0; + padding-left: 0; + list-style: none; +} + +.choices__list--single { + display: inline-block; + padding: 4px 16px 4px 4px; + width: 100%; +} + +[dir="rtl"] .choices__list--single { + padding-right: 4px; + padding-left: 16px; +} + +.choices__list--single .choices__item { + width: 100%; +} + +.choices__list--multiple { + display: inline; +} + +.choices__list--multiple .choices__item { + display: inline-block; + vertical-align: middle; + /* border-radius: 20px; */ + padding: 4px 6px; + font-size: 16px; + height: 40px; + /* font-size: 12px; */ + /* font-weight: 500; */ + margin-right: 3.75px; + /* margin-bottom: 3.75px; */ + background-color: #607d8b; + border: 1px solid #607d8b; + color: #FFFFFF; + /* font-weightfont-weight: bold; */ + word-break: break-all; +} + +.choices__list--multiple .choices__item[data-deletable] { + padding-right: 5px; +} + +[dir="rtl"] .choices__list--multiple .choices__item { + margin-right: 0; + margin-left: 3.75px; +} + +.choices__list--multiple .choices__item.is-highlighted { + background-color: #00a5bb; + border: 1px solid #008fa1; +} + +.is-disabled .choices__list--multiple .choices__item { + background-color: #aaaaaa; + border: 1px solid #919191; +} + +.choices__list--dropdown { + display: none; + z-index: 1; + position: absolute; + width: 100%; + background-color: #FFFFFF; + border: 1px solid #DDDDDD; + top: 100%; + margin-top: -1px; + /* border-bottom-left-radius: 2.5px; */ + /* border-bottom-right-radius: 2.5px; */ + overflow: hidden; + word-break: break-all; +} + +.choices__list--dropdown.is-active { + display: block; +} + +.is-open .choices__list--dropdown { + border-color: #b7b7b7; +} + +.is-flipped .choices__list--dropdown { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: -1px; + /* border-radius: .25rem .25rem 0 0; */ +} + +.choices__list--dropdown .choices__list { + position: relative; + max-height: 300px; + overflow: auto; + -webkit-overflow-scrolling: touch; + will-change: scroll-position; +} + +.choices__list--dropdown .choices__item { + position: relative; + padding: 10px; + font-size: 14px; +} + +[dir="rtl"] .choices__list--dropdown .choices__item { + text-align: right; +} + +@media (min-width: 640px) { + .choices__list--dropdown .choices__item--selectable { + padding-right: 100px; + } + .choices__list--dropdown .choices__item--selectable:after { + content: attr(data-select-text); + font-size: 12px; + opacity: 0; + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + } + [dir="rtl"] .choices__list--dropdown .choices__item--selectable { + text-align: right; + padding-left: 100px; + padding-right: 10px; + } + [dir="rtl"] .choices__list--dropdown .choices__item--selectable:after { + right: auto; + left: 10px; + } +} + +.choices__list--dropdown .choices__item--selectable.is-highlighted { + background-color: #f2f2f2; +} + +.choices__list--dropdown .choices__item--selectable.is-highlighted:after { + opacity: .5; +} + +.choices__item { + cursor: default; +} + +.choices__item--selectable { + cursor: pointer; +} + +.choices__item--disabled { + cursor: not-allowed; + user-select: none; + opacity: .5; +} + +.choices__heading { + /* font-weight: 600; */ + font-size: 12px; + padding: 10px; + border-bottom: 1px solid #f7f7f7; + color: gray; +} + +.choices__button { + text-indent: -9999px; + -webkit-appearance: none; + appearance: none; + border: 0; + background-color: transparent; + background-repeat: no-repeat; + background-position: center; + cursor: pointer; +} + +.choices__button:focus { + outline: none; +} + +.choices__input { + display: inline-block; + vertical-align: baseline; + background-color: #f9f9f9; + font-size: 14px; + margin-bottom: 5px; + border: 0; + border-radius: 0; + max-width: 100%; + padding: 4px 0 4px 2px; +} + +.choices__input:focus { + outline: 0; +} + +[dir="rtl"] .choices__input { + padding-right: 2px; + padding-left: 0; +} + +.choices__placeholder { + opacity: .5; +} + +.choices__input.is-hidden, +.choices[data-type*="select-one"] .choices__input.is-hidden, +.choices[data-type*="select-multiple"] .choices__input.is-hidden { + display: none; +} + +/*===== End of Choices ======*/ diff --git a/backoffice/vendors/choices/choices.js b/backoffice/vendors/choices/choices.js new file mode 100644 index 00000000..93e8b4e7 --- /dev/null +++ b/backoffice/vendors/choices/choices.js @@ -0,0 +1,6787 @@ +(function webpackUniversalModuleDefinition(root, factory) { + //CommonJS2 + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + //AMD + else if(typeof define === 'function' && define.amd) + define([], factory); + //CommonJS + else if(typeof exports === 'object') + exports["Choices"] = factory(); + //Window + else + root["Choices"] = factory(); +})(window, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "/public/assets/scripts/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 9); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.diff = exports.cloneObject = exports.existsInArray = exports.isIE11 = exports.fetchFromObject = exports.getWindowHeight = exports.dispatchEvent = exports.sortByScore = exports.sortByAlpha = exports.calcWidthOfInput = exports.strToEl = exports.sanitise = exports.isScrolledIntoView = exports.getAdjacentEl = exports.findAncestorByAttrName = exports.wrap = exports.isElement = exports.isType = exports.getType = exports.generateId = exports.generateChars = exports.getRandomNumber = void 0; + +var _this = void 0; + +var getRandomNumber = function getRandomNumber(min, max) { + return Math.floor(Math.random() * (max - min) + min); +}; + +exports.getRandomNumber = getRandomNumber; + +var generateChars = function generateChars(length) { + var chars = ''; + + for (var i = 0; i < length; i++) { + var randomChar = getRandomNumber(0, 36); + chars += randomChar.toString(36); + } + + return chars; +}; + +exports.generateChars = generateChars; + +var generateId = function generateId(element, prefix) { + var id = element.id || element.name && "".concat(element.name, "-").concat(generateChars(2)) || generateChars(4); + id = id.replace(/(:|\.|\[|\]|,)/g, ''); + id = "".concat(prefix, "-").concat(id); + return id; +}; + +exports.generateId = generateId; + +var getType = function getType(obj) { + return Object.prototype.toString.call(obj).slice(8, -1); +}; + +exports.getType = getType; + +var isType = function isType(type, obj) { + return obj !== undefined && obj !== null && getType(obj) === type; +}; + +exports.isType = isType; + +var isElement = function isElement(element) { + return element instanceof Element; +}; + +exports.isElement = isElement; + +var wrap = function wrap(element) { + var wrapper = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document.createElement('div'); + + if (element.nextSibling) { + element.parentNode.insertBefore(wrapper, element.nextSibling); + } else { + element.parentNode.appendChild(wrapper); + } + + return wrapper.appendChild(element); +}; + +exports.wrap = wrap; + +var findAncestorByAttrName = function findAncestorByAttrName(el, attr) { + var target = el; + + while (target) { + if (target.hasAttribute(attr)) { + return target; + } + + target = target.parentElement; + } + + return null; +}; + +exports.findAncestorByAttrName = findAncestorByAttrName; + +var getAdjacentEl = function getAdjacentEl(startEl, className) { + var direction = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; + + if (!startEl || !className) { + return; + } + + var parent = startEl.parentNode.parentNode; + var children = Array.from(parent.querySelectorAll(className)); + var startPos = children.indexOf(startEl); + var operatorDirection = direction > 0 ? 1 : -1; + return children[startPos + operatorDirection]; +}; + +exports.getAdjacentEl = getAdjacentEl; + +var isScrolledIntoView = function isScrolledIntoView(el, parent) { + var direction = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; + + if (!el) { + return; + } + + var isVisible; + + if (direction > 0) { + // In view from bottom + isVisible = parent.scrollTop + parent.offsetHeight >= el.offsetTop + el.offsetHeight; + } else { + // In view from top + isVisible = el.offsetTop >= parent.scrollTop; + } + + return isVisible; +}; + +exports.isScrolledIntoView = isScrolledIntoView; + +var sanitise = function sanitise(value) { + if (!isType('String', value)) { + return value; + } + + return value.replace(/&/g, '&').replace(/>/g, '&rt;').replace(/".concat(sanitise(value), "")); + testEl.style.position = 'absolute'; + testEl.style.padding = '0'; + testEl.style.top = '-9999px'; + testEl.style.left = '-9999px'; + testEl.style.width = 'auto'; + testEl.style.whiteSpace = 'pre'; + + if (document.body.contains(input) && window.getComputedStyle) { + var inputStyle = window.getComputedStyle(input); + + if (inputStyle) { + testEl.style.fontSize = inputStyle.fontSize; + testEl.style.fontFamily = inputStyle.fontFamily; + testEl.style.fontWeight = inputStyle.fontWeight; + testEl.style.fontStyle = inputStyle.fontStyle; + testEl.style.letterSpacing = inputStyle.letterSpacing; + testEl.style.textTransform = inputStyle.textTransform; + testEl.style.padding = inputStyle.padding; + } + } + + document.body.appendChild(testEl); + requestAnimationFrame(function () { + if (value && testEl.offsetWidth !== input.offsetWidth) { + width = testEl.offsetWidth + 4; + } + + document.body.removeChild(testEl); + callback.call(_this, "".concat(width, "px")); + }); + } else { + callback.call(_this, "".concat(width, "px")); + } +}; + +exports.calcWidthOfInput = calcWidthOfInput; + +var sortByAlpha = function sortByAlpha(a, b) { + var labelA = "".concat(a.label || a.value).toLowerCase(); + var labelB = "".concat(b.label || b.value).toLowerCase(); + + if (labelA < labelB) { + return -1; + } + + if (labelA > labelB) { + return 1; + } + + return 0; +}; + +exports.sortByAlpha = sortByAlpha; + +var sortByScore = function sortByScore(a, b) { + return a.score - b.score; +}; + +exports.sortByScore = sortByScore; + +var dispatchEvent = function dispatchEvent(element, type) { + var customArgs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + var event = new CustomEvent(type, { + detail: customArgs, + bubbles: true, + cancelable: true + }); + return element.dispatchEvent(event); +}; + +exports.dispatchEvent = dispatchEvent; + +var getWindowHeight = function getWindowHeight() { + var body = document.body; + var html = document.documentElement; + return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); +}; + +exports.getWindowHeight = getWindowHeight; + +var fetchFromObject = function fetchFromObject(object, path) { + var index = path.indexOf('.'); + + if (index > -1) { + return fetchFromObject(object[path.substring(0, index)], path.substr(index + 1)); + } + + return object[path]; +}; + +exports.fetchFromObject = fetchFromObject; + +var isIE11 = function isIE11() { + return !!(navigator.userAgent.match(/Trident/) && navigator.userAgent.match(/rv[ :]11/)); +}; + +exports.isIE11 = isIE11; + +var existsInArray = function existsInArray(array, value) { + var key = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'value'; + return array.some(function (item) { + if (isType('String', value)) { + return item[key] === value.trim(); + } + + return item[key] === value; + }); +}; + +exports.existsInArray = existsInArray; + +var cloneObject = function cloneObject(obj) { + return JSON.parse(JSON.stringify(obj)); +}; + +exports.cloneObject = cloneObject; + +var diff = function diff(a, b) { + var aKeys = Object.keys(a).sort(); + var bKeys = Object.keys(b).sort(); + return aKeys.filter(function (i) { + return bKeys.indexOf(i) < 0; + }); +}; + +exports.diff = diff; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SCROLLING_SPEED = exports.KEY_CODES = exports.ACTION_TYPES = exports.EVENTS = exports.DEFAULT_CONFIG = exports.DEFAULT_CLASSNAMES = void 0; + +var _utils = __webpack_require__(0); + +var DEFAULT_CLASSNAMES = { + containerOuter: 'choices', + containerInner: 'choices__inner', + input: 'choices__input', + inputCloned: 'choices__input--cloned', + list: 'choices__list', + listItems: 'choices__list--multiple', + listSingle: 'choices__list--single', + listDropdown: 'choices__list--dropdown', + item: 'choices__item', + itemSelectable: 'choices__item--selectable', + itemDisabled: 'choices__item--disabled', + itemChoice: 'choices__item--choice', + placeholder: 'choices__placeholder', + group: 'choices__group', + groupHeading: 'choices__heading', + button: 'choices__button', + activeState: 'is-active', + focusState: 'is-focused', + openState: 'is-open', + disabledState: 'is-disabled', + highlightedState: 'is-highlighted', + hiddenState: 'is-hidden', + flippedState: 'is-flipped', + loadingState: 'is-loading', + noResults: 'has-no-results', + noChoices: 'has-no-choices' +}; +exports.DEFAULT_CLASSNAMES = DEFAULT_CLASSNAMES; +var DEFAULT_CONFIG = { + items: [], + choices: [], + silent: false, + renderChoiceLimit: -1, + maxItemCount: -1, + addItems: true, + addItemFilterFn: null, + removeItems: true, + removeItemButton: false, + editItems: false, + duplicateItemsAllowed: true, + delimiter: ',', + paste: true, + searchEnabled: true, + searchChoices: true, + searchFloor: 1, + searchResultLimit: 4, + searchFields: ['label', 'value'], + position: 'auto', + resetScrollPosition: true, + shouldSort: true, + shouldSortItems: false, + sortFn: _utils.sortByAlpha, + placeholder: true, + placeholderValue: null, + searchPlaceholderValue: null, + prependValue: null, + appendValue: null, + renderSelectedChoices: 'auto', + loadingText: 'Loading...', + noResultsText: 'No results found', + noChoicesText: 'No choices to choose from', + itemSelectText: 'Press to select', + uniqueItemText: 'Only unique values can be added', + customAddItemText: 'Only values matching specific conditions can be added', + addItemText: function addItemText(value) { + return "Press Enter to add \"".concat((0, _utils.sanitise)(value), "\""); + }, + maxItemText: function maxItemText(maxItemCount) { + return "Only ".concat(maxItemCount, " values can be added"); + }, + itemComparer: function itemComparer(choice, item) { + return choice === item; + }, + fuseOptions: { + includeScore: true + }, + callbackOnInit: null, + callbackOnCreateTemplates: null, + classNames: DEFAULT_CLASSNAMES +}; +exports.DEFAULT_CONFIG = DEFAULT_CONFIG; +var EVENTS = { + showDropdown: 'showDropdown', + hideDropdown: 'hideDropdown', + change: 'change', + choice: 'choice', + search: 'search', + addItem: 'addItem', + removeItem: 'removeItem', + highlightItem: 'highlightItem', + highlightChoice: 'highlightChoice' +}; +exports.EVENTS = EVENTS; +var ACTION_TYPES = { + ADD_CHOICE: 'ADD_CHOICE', + FILTER_CHOICES: 'FILTER_CHOICES', + ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', + CLEAR_CHOICES: 'CLEAR_CHOICES', + ADD_GROUP: 'ADD_GROUP', + ADD_ITEM: 'ADD_ITEM', + REMOVE_ITEM: 'REMOVE_ITEM', + HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', + CLEAR_ALL: 'CLEAR_ALL' +}; +exports.ACTION_TYPES = ACTION_TYPES; +var KEY_CODES = { + BACK_KEY: 46, + DELETE_KEY: 8, + ENTER_KEY: 13, + A_KEY: 65, + ESC_KEY: 27, + UP_KEY: 38, + DOWN_KEY: 40, + PAGE_UP_KEY: 33, + PAGE_DOWN_KEY: 34 +}; +exports.KEY_CODES = KEY_CODES; +var SCROLLING_SPEED = 4; +exports.SCROLLING_SPEED = SCROLLING_SPEED; + +/***/ }), +/* 2 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global, module) {/* harmony import */ var _ponyfill_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7); +/* global window */ + + +var root; + +if (typeof self !== 'undefined') { + root = self; +} else if (typeof window !== 'undefined') { + root = window; +} else if (typeof global !== 'undefined') { + root = global; +} else if (true) { + root = module; +} else {} + +var result = Object(_ponyfill_js__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"])(root); +/* harmony default export */ __webpack_exports__["a"] = (result); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(3), __webpack_require__(14)(module))) + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || new Function("return this")(); +} catch (e) { + // This works if the window reference is available + if (typeof window === "object") g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; + + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _utils = __webpack_require__(0); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var WrappedElement = +/*#__PURE__*/ +function () { + function WrappedElement(_ref) { + var element = _ref.element, + classNames = _ref.classNames; + + _classCallCheck(this, WrappedElement); + + Object.assign(this, { + element: element, + classNames: classNames + }); + + if (!(0, _utils.isElement)(element)) { + throw new TypeError('Invalid element passed'); + } + + this.isDisabled = false; + } + + _createClass(WrappedElement, [{ + key: "conceal", + value: function conceal() { + // Hide passed input + this.element.classList.add(this.classNames.input); + this.element.classList.add(this.classNames.hiddenState); // Remove element from tab index + + this.element.tabIndex = '-1'; // Backup original styles if any + + var origStyle = this.element.getAttribute('style'); + + if (origStyle) { + this.element.setAttribute('data-choice-orig-style', origStyle); + } + + this.element.setAttribute('aria-hidden', 'true'); + this.element.setAttribute('data-choice', 'active'); + } + }, { + key: "reveal", + value: function reveal() { + // Reinstate passed element + this.element.classList.remove(this.classNames.input); + this.element.classList.remove(this.classNames.hiddenState); + this.element.removeAttribute('tabindex'); // Recover original styles if any + + var origStyle = this.element.getAttribute('data-choice-orig-style'); + + if (origStyle) { + this.element.removeAttribute('data-choice-orig-style'); + this.element.setAttribute('style', origStyle); + } else { + this.element.removeAttribute('style'); + } + + this.element.removeAttribute('aria-hidden'); + this.element.removeAttribute('data-choice'); // Re-assign values - this is weird, I know + + this.element.value = this.element.value; + } + }, { + key: "enable", + value: function enable() { + this.element.removeAttribute('disabled'); + this.element.disabled = false; + this.isDisabled = false; + } + }, { + key: "disable", + value: function disable() { + this.element.setAttribute('disabled', ''); + this.element.disabled = true; + this.isDisabled = true; + } + }, { + key: "triggerEvent", + value: function triggerEvent(eventType, data) { + (0, _utils.dispatchEvent)(this.element, eventType, data); + } + }, { + key: "value", + get: function get() { + return this.element.value; + } + }]); + + return WrappedElement; +}(); + +exports.default = WrappedElement; + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = exports.TEMPLATES = void 0; + +var _classnames = _interopRequireDefault(__webpack_require__(27)); + +var _utils = __webpack_require__(0); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var TEMPLATES = { + containerOuter: function containerOuter(globalClasses, direction, isSelectElement, isSelectOneElement, searchEnabled, passedElementType) { + var tabIndex = isSelectOneElement ? 'tabindex="0"' : ''; + var role = isSelectElement ? 'role="listbox"' : ''; + var ariaAutoComplete = ''; + + if (isSelectElement && searchEnabled) { + role = 'role="combobox"'; + ariaAutoComplete = 'aria-autocomplete="list"'; + } + + return (0, _utils.strToEl)("\n \n \n ")); + }, + containerInner: function containerInner(globalClasses) { + return (0, _utils.strToEl)("\n
\n ")); + }, + itemList: function itemList(globalClasses, isSelectOneElement) { + var _classNames; + + var localClasses = (0, _classnames.default)(globalClasses.list, (_classNames = {}, _defineProperty(_classNames, globalClasses.listSingle, isSelectOneElement), _defineProperty(_classNames, globalClasses.listItems, !isSelectOneElement), _classNames)); + return (0, _utils.strToEl)("\n
\n ")); + }, + placeholder: function placeholder(globalClasses, value) { + return (0, _utils.strToEl)("\n
\n ").concat(value, "\n
\n ")); + }, + item: function item(globalClasses, data, removeItemButton) { + var _classNames2; + + var ariaSelected = data.active ? 'aria-selected="true"' : ''; + var ariaDisabled = data.disabled ? 'aria-disabled="true"' : ''; + var localClasses = (0, _classnames.default)(globalClasses.item, (_classNames2 = {}, _defineProperty(_classNames2, globalClasses.highlightedState, data.highlighted), _defineProperty(_classNames2, globalClasses.itemSelectable, !data.highlighted), _defineProperty(_classNames2, globalClasses.placeholder, data.placeholder), _classNames2)); + + if (removeItemButton) { + var _classNames3; + + localClasses = (0, _classnames.default)(globalClasses.item, (_classNames3 = {}, _defineProperty(_classNames3, globalClasses.highlightedState, data.highlighted), _defineProperty(_classNames3, globalClasses.itemSelectable, !data.disabled), _defineProperty(_classNames3, globalClasses.placeholder, data.placeholder), _classNames3)); + return (0, _utils.strToEl)("\n \n ").concat(data.label, "\n Remove item\n \n \n ")); + } + + return (0, _utils.strToEl)("\n \n ").concat(data.label, "\n \n ")); + }, + choiceList: function choiceList(globalClasses, isSelectOneElement) { + var ariaMultiSelectable = !isSelectOneElement ? 'aria-multiselectable="true"' : ''; + return (0, _utils.strToEl)("\n \n \n ")); + }, + choiceGroup: function choiceGroup(globalClasses, data) { + var ariaDisabled = data.disabled ? 'aria-disabled="true"' : ''; + var localClasses = (0, _classnames.default)(globalClasses.group, _defineProperty({}, globalClasses.itemDisabled, data.disabled)); + return (0, _utils.strToEl)("\n \n
").concat(data.value, "
\n \n ")); + }, + choice: function choice(globalClasses, data, itemSelectText) { + var _classNames5; + + var role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"'; + var localClasses = (0, _classnames.default)(globalClasses.item, globalClasses.itemChoice, (_classNames5 = {}, _defineProperty(_classNames5, globalClasses.itemDisabled, data.disabled), _defineProperty(_classNames5, globalClasses.itemSelectable, !data.disabled), _defineProperty(_classNames5, globalClasses.placeholder, data.placeholder), _classNames5)); + return (0, _utils.strToEl)("\n \n ").concat(data.label, "\n \n ")); + }, + input: function input(globalClasses) { + var localClasses = (0, _classnames.default)(globalClasses.input, globalClasses.inputCloned); + return (0, _utils.strToEl)("\n \n ")); + }, + dropdown: function dropdown(globalClasses) { + var localClasses = (0, _classnames.default)(globalClasses.list, globalClasses.listDropdown); + return (0, _utils.strToEl)("\n \n \n ")); + }, + notice: function notice(globalClasses, label) { + var _classNames6; + + var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; + var localClasses = (0, _classnames.default)(globalClasses.item, globalClasses.itemChoice, (_classNames6 = {}, _defineProperty(_classNames6, globalClasses.noResults, type === 'no-results'), _defineProperty(_classNames6, globalClasses.noChoices, type === 'no-choices'), _classNames6)); + return (0, _utils.strToEl)("\n
\n ").concat(label, "\n
\n ")); + }, + option: function option(data) { + return (0, _utils.strToEl)("\n \n ")); + } +}; +exports.TEMPLATES = TEMPLATES; +var _default = TEMPLATES; +exports.default = _default; + +/***/ }), +/* 6 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); + +// EXTERNAL MODULE: ./node_modules/lodash-es/_freeGlobal.js +var _freeGlobal = __webpack_require__(8); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_root.js + + +/** Detect free variable `self`. */ +var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + +/** Used as a reference to the global object. */ +var root = _freeGlobal["a" /* default */] || freeSelf || Function('return this')(); + +/* harmony default export */ var _root = (root); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_Symbol.js + + +/** Built-in value references. */ +var Symbol = _root.Symbol; + +/* harmony default export */ var _Symbol = (Symbol); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_getRawTag.js + + +/** Used for built-in method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var _getRawTag_hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var nativeObjectToString = objectProto.toString; + +/** Built-in value references. */ +var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined; + +/** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ +function getRawTag(value) { + var isOwn = _getRawTag_hasOwnProperty.call(value, symToStringTag), + tag = value[symToStringTag]; + + try { + value[symToStringTag] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag] = tag; + } else { + delete value[symToStringTag]; + } + } + return result; +} + +/* harmony default export */ var _getRawTag = (getRawTag); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_objectToString.js +/** Used for built-in method references. */ +var _objectToString_objectProto = Object.prototype; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var _objectToString_nativeObjectToString = _objectToString_objectProto.toString; + +/** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ +function objectToString(value) { + return _objectToString_nativeObjectToString.call(value); +} + +/* harmony default export */ var _objectToString = (objectToString); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_baseGetTag.js + + + + +/** `Object#toString` result references. */ +var nullTag = '[object Null]', + undefinedTag = '[object Undefined]'; + +/** Built-in value references. */ +var _baseGetTag_symToStringTag = _Symbol ? _Symbol.toStringTag : undefined; + +/** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ +function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag; + } + return (_baseGetTag_symToStringTag && _baseGetTag_symToStringTag in Object(value)) + ? _getRawTag(value) + : _objectToString(value); +} + +/* harmony default export */ var _baseGetTag = (baseGetTag); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_overArg.js +/** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ +function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; +} + +/* harmony default export */ var _overArg = (overArg); + +// CONCATENATED MODULE: ./node_modules/lodash-es/_getPrototype.js + + +/** Built-in value references. */ +var getPrototype = _overArg(Object.getPrototypeOf, Object); + +/* harmony default export */ var _getPrototype = (getPrototype); + +// CONCATENATED MODULE: ./node_modules/lodash-es/isObjectLike.js +/** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ +function isObjectLike(value) { + return value != null && typeof value == 'object'; +} + +/* harmony default export */ var lodash_es_isObjectLike = (isObjectLike); + +// CONCATENATED MODULE: ./node_modules/lodash-es/isPlainObject.js + + + + +/** `Object#toString` result references. */ +var objectTag = '[object Object]'; + +/** Used for built-in method references. */ +var funcProto = Function.prototype, + isPlainObject_objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var funcToString = funcProto.toString; + +/** Used to check objects for own properties. */ +var isPlainObject_hasOwnProperty = isPlainObject_objectProto.hasOwnProperty; + +/** Used to infer the `Object` constructor. */ +var objectCtorString = funcToString.call(Object); + +/** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * @static + * @memberOf _ + * @since 0.8.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ +function isPlainObject(value) { + if (!lodash_es_isObjectLike(value) || _baseGetTag(value) != objectTag) { + return false; + } + var proto = _getPrototype(value); + if (proto === null) { + return true; + } + var Ctor = isPlainObject_hasOwnProperty.call(proto, 'constructor') && proto.constructor; + return typeof Ctor == 'function' && Ctor instanceof Ctor && + funcToString.call(Ctor) == objectCtorString; +} + +/* harmony default export */ var lodash_es_isPlainObject = (isPlainObject); + +// EXTERNAL MODULE: ./node_modules/symbol-observable/es/index.js +var es = __webpack_require__(2); + +// CONCATENATED MODULE: ./node_modules/redux/es/createStore.js + + + +/** + * These are private action types reserved by Redux. + * For any unknown actions, you must return the current state. + * If the current state is undefined, you must return the initial state. + * Do not reference these action types directly in your code. + */ +var ActionTypes = { + INIT: '@@redux/INIT' + + /** + * Creates a Redux store that holds the state tree. + * The only way to change the data in the store is to call `dispatch()` on it. + * + * There should only be a single store in your app. To specify how different + * parts of the state tree respond to actions, you may combine several reducers + * into a single reducer function by using `combineReducers`. + * + * @param {Function} reducer A function that returns the next state tree, given + * the current state tree and the action to handle. + * + * @param {any} [preloadedState] The initial state. You may optionally specify it + * to hydrate the state from the server in universal apps, or to restore a + * previously serialized user session. + * If you use `combineReducers` to produce the root reducer function, this must be + * an object with the same shape as `combineReducers` keys. + * + * @param {Function} [enhancer] The store enhancer. You may optionally specify it + * to enhance the store with third-party capabilities such as middleware, + * time travel, persistence, etc. The only store enhancer that ships with Redux + * is `applyMiddleware()`. + * + * @returns {Store} A Redux store that lets you read the state, dispatch actions + * and subscribe to changes. + */ +};function createStore_createStore(reducer, preloadedState, enhancer) { + var _ref2; + + if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { + enhancer = preloadedState; + preloadedState = undefined; + } + + if (typeof enhancer !== 'undefined') { + if (typeof enhancer !== 'function') { + throw new Error('Expected the enhancer to be a function.'); + } + + return enhancer(createStore_createStore)(reducer, preloadedState); + } + + if (typeof reducer !== 'function') { + throw new Error('Expected the reducer to be a function.'); + } + + var currentReducer = reducer; + var currentState = preloadedState; + var currentListeners = []; + var nextListeners = currentListeners; + var isDispatching = false; + + function ensureCanMutateNextListeners() { + if (nextListeners === currentListeners) { + nextListeners = currentListeners.slice(); + } + } + + /** + * Reads the state tree managed by the store. + * + * @returns {any} The current state tree of your application. + */ + function getState() { + return currentState; + } + + /** + * Adds a change listener. It will be called any time an action is dispatched, + * and some part of the state tree may potentially have changed. You may then + * call `getState()` to read the current state tree inside the callback. + * + * You may call `dispatch()` from a change listener, with the following + * caveats: + * + * 1. The subscriptions are snapshotted just before every `dispatch()` call. + * If you subscribe or unsubscribe while the listeners are being invoked, this + * will not have any effect on the `dispatch()` that is currently in progress. + * However, the next `dispatch()` call, whether nested or not, will use a more + * recent snapshot of the subscription list. + * + * 2. The listener should not expect to see all state changes, as the state + * might have been updated multiple times during a nested `dispatch()` before + * the listener is called. It is, however, guaranteed that all subscribers + * registered before the `dispatch()` started will be called with the latest + * state by the time it exits. + * + * @param {Function} listener A callback to be invoked on every dispatch. + * @returns {Function} A function to remove this change listener. + */ + function subscribe(listener) { + if (typeof listener !== 'function') { + throw new Error('Expected listener to be a function.'); + } + + var isSubscribed = true; + + ensureCanMutateNextListeners(); + nextListeners.push(listener); + + return function unsubscribe() { + if (!isSubscribed) { + return; + } + + isSubscribed = false; + + ensureCanMutateNextListeners(); + var index = nextListeners.indexOf(listener); + nextListeners.splice(index, 1); + }; + } + + /** + * Dispatches an action. It is the only way to trigger a state change. + * + * The `reducer` function, used to create the store, will be called with the + * current state tree and the given `action`. Its return value will + * be considered the **next** state of the tree, and the change listeners + * will be notified. + * + * The base implementation only supports plain object actions. If you want to + * dispatch a Promise, an Observable, a thunk, or something else, you need to + * wrap your store creating function into the corresponding middleware. For + * example, see the documentation for the `redux-thunk` package. Even the + * middleware will eventually dispatch plain object actions using this method. + * + * @param {Object} action A plain object representing “what changed”. It is + * a good idea to keep actions serializable so you can record and replay user + * sessions, or use the time travelling `redux-devtools`. An action must have + * a `type` property which may not be `undefined`. It is a good idea to use + * string constants for action types. + * + * @returns {Object} For convenience, the same action object you dispatched. + * + * Note that, if you use a custom middleware, it may wrap `dispatch()` to + * return something else (for example, a Promise you can await). + */ + function dispatch(action) { + if (!lodash_es_isPlainObject(action)) { + throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.'); + } + + if (typeof action.type === 'undefined') { + throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?'); + } + + if (isDispatching) { + throw new Error('Reducers may not dispatch actions.'); + } + + try { + isDispatching = true; + currentState = currentReducer(currentState, action); + } finally { + isDispatching = false; + } + + var listeners = currentListeners = nextListeners; + for (var i = 0; i < listeners.length; i++) { + var listener = listeners[i]; + listener(); + } + + return action; + } + + /** + * Replaces the reducer currently used by the store to calculate the state. + * + * You might need this if your app implements code splitting and you want to + * load some of the reducers dynamically. You might also need this if you + * implement a hot reloading mechanism for Redux. + * + * @param {Function} nextReducer The reducer for the store to use instead. + * @returns {void} + */ + function replaceReducer(nextReducer) { + if (typeof nextReducer !== 'function') { + throw new Error('Expected the nextReducer to be a function.'); + } + + currentReducer = nextReducer; + dispatch({ type: ActionTypes.INIT }); + } + + /** + * Interoperability point for observable/reactive libraries. + * @returns {observable} A minimal observable of state changes. + * For more information, see the observable proposal: + * https://github.com/tc39/proposal-observable + */ + function observable() { + var _ref; + + var outerSubscribe = subscribe; + return _ref = { + /** + * The minimal observable subscription method. + * @param {Object} observer Any object that can be used as an observer. + * The observer object should have a `next` method. + * @returns {subscription} An object with an `unsubscribe` method that can + * be used to unsubscribe the observable from the store, and prevent further + * emission of values from the observable. + */ + subscribe: function subscribe(observer) { + if (typeof observer !== 'object') { + throw new TypeError('Expected the observer to be an object.'); + } + + function observeState() { + if (observer.next) { + observer.next(getState()); + } + } + + observeState(); + var unsubscribe = outerSubscribe(observeState); + return { unsubscribe: unsubscribe }; + } + }, _ref[es["a" /* default */]] = function () { + return this; + }, _ref; + } + + // When a store is created, an "INIT" action is dispatched so that every + // reducer returns their initial state. This effectively populates + // the initial state tree. + dispatch({ type: ActionTypes.INIT }); + + return _ref2 = { + dispatch: dispatch, + subscribe: subscribe, + getState: getState, + replaceReducer: replaceReducer + }, _ref2[es["a" /* default */]] = observable, _ref2; +} +// CONCATENATED MODULE: ./node_modules/redux/es/utils/warning.js +/** + * Prints a warning in the console if it exists. + * + * @param {String} message The warning message. + * @returns {void} + */ +function warning(message) { + /* eslint-disable no-console */ + if (typeof console !== 'undefined' && typeof console.error === 'function') { + console.error(message); + } + /* eslint-enable no-console */ + try { + // This error was thrown as a convenience so that if you enable + // "break on all exceptions" in your console, + // it would pause the execution at this line. + throw new Error(message); + /* eslint-disable no-empty */ + } catch (e) {} + /* eslint-enable no-empty */ +} +// CONCATENATED MODULE: ./node_modules/redux/es/combineReducers.js + + + + +function getUndefinedStateErrorMessage(key, action) { + var actionType = action && action.type; + var actionName = actionType && '"' + actionType.toString() + '"' || 'an action'; + + return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state. ' + 'If you want this reducer to hold no value, you can return null instead of undefined.'; +} + +function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) { + var reducerKeys = Object.keys(reducers); + var argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer'; + + if (reducerKeys.length === 0) { + return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.'; + } + + if (!lodash_es_isPlainObject(inputState)) { + return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"'); + } + + var unexpectedKeys = Object.keys(inputState).filter(function (key) { + return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]; + }); + + unexpectedKeys.forEach(function (key) { + unexpectedKeyCache[key] = true; + }); + + if (unexpectedKeys.length > 0) { + return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.'); + } +} + +function assertReducerShape(reducers) { + Object.keys(reducers).forEach(function (key) { + var reducer = reducers[key]; + var initialState = reducer(undefined, { type: ActionTypes.INIT }); + + if (typeof initialState === 'undefined') { + throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined. If you don\'t want to set a value for this reducer, ' + 'you can use null instead of undefined.'); + } + + var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.'); + if (typeof reducer(undefined, { type: type }) === 'undefined') { + throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined, but can be null.'); + } + }); +} + +/** + * Turns an object whose values are different reducer functions, into a single + * reducer function. It will call every child reducer, and gather their results + * into a single state object, whose keys correspond to the keys of the passed + * reducer functions. + * + * @param {Object} reducers An object whose values correspond to different + * reducer functions that need to be combined into one. One handy way to obtain + * it is to use ES6 `import * as reducers` syntax. The reducers may never return + * undefined for any action. Instead, they should return their initial state + * if the state passed to them was undefined, and the current state for any + * unrecognized action. + * + * @returns {Function} A reducer function that invokes every reducer inside the + * passed object, and builds a state object with the same shape. + */ +function combineReducers(reducers) { + var reducerKeys = Object.keys(reducers); + var finalReducers = {}; + for (var i = 0; i < reducerKeys.length; i++) { + var key = reducerKeys[i]; + + if (false) {} + + if (typeof reducers[key] === 'function') { + finalReducers[key] = reducers[key]; + } + } + var finalReducerKeys = Object.keys(finalReducers); + + var unexpectedKeyCache = void 0; + if (false) {} + + var shapeAssertionError = void 0; + try { + assertReducerShape(finalReducers); + } catch (e) { + shapeAssertionError = e; + } + + return function combination() { + var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var action = arguments[1]; + + if (shapeAssertionError) { + throw shapeAssertionError; + } + + if (false) { var warningMessage; } + + var hasChanged = false; + var nextState = {}; + for (var _i = 0; _i < finalReducerKeys.length; _i++) { + var _key = finalReducerKeys[_i]; + var reducer = finalReducers[_key]; + var previousStateForKey = state[_key]; + var nextStateForKey = reducer(previousStateForKey, action); + if (typeof nextStateForKey === 'undefined') { + var errorMessage = getUndefinedStateErrorMessage(_key, action); + throw new Error(errorMessage); + } + nextState[_key] = nextStateForKey; + hasChanged = hasChanged || nextStateForKey !== previousStateForKey; + } + return hasChanged ? nextState : state; + }; +} +// CONCATENATED MODULE: ./node_modules/redux/es/bindActionCreators.js +function bindActionCreator(actionCreator, dispatch) { + return function () { + return dispatch(actionCreator.apply(undefined, arguments)); + }; +} + +/** + * Turns an object whose values are action creators, into an object with the + * same keys, but with every function wrapped into a `dispatch` call so they + * may be invoked directly. This is just a convenience method, as you can call + * `store.dispatch(MyActionCreators.doSomething())` yourself just fine. + * + * For convenience, you can also pass a single function as the first argument, + * and get a function in return. + * + * @param {Function|Object} actionCreators An object whose values are action + * creator functions. One handy way to obtain it is to use ES6 `import * as` + * syntax. You may also pass a single function. + * + * @param {Function} dispatch The `dispatch` function available on your Redux + * store. + * + * @returns {Function|Object} The object mimicking the original object, but with + * every action creator wrapped into the `dispatch` call. If you passed a + * function as `actionCreators`, the return value will also be a single + * function. + */ +function bindActionCreators(actionCreators, dispatch) { + if (typeof actionCreators === 'function') { + return bindActionCreator(actionCreators, dispatch); + } + + if (typeof actionCreators !== 'object' || actionCreators === null) { + throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?'); + } + + var keys = Object.keys(actionCreators); + var boundActionCreators = {}; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var actionCreator = actionCreators[key]; + if (typeof actionCreator === 'function') { + boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); + } + } + return boundActionCreators; +} +// CONCATENATED MODULE: ./node_modules/redux/es/compose.js +/** + * Composes single-argument functions from right to left. The rightmost + * function can take multiple arguments as it provides the signature for + * the resulting composite function. + * + * @param {...Function} funcs The functions to compose. + * @returns {Function} A function obtained by composing the argument functions + * from right to left. For example, compose(f, g, h) is identical to doing + * (...args) => f(g(h(...args))). + */ + +function compose() { + for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) { + funcs[_key] = arguments[_key]; + } + + if (funcs.length === 0) { + return function (arg) { + return arg; + }; + } + + if (funcs.length === 1) { + return funcs[0]; + } + + return funcs.reduce(function (a, b) { + return function () { + return a(b.apply(undefined, arguments)); + }; + }); +} +// CONCATENATED MODULE: ./node_modules/redux/es/applyMiddleware.js +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + + + +/** + * Creates a store enhancer that applies middleware to the dispatch method + * of the Redux store. This is handy for a variety of tasks, such as expressing + * asynchronous actions in a concise manner, or logging every action payload. + * + * See `redux-thunk` package as an example of the Redux middleware. + * + * Because middleware is potentially asynchronous, this should be the first + * store enhancer in the composition chain. + * + * Note that each middleware will be given the `dispatch` and `getState` functions + * as named arguments. + * + * @param {...Function} middlewares The middleware chain to be applied. + * @returns {Function} A store enhancer applying the middleware. + */ +function applyMiddleware() { + for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { + middlewares[_key] = arguments[_key]; + } + + return function (createStore) { + return function (reducer, preloadedState, enhancer) { + var store = createStore(reducer, preloadedState, enhancer); + var _dispatch = store.dispatch; + var chain = []; + + var middlewareAPI = { + getState: store.getState, + dispatch: function dispatch(action) { + return _dispatch(action); + } + }; + chain = middlewares.map(function (middleware) { + return middleware(middlewareAPI); + }); + _dispatch = compose.apply(undefined, chain)(store.dispatch); + + return _extends({}, store, { + dispatch: _dispatch + }); + }; + }; +} +// CONCATENATED MODULE: ./node_modules/redux/es/index.js +/* concated harmony reexport createStore */__webpack_require__.d(__webpack_exports__, "createStore", function() { return createStore_createStore; }); +/* concated harmony reexport combineReducers */__webpack_require__.d(__webpack_exports__, "combineReducers", function() { return combineReducers; }); +/* concated harmony reexport bindActionCreators */__webpack_require__.d(__webpack_exports__, "bindActionCreators", function() { return bindActionCreators; }); +/* concated harmony reexport applyMiddleware */__webpack_require__.d(__webpack_exports__, "applyMiddleware", function() { return applyMiddleware; }); +/* concated harmony reexport compose */__webpack_require__.d(__webpack_exports__, "compose", function() { return compose; }); + + + + + + + +/* +* This is a dummy function to check if the function name has been altered by minification. +* If the function has been minified and NODE_ENV !== 'production', warn the user. +*/ +function isCrushed() {} + +if (false) {} + + + +/***/ }), +/* 7 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return symbolObservablePonyfill; }); +function symbolObservablePonyfill(root) { + var result; + var Symbol = root.Symbol; + + if (typeof Symbol === 'function') { + if (Symbol.observable) { + result = Symbol.observable; + } else { + result = Symbol('observable'); + Symbol.observable = result; + } + } else { + result = '@@observable'; + } + + return result; +}; + + +/***/ }), +/* 8 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) {/** Detect free variable `global` from Node.js. */ +var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; + +/* harmony default export */ __webpack_exports__["a"] = (freeGlobal); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(3))) + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = __webpack_require__(10); + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _fuse = _interopRequireDefault(__webpack_require__(11)); + +var _deepmerge = _interopRequireDefault(__webpack_require__(12)); + +var _store = _interopRequireDefault(__webpack_require__(13)); + +var _components = __webpack_require__(20); + +var _constants = __webpack_require__(1); + +var _templates = __webpack_require__(5); + +var _choices = __webpack_require__(28); + +var _items = __webpack_require__(29); + +var _groups = __webpack_require__(30); + +var _misc = __webpack_require__(31); + +var _general = __webpack_require__(32); + +var _utils = __webpack_require__(0); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +/** + * Choices + * @author Josh Johnson + */ +var Choices = +/*#__PURE__*/ +function () { + function Choices() { + var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '[data-choice]'; + var userConfig = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Choices); + + if ((0, _utils.isType)('String', element)) { + var elements = Array.from(document.querySelectorAll(element)); // If there are multiple elements, create a new instance + // for each element besides the first one (as that already has an instance) + + if (elements.length > 1) { + return this._generateInstances(elements, userConfig); + } + } + + this.config = _deepmerge.default.all([_constants.DEFAULT_CONFIG, Choices.userDefaults, userConfig], // When merging array configs, replace with a copy of the userConfig array, + // instead of concatenating with the default array + { + arrayMerge: function arrayMerge(destinationArray, sourceArray) { + return [].concat(sourceArray); + } + }); + var invalidConfigOptions = (0, _utils.diff)(this.config, _constants.DEFAULT_CONFIG); + + if (invalidConfigOptions.length) { + console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); + } + + if (!['auto', 'always'].includes(this.config.renderSelectedChoices)) { + this.config.renderSelectedChoices = 'auto'; + } // Retrieve triggering element (i.e. element with 'data-choice' trigger) + + + var passedElement = (0, _utils.isType)('String', element) ? document.querySelector(element) : element; + + if (!passedElement) { + return console.error('Could not find passed element or passed element was of an invalid type'); + } + + this._isTextElement = passedElement.type === 'text'; + this._isSelectOneElement = passedElement.type === 'select-one'; + this._isSelectMultipleElement = passedElement.type === 'select-multiple'; + this._isSelectElement = this._isSelectOneElement || this._isSelectMultipleElement; + + if (this._isTextElement) { + this.passedElement = new _components.WrappedInput({ + element: passedElement, + classNames: this.config.classNames, + delimiter: this.config.delimiter + }); + } else if (this._isSelectElement) { + this.passedElement = new _components.WrappedSelect({ + element: passedElement, + classNames: this.config.classNames + }); + } + + if (!this.passedElement) { + return console.error('Passed element was of an invalid type'); + } + + if (this.config.shouldSortItems === true && this._isSelectOneElement && !this.config.silent) { + console.warn("shouldSortElements: Type of passed element is 'select-one', falling back to false."); + } + + this.initialised = false; + this._store = new _store.default(this.render); + this._initialState = {}; + this._currentState = {}; + this._prevState = {}; + this._currentValue = ''; + this._canSearch = this.config.searchEnabled; + this._isScrollingOnIe = false; + this._highlightPosition = 0; + this._wasTap = true; + this._placeholderValue = this._generatePlaceholderValue(); + this._baseId = (0, _utils.generateId)(this.passedElement.element, 'choices-'); + this._direction = this.passedElement.element.getAttribute('dir') || 'ltr'; + this._idNames = { + itemChoice: 'item-choice' + }; // Assign preset choices from passed object + + this._presetChoices = this.config.choices; // Assign preset items from passed object first + + this._presetItems = this.config.items; // Then add any values passed from attribute + + if (this.passedElement.value) { + this._presetItems = this._presetItems.concat(this.passedElement.value.split(this.config.delimiter)); + } + + this._render = this._render.bind(this); + this._onFocus = this._onFocus.bind(this); + this._onBlur = this._onBlur.bind(this); + this._onKeyUp = this._onKeyUp.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); + this._onClick = this._onClick.bind(this); + this._onTouchMove = this._onTouchMove.bind(this); + this._onTouchEnd = this._onTouchEnd.bind(this); + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseOver = this._onMouseOver.bind(this); + this._onFormReset = this._onFormReset.bind(this); + this._onAKey = this._onAKey.bind(this); + this._onEnterKey = this._onEnterKey.bind(this); + this._onEscapeKey = this._onEscapeKey.bind(this); + this._onDirectionKey = this._onDirectionKey.bind(this); + this._onDeleteKey = this._onDeleteKey.bind(this); // If element has already been initialised with Choices, fail silently + + if (this.passedElement.element.getAttribute('data-choice') === 'active') { + console.warn('Trying to initialise Choices on element already initialised'); + } // Let's go + + + this.init(); + } + /* ======================================== + = Public functions = + ======================================== */ + + + _createClass(Choices, [{ + key: "init", + value: function init() { + if (this.initialised) { + return; + } + + this._createTemplates(); + + this._createElements(); + + this._createStructure(); // Set initial state (We need to clone the state because some reducers + // modify the inner objects properties in the state) 🤢 + + + this._initialState = (0, _utils.cloneObject)(this._store.state); + + this._store.subscribe(this._render); + + this._render(); + + this._addEventListeners(); + + var shouldDisable = !this.config.addItems || this.passedElement.element.hasAttribute('disabled'); + + if (shouldDisable) { + this.disable(); + } + + this.initialised = true; + var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function + + if (callbackOnInit && (0, _utils.isType)('Function', callbackOnInit)) { + callbackOnInit.call(this); + } + } + }, { + key: "destroy", + value: function destroy() { + if (!this.initialised) { + return; + } + + this._removeEventListeners(); + + this.passedElement.reveal(); + this.containerOuter.unwrap(this.passedElement.element); + + if (this._isSelectElement) { + this.passedElement.options = this._presetChoices; + } + + this.clearStore(); + this.config.templates = null; + this.initialised = false; + } + }, { + key: "enable", + value: function enable() { + if (this.passedElement.isDisabled) { + this.passedElement.enable(); + } + + if (this.containerOuter.isDisabled) { + this._addEventListeners(); + + this.input.enable(); + this.containerOuter.enable(); + } + + return this; + } + }, { + key: "disable", + value: function disable() { + if (!this.passedElement.isDisabled) { + this.passedElement.disable(); + } + + if (!this.containerOuter.isDisabled) { + this._removeEventListeners(); + + this.input.disable(); + this.containerOuter.disable(); + } + + return this; + } + }, { + key: "highlightItem", + value: function highlightItem(item) { + var runEvent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + + if (!item) { + return this; + } + + var id = item.id, + _item$groupId = item.groupId, + groupId = _item$groupId === void 0 ? -1 : _item$groupId, + _item$value = item.value, + value = _item$value === void 0 ? '' : _item$value, + _item$label = item.label, + label = _item$label === void 0 ? '' : _item$label; + var group = groupId >= 0 ? this._store.getGroupById(groupId) : null; + + this._store.dispatch((0, _items.highlightItem)(id, true)); + + if (runEvent) { + this.passedElement.triggerEvent(_constants.EVENTS.highlightItem, { + id: id, + value: value, + label: label, + groupValue: group && group.value ? group.value : null + }); + } + + return this; + } + }, { + key: "unhighlightItem", + value: function unhighlightItem(item) { + if (!item) { + return this; + } + + var id = item.id, + _item$groupId2 = item.groupId, + groupId = _item$groupId2 === void 0 ? -1 : _item$groupId2, + _item$value2 = item.value, + value = _item$value2 === void 0 ? '' : _item$value2, + _item$label2 = item.label, + label = _item$label2 === void 0 ? '' : _item$label2; + var group = groupId >= 0 ? this._store.getGroupById(groupId) : null; + + this._store.dispatch((0, _items.highlightItem)(id, false)); + + this.passedElement.triggerEvent(_constants.EVENTS.highlightItem, { + id: id, + value: value, + label: label, + groupValue: group && group.value ? group.value : null + }); + return this; + } + }, { + key: "highlightAll", + value: function highlightAll() { + var _this = this; + + this._store.items.forEach(function (item) { + return _this.highlightItem(item); + }); + + return this; + } + }, { + key: "unhighlightAll", + value: function unhighlightAll() { + var _this2 = this; + + this._store.items.forEach(function (item) { + return _this2.unhighlightItem(item); + }); + + return this; + } + }, { + key: "removeActiveItemsByValue", + value: function removeActiveItemsByValue(value) { + var _this3 = this; + + this._store.activeItems.filter(function (item) { + return item.value === value; + }).forEach(function (item) { + return _this3._removeItem(item); + }); + + return this; + } + }, { + key: "removeActiveItems", + value: function removeActiveItems(excludedId) { + var _this4 = this; + + this._store.activeItems.filter(function (_ref) { + var id = _ref.id; + return id !== excludedId; + }).forEach(function (item) { + return _this4._removeItem(item); + }); + + return this; + } + }, { + key: "removeHighlightedItems", + value: function removeHighlightedItems() { + var _this5 = this; + + var runEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + this._store.highlightedActiveItems.forEach(function (item) { + _this5._removeItem(item); // If this action was performed by the user + // trigger the event + + + if (runEvent) { + _this5._triggerChange(item.value); + } + }); + + return this; + } + }, { + key: "showDropdown", + value: function showDropdown(preventInputFocus) { + var _this6 = this; + + if (this.dropdown.isActive) { + return this; + } + + requestAnimationFrame(function () { + _this6.dropdown.show(); + + _this6.containerOuter.open(_this6.dropdown.distanceFromTopWindow()); + + if (!preventInputFocus && _this6._canSearch) { + _this6.input.focus(); + } + + _this6.passedElement.triggerEvent(_constants.EVENTS.showDropdown, {}); + }); + return this; + } + }, { + key: "hideDropdown", + value: function hideDropdown(preventInputBlur) { + var _this7 = this; + + if (!this.dropdown.isActive) { + return this; + } + + requestAnimationFrame(function () { + _this7.dropdown.hide(); + + _this7.containerOuter.close(); + + if (!preventInputBlur && _this7._canSearch) { + _this7.input.removeActiveDescendant(); + + _this7.input.blur(); + } + + _this7.passedElement.triggerEvent(_constants.EVENTS.hideDropdown, {}); + }); + return this; + } + }, { + key: "getValue", + value: function getValue() { + var valueOnly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + var values = this._store.activeItems.reduce(function (selectedItems, item) { + var itemValue = valueOnly ? item.value : item; + selectedItems.push(itemValue); + return selectedItems; + }, []); + + return this._isSelectOneElement ? values[0] : values; + } + }, { + key: "setValue", + value: function setValue(args) { + var _this8 = this; + + if (!this.initialised) { + return this; + } + + [].concat(args).forEach(function (value) { + return _this8._setChoiceOrItem(value); + }); + return this; + } + }, { + key: "setChoiceByValue", + value: function setChoiceByValue(value) { + var _this9 = this; + + if (!this.initialised || this._isTextElement) { + return this; + } // If only one value has been passed, convert to array + + + var choiceValue = (0, _utils.isType)('Array', value) ? value : [value]; // Loop through each value and + + choiceValue.forEach(function (val) { + return _this9._findAndSelectChoiceByValue(val); + }); + return this; + } + }, { + key: "setChoices", + value: function setChoices() { + var _this10 = this; + + var choices = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var label = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; + var replaceChoices = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + if (!this._isSelectElement || !value) { + return this; + } // Clear choices if needed + + + if (replaceChoices) { + this.clearChoices(); + } + + this.containerOuter.removeLoadingState(); + + var addGroupsAndChoices = function addGroupsAndChoices(groupOrChoice) { + if (groupOrChoice.choices) { + _this10._addGroup({ + group: groupOrChoice, + id: groupOrChoice.id || null, + valueKey: value, + labelKey: label + }); + } else { + _this10._addChoice({ + value: groupOrChoice[value], + label: groupOrChoice[label], + isSelected: groupOrChoice.selected, + isDisabled: groupOrChoice.disabled, + customProperties: groupOrChoice.customProperties, + placeholder: groupOrChoice.placeholder + }); + } + }; + + this._setLoading(true); + + choices.forEach(addGroupsAndChoices); + + this._setLoading(false); + + return this; + } + }, { + key: "clearChoices", + value: function clearChoices() { + this._store.dispatch((0, _choices.clearChoices)()); + } + }, { + key: "clearStore", + value: function clearStore() { + this._store.dispatch((0, _misc.clearAll)()); + + return this; + } + }, { + key: "clearInput", + value: function clearInput() { + var shouldSetInputWidth = !this._isSelectOneElement; + this.input.clear(shouldSetInputWidth); + + if (!this._isTextElement && this._canSearch) { + this._isSearching = false; + + this._store.dispatch((0, _choices.activateChoices)(true)); + } + + return this; + } + }, { + key: "ajax", + value: function ajax(fn) { + var _this11 = this; + + if (!this.initialised || !this._isSelectElement || !fn) { + return this; + } + + requestAnimationFrame(function () { + return _this11._handleLoadingState(true); + }); + fn(this._ajaxCallback()); + return this; + } + /* ===== End of Public functions ====== */ + + /* ============================================= + = Private functions = + ============================================= */ + + }, { + key: "_render", + value: function _render() { + if (this._store.isLoading()) { + return; + } + + this._currentState = this._store.state; + var stateChanged = this._currentState.choices !== this._prevState.choices || this._currentState.groups !== this._prevState.groups || this._currentState.items !== this._prevState.items; + var shouldRenderChoices = this._isSelectElement; + var shouldRenderItems = this._currentState.items !== this._prevState.items; + + if (!stateChanged) { + return; + } + + if (shouldRenderChoices) { + this._renderChoices(); + } + + if (shouldRenderItems) { + this._renderItems(); + } + + this._prevState = this._currentState; + } + }, { + key: "_renderChoices", + value: function _renderChoices() { + var _this12 = this; + + var _this$_store = this._store, + activeGroups = _this$_store.activeGroups, + activeChoices = _this$_store.activeChoices; + var choiceListFragment = document.createDocumentFragment(); + this.choiceList.clear(); + + if (this.config.resetScrollPosition) { + requestAnimationFrame(function () { + return _this12.choiceList.scrollToTop(); + }); + } // If we have grouped options + + + if (activeGroups.length >= 1 && !this._isSearching) { + // If we have a placeholder choice along with groups + var activePlaceholders = activeChoices.filter(function (activeChoice) { + return activeChoice.placeholder === true && activeChoice.groupId === -1; + }); + + if (activePlaceholders.length >= 1) { + choiceListFragment = this._createChoicesFragment(activePlaceholders, choiceListFragment); + } + + choiceListFragment = this._createGroupsFragment(activeGroups, activeChoices, choiceListFragment); + } else if (activeChoices.length >= 1) { + choiceListFragment = this._createChoicesFragment(activeChoices, choiceListFragment); + } // If we have choices to show + + + if (choiceListFragment.childNodes && choiceListFragment.childNodes.length > 0) { + var activeItems = this._store.activeItems; + + var canAddItem = this._canAddItem(activeItems, this.input.value); // ...and we can select them + + + if (canAddItem.response) { + // ...append them and highlight the first choice + this.choiceList.append(choiceListFragment); + + this._highlightChoice(); + } else { + // ...otherwise show a notice + this.choiceList.append(this._getTemplate('notice', canAddItem.notice)); + } + } else { + // Otherwise show a notice + var dropdownItem; + var notice; + + if (this._isSearching) { + notice = (0, _utils.isType)('Function', this.config.noResultsText) ? this.config.noResultsText() : this.config.noResultsText; + dropdownItem = this._getTemplate('notice', notice, 'no-results'); + } else { + notice = (0, _utils.isType)('Function', this.config.noChoicesText) ? this.config.noChoicesText() : this.config.noChoicesText; + dropdownItem = this._getTemplate('notice', notice, 'no-choices'); + } + + this.choiceList.append(dropdownItem); + } + } + }, { + key: "_renderItems", + value: function _renderItems() { + var activeItems = this._store.activeItems || []; + this.itemList.clear(); // Create a fragment to store our list items + // (so we don't have to update the DOM for each item) + + var itemListFragment = this._createItemsFragment(activeItems); // If we have items to add, append them + + + if (itemListFragment.childNodes) { + this.itemList.append(itemListFragment); + } + } + }, { + key: "_createGroupsFragment", + value: function _createGroupsFragment(groups, choices, fragment) { + var _this13 = this; + + var groupFragment = fragment || document.createDocumentFragment(); + + var getGroupChoices = function getGroupChoices(group) { + return choices.filter(function (choice) { + if (_this13._isSelectOneElement) { + return choice.groupId === group.id; + } + + return choice.groupId === group.id && (_this13.config.renderSelectedChoices === 'always' || !choice.selected); + }); + }; // If sorting is enabled, filter groups + + + if (this.config.shouldSort) { + groups.sort(this.config.sortFn); + } + + groups.forEach(function (group) { + var groupChoices = getGroupChoices(group); + + if (groupChoices.length >= 1) { + var dropdownGroup = _this13._getTemplate('choiceGroup', group); + + groupFragment.appendChild(dropdownGroup); + + _this13._createChoicesFragment(groupChoices, groupFragment, true); + } + }); + return groupFragment; + } + }, { + key: "_createChoicesFragment", + value: function _createChoicesFragment(choices, fragment) { + var _this14 = this; + + var withinGroup = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + // Create a fragment to store our list items (so we don't have to update the DOM for each item) + var choicesFragment = fragment || document.createDocumentFragment(); + var _this$config = this.config, + renderSelectedChoices = _this$config.renderSelectedChoices, + searchResultLimit = _this$config.searchResultLimit, + renderChoiceLimit = _this$config.renderChoiceLimit; + var filter = this._isSearching ? _utils.sortByScore : this.config.sortFn; + + var appendChoice = function appendChoice(choice) { + var shouldRender = renderSelectedChoices === 'auto' ? _this14._isSelectOneElement || !choice.selected : true; + + if (shouldRender) { + var dropdownItem = _this14._getTemplate('choice', choice, _this14.config.itemSelectText); + + choicesFragment.appendChild(dropdownItem); + } + }; + + var rendererableChoices = choices; + + if (renderSelectedChoices === 'auto' && !this._isSelectOneElement) { + rendererableChoices = choices.filter(function (choice) { + return !choice.selected; + }); + } // Split array into placeholders and "normal" choices + + + var _rendererableChoices$ = rendererableChoices.reduce(function (acc, choice) { + if (choice.placeholder) { + acc.placeholderChoices.push(choice); + } else { + acc.normalChoices.push(choice); + } + + return acc; + }, { + placeholderChoices: [], + normalChoices: [] + }), + placeholderChoices = _rendererableChoices$.placeholderChoices, + normalChoices = _rendererableChoices$.normalChoices; // If sorting is enabled or the user is searching, filter choices + + + if (this.config.shouldSort || this._isSearching) { + normalChoices.sort(filter); + } + + var choiceLimit = rendererableChoices.length; // Prepend placeholeder + + var sortedChoices = [].concat(placeholderChoices, normalChoices); + + if (this._isSearching) { + choiceLimit = searchResultLimit; + } else if (renderChoiceLimit > 0 && !withinGroup) { + choiceLimit = renderChoiceLimit; + } // Add each choice to dropdown within range + + + for (var i = 0; i < choiceLimit; i += 1) { + if (sortedChoices[i]) { + appendChoice(sortedChoices[i]); + } + } + + return choicesFragment; + } + }, { + key: "_createItemsFragment", + value: function _createItemsFragment(items) { + var _this15 = this; + + var fragment = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + // Create fragment to add elements to + var _this$config2 = this.config, + shouldSortItems = _this$config2.shouldSortItems, + sortFn = _this$config2.sortFn, + removeItemButton = _this$config2.removeItemButton; + var itemListFragment = fragment || document.createDocumentFragment(); // If sorting is enabled, filter items + + if (shouldSortItems && !this._isSelectOneElement) { + items.sort(sortFn); + } + + if (this._isTextElement) { + // Update the value of the hidden input + this.passedElement.value = items; + } else { + // Update the options of the hidden input + this.passedElement.options = items; + } + + var addItemToFragment = function addItemToFragment(item) { + // Create new list element + var listItem = _this15._getTemplate('item', item, removeItemButton); // Append it to list + + + itemListFragment.appendChild(listItem); + }; // Add each list item to list + + + items.forEach(function (item) { + return addItemToFragment(item); + }); + return itemListFragment; + } + }, { + key: "_triggerChange", + value: function _triggerChange(value) { + if (value === undefined || value === null) { + return; + } + + this.passedElement.triggerEvent(_constants.EVENTS.change, { + value: value + }); + } + }, { + key: "_selectPlaceholderChoice", + value: function _selectPlaceholderChoice() { + var placeholderChoice = this._store.placeholderChoice; + + if (placeholderChoice) { + this._addItem({ + value: placeholderChoice.value, + label: placeholderChoice.label, + choiceId: placeholderChoice.id, + groupId: placeholderChoice.groupId, + placeholder: placeholderChoice.placeholder + }); + + this._triggerChange(placeholderChoice.value); + } + } + }, { + key: "_handleButtonAction", + value: function _handleButtonAction(activeItems, element) { + if (!activeItems || !element || !this.config.removeItems || !this.config.removeItemButton) { + return; + } + + var itemId = element.parentNode.getAttribute('data-id'); + var itemToRemove = activeItems.find(function (item) { + return item.id === parseInt(itemId, 10); + }); // Remove item associated with button + + this._removeItem(itemToRemove); + + this._triggerChange(itemToRemove.value); + + if (this._isSelectOneElement) { + this._selectPlaceholderChoice(); + } + } + }, { + key: "_handleItemAction", + value: function _handleItemAction(activeItems, element) { + var _this16 = this; + + var hasShiftKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + if (!activeItems || !element || !this.config.removeItems || this._isSelectOneElement) { + return; + } + + var passedId = element.getAttribute('data-id'); // We only want to select one item with a click + // so we deselect any items that aren't the target + // unless shift is being pressed + + activeItems.forEach(function (item) { + if (item.id === parseInt(passedId, 10) && !item.highlighted) { + _this16.highlightItem(item); + } else if (!hasShiftKey && item.highlighted) { + _this16.unhighlightItem(item); + } + }); // Focus input as without focus, a user cannot do anything with a + // highlighted item + + this.input.focus(); + } + }, { + key: "_handleChoiceAction", + value: function _handleChoiceAction(activeItems, element) { + if (!activeItems || !element) { + return; + } // If we are clicking on an option + + + var id = element.getAttribute('data-id'); + + var choice = this._store.getChoiceById(id); + + var passedKeyCode = activeItems[0] && activeItems[0].keyCode ? activeItems[0].keyCode : null; + var hasActiveDropdown = this.dropdown.isActive; // Update choice keyCode + + choice.keyCode = passedKeyCode; + this.passedElement.triggerEvent(_constants.EVENTS.choice, { + choice: choice + }); + + if (choice && !choice.selected && !choice.disabled) { + var canAddItem = this._canAddItem(activeItems, choice.value); + + if (canAddItem.response) { + this._addItem({ + value: choice.value, + label: choice.label, + choiceId: choice.id, + groupId: choice.groupId, + customProperties: choice.customProperties, + placeholder: choice.placeholder, + keyCode: choice.keyCode + }); + + this._triggerChange(choice.value); + } + } + + this.clearInput(); // We wont to close the dropdown if we are dealing with a single select box + + if (hasActiveDropdown && this._isSelectOneElement) { + this.hideDropdown(true); + this.containerOuter.focus(); + } + } + }, { + key: "_handleBackspace", + value: function _handleBackspace(activeItems) { + if (!this.config.removeItems || !activeItems) { + return; + } + + var lastItem = activeItems[activeItems.length - 1]; + var hasHighlightedItems = activeItems.some(function (item) { + return item.highlighted; + }); // If editing the last item is allowed and there are not other selected items, + // we can edit the item value. Otherwise if we can remove items, remove all selected items + + if (this.config.editItems && !hasHighlightedItems && lastItem) { + this.input.value = lastItem.value; + this.input.setWidth(); + + this._removeItem(lastItem); + + this._triggerChange(lastItem.value); + } else { + if (!hasHighlightedItems) { + // Highlight last item if none already highlighted + this.highlightItem(lastItem, false); + } + + this.removeHighlightedItems(true); + } + } + }, { + key: "_setLoading", + value: function _setLoading(isLoading) { + this._store.dispatch((0, _general.setIsLoading)(isLoading)); + } + }, { + key: "_handleLoadingState", + value: function _handleLoadingState() { + var setLoading = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + var placeholderItem = this.itemList.getChild(".".concat(this.config.classNames.placeholder)); + + if (setLoading) { + this.disable(); + this.containerOuter.addLoadingState(); + + if (this._isSelectOneElement) { + if (!placeholderItem) { + placeholderItem = this._getTemplate('placeholder', this.config.loadingText); + this.itemList.append(placeholderItem); + } else { + placeholderItem.innerHTML = this.config.loadingText; + } + } else { + this.input.placeholder = this.config.loadingText; + } + } else { + this.enable(); + this.containerOuter.removeLoadingState(); + + if (this._isSelectOneElement) { + placeholderItem.innerHTML = this._placeholderValue || ''; + } else { + this.input.placeholder = this._placeholderValue || ''; + } + } + } + }, { + key: "_handleSearch", + value: function _handleSearch(value) { + if (!value || !this.input.isFocussed) { + return; + } + + var choices = this._store.choices; + var _this$config3 = this.config, + searchFloor = _this$config3.searchFloor, + searchChoices = _this$config3.searchChoices; + var hasUnactiveChoices = choices.some(function (option) { + return !option.active; + }); // Check that we have a value to search and the input was an alphanumeric character + + if (value && value.length >= searchFloor) { + var resultCount = searchChoices ? this._searchChoices(value) : 0; // Trigger search event + + this.passedElement.triggerEvent(_constants.EVENTS.search, { + value: value, + resultCount: resultCount + }); + } else if (hasUnactiveChoices) { + // Otherwise reset choices to active + this._isSearching = false; + + this._store.dispatch((0, _choices.activateChoices)(true)); + } + } + }, { + key: "_canAddItem", + value: function _canAddItem(activeItems, value) { + var canAddItem = true; + var notice = (0, _utils.isType)('Function', this.config.addItemText) ? this.config.addItemText(value) : this.config.addItemText; + + if (!this._isSelectOneElement) { + var isDuplicateValue = (0, _utils.existsInArray)(activeItems, value); + + if (this.config.maxItemCount > 0 && this.config.maxItemCount <= activeItems.length) { + // If there is a max entry limit and we have reached that limit + // don't update + canAddItem = false; + notice = (0, _utils.isType)('Function', this.config.maxItemText) ? this.config.maxItemText(this.config.maxItemCount) : this.config.maxItemText; + } + + if (!this.config.duplicateItemsAllowed && isDuplicateValue && canAddItem) { + canAddItem = false; + notice = (0, _utils.isType)('Function', this.config.uniqueItemText) ? this.config.uniqueItemText(value) : this.config.uniqueItemText; + } + + if (this._isTextElement && this.config.addItems && canAddItem && (0, _utils.isType)('Function', this.config.addItemFilterFn) && !this.config.addItemFilterFn(value)) { + canAddItem = false; + notice = (0, _utils.isType)('Function', this.config.customAddItemText) ? this.config.customAddItemText(value) : this.config.customAddItemText; + } + } + + return { + response: canAddItem, + notice: notice + }; + } + }, { + key: "_ajaxCallback", + value: function _ajaxCallback() { + var _this17 = this; + + return function (results, value, label) { + if (!results || !value) { + return; + } + + var parsedResults = (0, _utils.isType)('Object', results) ? [results] : results; + + if (parsedResults && (0, _utils.isType)('Array', parsedResults) && parsedResults.length) { + // Remove loading states/text + _this17._handleLoadingState(false); + + _this17._setLoading(true); // Add each result as a choice + + + parsedResults.forEach(function (result) { + if (result.choices) { + _this17._addGroup({ + group: result, + id: result.id || null, + valueKey: value, + labelKey: label + }); + } else { + _this17._addChoice({ + value: (0, _utils.fetchFromObject)(result, value), + label: (0, _utils.fetchFromObject)(result, label), + isSelected: result.selected, + isDisabled: result.disabled, + customProperties: result.customProperties, + placeholder: result.placeholder + }); + } + }); + + _this17._setLoading(false); + + if (_this17._isSelectOneElement) { + _this17._selectPlaceholderChoice(); + } + } else { + // No results, remove loading state + _this17._handleLoadingState(false); + } + }; + } + }, { + key: "_searchChoices", + value: function _searchChoices(value) { + var newValue = (0, _utils.isType)('String', value) ? value.trim() : value; + var currentValue = (0, _utils.isType)('String', this._currentValue) ? this._currentValue.trim() : this._currentValue; + + if (newValue.length < 1 && newValue === "".concat(currentValue, " ")) { + return 0; + } // If new value matches the desired length and is not the same as the current value with a space + + + var haystack = this._store.searchableChoices; + var needle = newValue; + var keys = [].concat(this.config.searchFields); + var options = Object.assign(this.config.fuseOptions, { + keys: keys + }); + var fuse = new _fuse.default(haystack, options); + var results = fuse.search(needle); + this._currentValue = newValue; + this._highlightPosition = 0; + this._isSearching = true; + + this._store.dispatch((0, _choices.filterChoices)(results)); + + return results.length; + } + }, { + key: "_addEventListeners", + value: function _addEventListeners() { + document.addEventListener('keyup', this._onKeyUp); + document.addEventListener('keydown', this._onKeyDown); + document.addEventListener('click', this._onClick); + document.addEventListener('touchmove', this._onTouchMove); + document.addEventListener('touchend', this._onTouchEnd); + document.addEventListener('mousedown', this._onMouseDown); + document.addEventListener('mouseover', this._onMouseOver); + + if (this._isSelectOneElement) { + this.containerOuter.element.addEventListener('focus', this._onFocus); + this.containerOuter.element.addEventListener('blur', this._onBlur); + } + + this.input.element.addEventListener('focus', this._onFocus); + this.input.element.addEventListener('blur', this._onBlur); + + if (this.input.element.form) { + this.input.element.form.addEventListener('reset', this._onFormReset); + } + + this.input.addEventListeners(); + } + }, { + key: "_removeEventListeners", + value: function _removeEventListeners() { + document.removeEventListener('keyup', this._onKeyUp); + document.removeEventListener('keydown', this._onKeyDown); + document.removeEventListener('click', this._onClick); + document.removeEventListener('touchmove', this._onTouchMove); + document.removeEventListener('touchend', this._onTouchEnd); + document.removeEventListener('mousedown', this._onMouseDown); + document.removeEventListener('mouseover', this._onMouseOver); + + if (this._isSelectOneElement) { + this.containerOuter.element.removeEventListener('focus', this._onFocus); + this.containerOuter.element.removeEventListener('blur', this._onBlur); + } + + this.input.element.removeEventListener('focus', this._onFocus); + this.input.element.removeEventListener('blur', this._onBlur); + + if (this.input.element.form) { + this.input.element.form.removeEventListener('reset', this._onFormReset); + } + + this.input.removeEventListeners(); + } + }, { + key: "_onKeyDown", + value: function _onKeyDown(event) { + var _keyDownActions; + + var target = event.target, + keyCode = event.keyCode, + ctrlKey = event.ctrlKey, + metaKey = event.metaKey; + + if (target !== this.input.element && !this.containerOuter.element.contains(target)) { + return; + } + + var activeItems = this._store.activeItems; + var hasFocusedInput = this.input.isFocussed; + var hasActiveDropdown = this.dropdown.isActive; + var hasItems = this.itemList.hasChildren; + var keyString = String.fromCharCode(keyCode); + var BACK_KEY = _constants.KEY_CODES.BACK_KEY, + DELETE_KEY = _constants.KEY_CODES.DELETE_KEY, + ENTER_KEY = _constants.KEY_CODES.ENTER_KEY, + A_KEY = _constants.KEY_CODES.A_KEY, + ESC_KEY = _constants.KEY_CODES.ESC_KEY, + UP_KEY = _constants.KEY_CODES.UP_KEY, + DOWN_KEY = _constants.KEY_CODES.DOWN_KEY, + PAGE_UP_KEY = _constants.KEY_CODES.PAGE_UP_KEY, + PAGE_DOWN_KEY = _constants.KEY_CODES.PAGE_DOWN_KEY; + var hasCtrlDownKeyPressed = ctrlKey || metaKey; // If a user is typing and the dropdown is not active + + if (!this._isTextElement && /[a-zA-Z0-9-_ ]/.test(keyString)) { + this.showDropdown(); + } // Map keys to key actions + + + var keyDownActions = (_keyDownActions = {}, _defineProperty(_keyDownActions, A_KEY, this._onAKey), _defineProperty(_keyDownActions, ENTER_KEY, this._onEnterKey), _defineProperty(_keyDownActions, ESC_KEY, this._onEscapeKey), _defineProperty(_keyDownActions, UP_KEY, this._onDirectionKey), _defineProperty(_keyDownActions, PAGE_UP_KEY, this._onDirectionKey), _defineProperty(_keyDownActions, DOWN_KEY, this._onDirectionKey), _defineProperty(_keyDownActions, PAGE_DOWN_KEY, this._onDirectionKey), _defineProperty(_keyDownActions, DELETE_KEY, this._onDeleteKey), _defineProperty(_keyDownActions, BACK_KEY, this._onDeleteKey), _keyDownActions); // If keycode has a function, run it + + if (keyDownActions[keyCode]) { + keyDownActions[keyCode]({ + event: event, + target: target, + keyCode: keyCode, + metaKey: metaKey, + activeItems: activeItems, + hasFocusedInput: hasFocusedInput, + hasActiveDropdown: hasActiveDropdown, + hasItems: hasItems, + hasCtrlDownKeyPressed: hasCtrlDownKeyPressed + }); + } + } + }, { + key: "_onKeyUp", + value: function _onKeyUp(_ref2) { + var target = _ref2.target, + keyCode = _ref2.keyCode; + + if (target !== this.input.element) { + return; + } + + var value = this.input.value; + var activeItems = this._store.activeItems; + + var canAddItem = this._canAddItem(activeItems, value); + + var backKey = _constants.KEY_CODES.BACK_KEY, + deleteKey = _constants.KEY_CODES.DELETE_KEY; // We are typing into a text input and have a value, we want to show a dropdown + // notice. Otherwise hide the dropdown + + if (this._isTextElement) { + var canShowDropdownNotice = canAddItem.notice && value; + + if (canShowDropdownNotice) { + var dropdownItem = this._getTemplate('notice', canAddItem.notice); + + this.dropdown.element.innerHTML = dropdownItem.outerHTML; + this.showDropdown(true); + } else { + this.hideDropdown(true); + } + } else { + var userHasRemovedValue = (keyCode === backKey || keyCode === deleteKey) && !target.value; + var canReactivateChoices = !this._isTextElement && this._isSearching; + var canSearch = this._canSearch && canAddItem.response; + + if (userHasRemovedValue && canReactivateChoices) { + this._isSearching = false; + + this._store.dispatch((0, _choices.activateChoices)(true)); + } else if (canSearch) { + this._handleSearch(this.input.value); + } + } + + this._canSearch = this.config.searchEnabled; + } + }, { + key: "_onAKey", + value: function _onAKey(_ref3) { + var hasItems = _ref3.hasItems, + hasCtrlDownKeyPressed = _ref3.hasCtrlDownKeyPressed; + + // If CTRL + A or CMD + A have been pressed and there are items to select + if (hasCtrlDownKeyPressed && hasItems) { + this._canSearch = false; + var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; + + if (shouldHightlightAll) { + this.highlightAll(); + } + } + } + }, { + key: "_onEnterKey", + value: function _onEnterKey(_ref4) { + var event = _ref4.event, + target = _ref4.target, + activeItems = _ref4.activeItems, + hasActiveDropdown = _ref4.hasActiveDropdown; + var enterKey = _constants.KEY_CODES.ENTER_KEY; + var targetWasButton = target.hasAttribute('data-button'); + + if (this._isTextElement && target.value) { + var value = this.input.value; + + var canAddItem = this._canAddItem(activeItems, value); + + if (canAddItem.response) { + this.hideDropdown(true); + + this._addItem({ + value: value + }); + + this._triggerChange(value); + + this.clearInput(); + } + } + + if (targetWasButton) { + this._handleButtonAction(activeItems, target); + + event.preventDefault(); + } + + if (hasActiveDropdown) { + var highlightedChoice = this.dropdown.getChild(".".concat(this.config.classNames.highlightedState)); + + if (highlightedChoice) { + // add enter keyCode value + if (activeItems[0]) { + activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign + } + + this._handleChoiceAction(activeItems, highlightedChoice); + } + + event.preventDefault(); + } else if (this._isSelectOneElement) { + this.showDropdown(); + event.preventDefault(); + } + } + }, { + key: "_onEscapeKey", + value: function _onEscapeKey(_ref5) { + var hasActiveDropdown = _ref5.hasActiveDropdown; + + if (hasActiveDropdown) { + this.hideDropdown(true); + this.containerOuter.focus(); + } + } + }, { + key: "_onDirectionKey", + value: function _onDirectionKey(_ref6) { + var event = _ref6.event, + hasActiveDropdown = _ref6.hasActiveDropdown, + keyCode = _ref6.keyCode, + metaKey = _ref6.metaKey; + var downKey = _constants.KEY_CODES.DOWN_KEY, + pageUpKey = _constants.KEY_CODES.PAGE_UP_KEY, + pageDownKey = _constants.KEY_CODES.PAGE_DOWN_KEY; // If up or down key is pressed, traverse through options + + if (hasActiveDropdown || this._isSelectOneElement) { + this.showDropdown(); + this._canSearch = false; + var directionInt = keyCode === downKey || keyCode === pageDownKey ? 1 : -1; + var skipKey = metaKey || keyCode === pageDownKey || keyCode === pageUpKey; + var selectableChoiceIdentifier = '[data-choice-selectable]'; + var nextEl; + + if (skipKey) { + if (directionInt > 0) { + nextEl = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)).pop(); + } else { + nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); + } + } else { + var currentEl = this.dropdown.element.querySelector(".".concat(this.config.classNames.highlightedState)); + + if (currentEl) { + nextEl = (0, _utils.getAdjacentEl)(currentEl, selectableChoiceIdentifier, directionInt); + } else { + nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); + } + } + + if (nextEl) { + // We prevent default to stop the cursor moving + // when pressing the arrow + if (!(0, _utils.isScrolledIntoView)(nextEl, this.choiceList.element, directionInt)) { + this.choiceList.scrollToChoice(nextEl, directionInt); + } + + this._highlightChoice(nextEl); + } // Prevent default to maintain cursor position whilst + // traversing dropdown options + + + event.preventDefault(); + } + } + }, { + key: "_onDeleteKey", + value: function _onDeleteKey(_ref7) { + var event = _ref7.event, + target = _ref7.target, + hasFocusedInput = _ref7.hasFocusedInput, + activeItems = _ref7.activeItems; + + // If backspace or delete key is pressed and the input has no value + if (hasFocusedInput && !target.value && !this._isSelectOneElement) { + this._handleBackspace(activeItems); + + event.preventDefault(); + } + } + }, { + key: "_onTouchMove", + value: function _onTouchMove() { + if (this._wasTap) { + this._wasTap = false; + } + } + }, { + key: "_onTouchEnd", + value: function _onTouchEnd(event) { + var _ref8 = event || event.touches[0], + target = _ref8.target; + + var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); + + if (touchWasWithinContainer) { + var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; + + if (containerWasExactTarget) { + if (this._isTextElement) { + this.input.focus(); + } else if (this._isSelectMultipleElement) { + this.showDropdown(); + } + } // Prevents focus event firing + + + event.stopPropagation(); + } + + this._wasTap = true; + } + }, { + key: "_onMouseDown", + value: function _onMouseDown(event) { + var target = event.target, + shiftKey = event.shiftKey; // If we have our mouse down on the scrollbar and are on IE11... + + if (this.choiceList.element.contains(target) && (0, _utils.isIE11)()) { + this._isScrollingOnIe = true; + } + + if (!this.containerOuter.element.contains(target) || target === this.input.element) { + return; + } + + var activeItems = this._store.activeItems; + var hasShiftKey = shiftKey; + var buttonTarget = (0, _utils.findAncestorByAttrName)(target, 'data-button'); + var itemTarget = (0, _utils.findAncestorByAttrName)(target, 'data-item'); + var choiceTarget = (0, _utils.findAncestorByAttrName)(target, 'data-choice'); + + if (buttonTarget) { + this._handleButtonAction(activeItems, buttonTarget); + } else if (itemTarget) { + this._handleItemAction(activeItems, itemTarget, hasShiftKey); + } else if (choiceTarget) { + this._handleChoiceAction(activeItems, choiceTarget); + } + + event.preventDefault(); + } + }, { + key: "_onMouseOver", + value: function _onMouseOver(_ref9) { + var target = _ref9.target; + var targetWithinDropdown = target === this.dropdown || this.dropdown.element.contains(target); + var shouldHighlightChoice = targetWithinDropdown && target.hasAttribute('data-choice'); + + if (shouldHighlightChoice) { + this._highlightChoice(target); + } + } + }, { + key: "_onClick", + value: function _onClick(_ref10) { + var target = _ref10.target; + var clickWasWithinContainer = this.containerOuter.element.contains(target); + + if (clickWasWithinContainer) { + if (!this.dropdown.isActive && !this.containerOuter.isDisabled) { + if (this._isTextElement) { + if (document.activeElement !== this.input.element) { + this.input.focus(); + } + } else { + this.showDropdown(); + this.containerOuter.focus(); + } + } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { + this.hideDropdown(); + } + } else { + var hasHighlightedItems = this._store.highlightedActiveItems; + + if (hasHighlightedItems) { + this.unhighlightAll(); + } + + this.containerOuter.removeFocusState(); + this.hideDropdown(true); + } + } + }, { + key: "_onFocus", + value: function _onFocus(_ref11) { + var _this18 = this; + + var target = _ref11.target; + var focusWasWithinContainer = this.containerOuter.element.contains(target); + + if (!focusWasWithinContainer) { + return; + } + + var focusActions = { + text: function text() { + if (target === _this18.input.element) { + _this18.containerOuter.addFocusState(); + } + }, + 'select-one': function selectOne() { + _this18.containerOuter.addFocusState(); + + if (target === _this18.input.element) { + _this18.showDropdown(true); + } + }, + 'select-multiple': function selectMultiple() { + if (target === _this18.input.element) { + _this18.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown + // isn't already open, focus and show dropdown + + + _this18.containerOuter.addFocusState(); + } + } + }; + focusActions[this.passedElement.element.type](); + } + }, { + key: "_onBlur", + value: function _onBlur(_ref12) { + var _this19 = this; + + var target = _ref12.target; + var blurWasWithinContainer = this.containerOuter.element.contains(target); + + if (blurWasWithinContainer && !this._isScrollingOnIe) { + var activeItems = this._store.activeItems; + var hasHighlightedItems = activeItems.some(function (item) { + return item.highlighted; + }); + var blurActions = { + text: function text() { + if (target === _this19.input.element) { + _this19.containerOuter.removeFocusState(); + + if (hasHighlightedItems) { + _this19.unhighlightAll(); + } + + _this19.hideDropdown(true); + } + }, + 'select-one': function selectOne() { + _this19.containerOuter.removeFocusState(); + + if (target === _this19.input.element || target === _this19.containerOuter.element && !_this19._canSearch) { + _this19.hideDropdown(true); + } + }, + 'select-multiple': function selectMultiple() { + if (target === _this19.input.element) { + _this19.containerOuter.removeFocusState(); + + _this19.hideDropdown(true); + + if (hasHighlightedItems) { + _this19.unhighlightAll(); + } + } + } + }; + blurActions[this.passedElement.element.type](); + } else { + // On IE11, clicking the scollbar blurs our input and thus + // closes the dropdown. To stop this, we refocus our input + // if we know we are on IE *and* are scrolling. + this._isScrollingOnIe = false; + this.input.element.focus(); + } + } + }, { + key: "_onFormReset", + value: function _onFormReset() { + this._store.dispatch((0, _misc.resetTo)(this._initialState)); + } + }, { + key: "_highlightChoice", + value: function _highlightChoice() { + var _this20 = this; + + var el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var choices = Array.from(this.dropdown.element.querySelectorAll('[data-choice-selectable]')); + + if (!choices.length) { + return; + } + + var passedEl = el; + var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(".".concat(this.config.classNames.highlightedState))); // Remove any highlighted choices + + highlightedChoices.forEach(function (choice) { + choice.classList.remove(_this20.config.classNames.highlightedState); + choice.setAttribute('aria-selected', 'false'); + }); + + if (passedEl) { + this._highlightPosition = choices.indexOf(passedEl); + } else { + // Highlight choice based on last known highlight location + if (choices.length > this._highlightPosition) { + // If we have an option to highlight + passedEl = choices[this._highlightPosition]; + } else { + // Otherwise highlight the option before + passedEl = choices[choices.length - 1]; + } + + if (!passedEl) { + passedEl = choices[0]; + } + } + + passedEl.classList.add(this.config.classNames.highlightedState); + passedEl.setAttribute('aria-selected', 'true'); + this.passedElement.triggerEvent(_constants.EVENTS.highlightChoice, { + el: passedEl + }); + + if (this.dropdown.isActive) { + // IE11 ignores aria-label and blocks virtual keyboard + // if aria-activedescendant is set without a dropdown + this.input.setActiveDescendant(passedEl.id); + this.containerOuter.setActiveDescendant(passedEl.id); + } + } + }, { + key: "_addItem", + value: function _addItem(_ref13) { + var value = _ref13.value, + _ref13$label = _ref13.label, + label = _ref13$label === void 0 ? null : _ref13$label, + _ref13$choiceId = _ref13.choiceId, + choiceId = _ref13$choiceId === void 0 ? -1 : _ref13$choiceId, + _ref13$groupId = _ref13.groupId, + groupId = _ref13$groupId === void 0 ? -1 : _ref13$groupId, + _ref13$customProperti = _ref13.customProperties, + customProperties = _ref13$customProperti === void 0 ? null : _ref13$customProperti, + _ref13$placeholder = _ref13.placeholder, + placeholder = _ref13$placeholder === void 0 ? false : _ref13$placeholder, + _ref13$keyCode = _ref13.keyCode, + keyCode = _ref13$keyCode === void 0 ? null : _ref13$keyCode; + var passedValue = (0, _utils.isType)('String', value) ? value.trim() : value; + var passedKeyCode = keyCode; + var passedCustomProperties = customProperties; + var items = this._store.items; + var passedLabel = label || passedValue; + var passedOptionId = parseInt(choiceId, 10) || -1; + var group = groupId >= 0 ? this._store.getGroupById(groupId) : null; + var id = items ? items.length + 1 : 1; // If a prepended value has been passed, prepend it + + if (this.config.prependValue) { + passedValue = this.config.prependValue + passedValue.toString(); + } // If an appended value has been passed, append it + + + if (this.config.appendValue) { + passedValue += this.config.appendValue.toString(); + } + + this._store.dispatch((0, _items.addItem)({ + value: passedValue, + label: passedLabel, + id: id, + choiceId: passedOptionId, + groupId: groupId, + customProperties: customProperties, + placeholder: placeholder, + keyCode: passedKeyCode + })); + + if (this._isSelectOneElement) { + this.removeActiveItems(id); + } // Trigger change event + + + this.passedElement.triggerEvent(_constants.EVENTS.addItem, { + id: id, + value: passedValue, + label: passedLabel, + customProperties: passedCustomProperties, + groupValue: group && group.value ? group.value : undefined, + keyCode: passedKeyCode + }); + return this; + } + }, { + key: "_removeItem", + value: function _removeItem(item) { + if (!item || !(0, _utils.isType)('Object', item)) { + return this; + } + + var id = item.id, + value = item.value, + label = item.label, + choiceId = item.choiceId, + groupId = item.groupId; + var group = groupId >= 0 ? this._store.getGroupById(groupId) : null; + + this._store.dispatch((0, _items.removeItem)(id, choiceId)); + + if (group && group.value) { + this.passedElement.triggerEvent(_constants.EVENTS.removeItem, { + id: id, + value: value, + label: label, + groupValue: group.value + }); + } else { + this.passedElement.triggerEvent(_constants.EVENTS.removeItem, { + id: id, + value: value, + label: label + }); + } + + return this; + } + }, { + key: "_addChoice", + value: function _addChoice(_ref14) { + var value = _ref14.value, + _ref14$label = _ref14.label, + label = _ref14$label === void 0 ? null : _ref14$label, + _ref14$isSelected = _ref14.isSelected, + isSelected = _ref14$isSelected === void 0 ? false : _ref14$isSelected, + _ref14$isDisabled = _ref14.isDisabled, + isDisabled = _ref14$isDisabled === void 0 ? false : _ref14$isDisabled, + _ref14$groupId = _ref14.groupId, + groupId = _ref14$groupId === void 0 ? -1 : _ref14$groupId, + _ref14$customProperti = _ref14.customProperties, + customProperties = _ref14$customProperti === void 0 ? null : _ref14$customProperti, + _ref14$placeholder = _ref14.placeholder, + placeholder = _ref14$placeholder === void 0 ? false : _ref14$placeholder, + _ref14$keyCode = _ref14.keyCode, + keyCode = _ref14$keyCode === void 0 ? null : _ref14$keyCode; + + if (typeof value === 'undefined' || value === null) { + return; + } // Generate unique id + + + var choices = this._store.choices; + var choiceLabel = label || value; + var choiceId = choices ? choices.length + 1 : 1; + var choiceElementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choiceId); + + this._store.dispatch((0, _choices.addChoice)({ + value: value, + label: choiceLabel, + id: choiceId, + groupId: groupId, + disabled: isDisabled, + elementId: choiceElementId, + customProperties: customProperties, + placeholder: placeholder, + keyCode: keyCode + })); + + if (isSelected) { + this._addItem({ + value: value, + label: choiceLabel, + choiceId: choiceId, + customProperties: customProperties, + placeholder: placeholder, + keyCode: keyCode + }); + } + } + }, { + key: "_addGroup", + value: function _addGroup(_ref15) { + var _this21 = this; + + var group = _ref15.group, + id = _ref15.id, + _ref15$valueKey = _ref15.valueKey, + valueKey = _ref15$valueKey === void 0 ? 'value' : _ref15$valueKey, + _ref15$labelKey = _ref15.labelKey, + labelKey = _ref15$labelKey === void 0 ? 'label' : _ref15$labelKey; + var groupChoices = (0, _utils.isType)('Object', group) ? group.choices : Array.from(group.getElementsByTagName('OPTION')); + var groupId = id || Math.floor(new Date().valueOf() * Math.random()); + var isDisabled = group.disabled ? group.disabled : false; + + if (groupChoices) { + this._store.dispatch((0, _groups.addGroup)(group.label, groupId, true, isDisabled)); + + var addGroupChoices = function addGroupChoices(choice) { + var isOptDisabled = choice.disabled || choice.parentNode && choice.parentNode.disabled; + + _this21._addChoice({ + value: choice[valueKey], + label: (0, _utils.isType)('Object', choice) ? choice[labelKey] : choice.innerHTML, + isSelected: choice.selected, + isDisabled: isOptDisabled, + groupId: groupId, + customProperties: choice.customProperties, + placeholder: choice.placeholder + }); + }; + + groupChoices.forEach(addGroupChoices); + } else { + this._store.dispatch((0, _groups.addGroup)(group.label, group.id, false, group.disabled)); + } + } + }, { + key: "_getTemplate", + value: function _getTemplate(template) { + var _templates$template; + + if (!template) { + return null; + } + + var _this$config4 = this.config, + templates = _this$config4.templates, + classNames = _this$config4.classNames; + + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + return (_templates$template = templates[template]).call.apply(_templates$template, [this, classNames].concat(args)); + } + }, { + key: "_createTemplates", + value: function _createTemplates() { + var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; + var userTemplates = {}; + + if (callbackOnCreateTemplates && (0, _utils.isType)('Function', callbackOnCreateTemplates)) { + userTemplates = callbackOnCreateTemplates.call(this, _utils.strToEl); + } + + this.config.templates = (0, _deepmerge.default)(_templates.TEMPLATES, userTemplates); + } + }, { + key: "_createElements", + value: function _createElements() { + this.containerOuter = new _components.Container({ + element: this._getTemplate('containerOuter', this._direction, this._isSelectElement, this._isSelectOneElement, this.config.searchEnabled, this.passedElement.element.type), + classNames: this.config.classNames, + type: this.passedElement.element.type, + position: this.config.position + }); + this.containerInner = new _components.Container({ + element: this._getTemplate('containerInner'), + classNames: this.config.classNames, + type: this.passedElement.element.type, + position: this.config.position + }); + this.input = new _components.Input({ + element: this._getTemplate('input'), + classNames: this.config.classNames, + type: this.passedElement.element.type + }); + this.choiceList = new _components.List({ + element: this._getTemplate('choiceList', this._isSelectOneElement) + }); + this.itemList = new _components.List({ + element: this._getTemplate('itemList', this._isSelectOneElement) + }); + this.dropdown = new _components.Dropdown({ + element: this._getTemplate('dropdown'), + classNames: this.config.classNames, + type: this.passedElement.element.type + }); + } + }, { + key: "_createStructure", + value: function _createStructure() { + // Hide original element + this.passedElement.conceal(); // Wrap input in container preserving DOM ordering + + this.containerInner.wrap(this.passedElement.element); // Wrapper inner container with outer container + + this.containerOuter.wrap(this.containerInner.element); + + if (this._isSelectOneElement) { + this.input.placeholder = this.config.searchPlaceholderValue || ''; + } else if (this._placeholderValue) { + this.input.placeholder = this._placeholderValue; + this.input.setWidth(true); + } + + this.containerOuter.element.appendChild(this.containerInner.element); + this.containerOuter.element.appendChild(this.dropdown.element); + this.containerInner.element.appendChild(this.itemList.element); + + if (!this._isTextElement) { + this.dropdown.element.appendChild(this.choiceList.element); + } + + if (!this._isSelectOneElement) { + this.containerInner.element.appendChild(this.input.element); + } else if (this.config.searchEnabled) { + this.dropdown.element.insertBefore(this.input.element, this.dropdown.element.firstChild); + } + + if (this._isSelectElement) { + this._addPredefinedChoices(); + } else if (this._isTextElement) { + this._addPredefinedItems(); + } + } + }, { + key: "_addPredefinedChoices", + value: function _addPredefinedChoices() { + var _this22 = this; + + var passedGroups = this.passedElement.optionGroups; + this._highlightPosition = 0; + this._isSearching = false; + + this._setLoading(true); + + if (passedGroups && passedGroups.length) { + // If we have a placeholder option + var placeholderChoice = this.passedElement.placeholderOption; + + if (placeholderChoice && placeholderChoice.parentNode.tagName === 'SELECT') { + this._addChoice({ + value: placeholderChoice.value, + label: placeholderChoice.innerHTML, + isSelected: placeholderChoice.selected, + isDisabled: placeholderChoice.disabled, + placeholder: true + }); + } + + passedGroups.forEach(function (group) { + return _this22._addGroup({ + group: group, + id: group.id || null + }); + }); + } else { + var passedOptions = this.passedElement.options; + var filter = this.config.sortFn; + var allChoices = this._presetChoices; // Create array of options from option elements + + passedOptions.forEach(function (o) { + allChoices.push({ + value: o.value, + label: o.innerHTML, + selected: o.selected, + disabled: o.disabled || o.parentNode.disabled, + placeholder: o.hasAttribute('placeholder'), + customProperties: o.getAttribute('data-custom-properties') + }); + }); // If sorting is enabled or the user is searching, filter choices + + if (this.config.shouldSort) allChoices.sort(filter); // Determine whether there is a selected choice + + var hasSelectedChoice = allChoices.some(function (choice) { + return choice.selected; + }); + + var handleChoice = function handleChoice(choice, index) { + var value = choice.value, + label = choice.label, + customProperties = choice.customProperties, + placeholder = choice.placeholder; + + if (_this22._isSelectElement) { + // If the choice is actually a group + if (choice.choices) { + _this22._addGroup({ + group: choice, + id: choice.id || null + }); + } else { + // If there is a selected choice already or the choice is not + // the first in the array, add each choice normally + // Otherwise pre-select the first choice in the array if it's a single select + var shouldPreselect = _this22._isSelectOneElement && !hasSelectedChoice && index === 0; + var isSelected = shouldPreselect ? true : choice.selected; + var isDisabled = shouldPreselect ? false : choice.disabled; + + _this22._addChoice({ + value: value, + label: label, + isSelected: isSelected, + isDisabled: isDisabled, + customProperties: customProperties, + placeholder: placeholder + }); + } + } else { + _this22._addChoice({ + value: value, + label: label, + isSelected: choice.selected, + isDisabled: choice.disabled, + customProperties: customProperties, + placeholder: placeholder + }); + } + }; // Add each choice + + + allChoices.forEach(function (choice, index) { + return handleChoice(choice, index); + }); + } + + this._setLoading(false); + } + }, { + key: "_addPredefinedItems", + value: function _addPredefinedItems() { + var _this23 = this; + + var handlePresetItem = function handlePresetItem(item) { + var itemType = (0, _utils.getType)(item); + + if (itemType === 'Object' && item.value) { + _this23._addItem({ + value: item.value, + label: item.label, + choiceId: item.id, + customProperties: item.customProperties, + placeholder: item.placeholder + }); + } else if (itemType === 'String') { + _this23._addItem({ + value: item + }); + } + }; + + this._presetItems.forEach(function (item) { + return handlePresetItem(item); + }); + } + }, { + key: "_setChoiceOrItem", + value: function _setChoiceOrItem(item) { + var _this24 = this; + + var itemType = (0, _utils.getType)(item).toLowerCase(); + var handleType = { + object: function object() { + if (!item.value) { + return; + } // If we are dealing with a select input, we need to create an option first + // that is then selected. For text inputs we can just add items normally. + + + if (!_this24._isTextElement) { + _this24._addChoice({ + value: item.value, + label: item.label, + isSelected: true, + isDisabled: false, + customProperties: item.customProperties, + placeholder: item.placeholder + }); + } else { + _this24._addItem({ + value: item.value, + label: item.label, + choiceId: item.id, + customProperties: item.customProperties, + placeholder: item.placeholder + }); + } + }, + string: function string() { + if (!_this24._isTextElement) { + _this24._addChoice({ + value: item, + label: item, + isSelected: true, + isDisabled: false + }); + } else { + _this24._addItem({ + value: item + }); + } + } + }; + handleType[itemType](); + } + }, { + key: "_findAndSelectChoiceByValue", + value: function _findAndSelectChoiceByValue(val) { + var _this25 = this; + + var choices = this._store.choices; // Check 'value' property exists and the choice isn't already selected + + var foundChoice = choices.find(function (choice) { + return _this25.config.itemComparer(choice.value, val); + }); + + if (foundChoice && !foundChoice.selected) { + this._addItem({ + value: foundChoice.value, + label: foundChoice.label, + choiceId: foundChoice.id, + groupId: foundChoice.groupId, + customProperties: foundChoice.customProperties, + placeholder: foundChoice.placeholder, + keyCode: foundChoice.keyCode + }); + } + } + }, { + key: "_generateInstances", + value: function _generateInstances(elements, config) { + return elements.reduce(function (instances, element) { + instances.push(new Choices(element, config)); + return instances; + }, [this]); + } + }, { + key: "_generatePlaceholderValue", + value: function _generatePlaceholderValue() { + if (this._isSelectOneElement) { + return false; + } + + return this.config.placeholder ? this.config.placeholderValue || this.passedElement.element.getAttribute('placeholder') : false; + } + /* ===== End of Private functions ====== */ + + }]); + + return Choices; +}(); + +Choices.userDefaults = {}; // We cannot export default here due to Webpack: https://github.com/webpack/webpack/issues/3929 + +module.exports = Choices; + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +/*! + * Fuse.js v3.4.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(true) + module.exports = factory(); + else {} +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "./src/bitap/bitap_matched_indices.js": +/*!********************************************!*\ + !*** ./src/bitap/bitap_matched_indices.js ***! + \********************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +module.exports = function () { + var matchmask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var minMatchCharLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; + var matchedIndices = []; + var start = -1; + var end = -1; + var i = 0; + + for (var len = matchmask.length; i < len; i += 1) { + var match = matchmask[i]; + + if (match && start === -1) { + start = i; + } else if (!match && start !== -1) { + end = i - 1; + + if (end - start + 1 >= minMatchCharLength) { + matchedIndices.push([start, end]); + } + + start = -1; + } + } // (i-1 - start) + 1 => i - start + + + if (matchmask[i - 1] && i - start >= minMatchCharLength) { + matchedIndices.push([start, i - 1]); + } + + return matchedIndices; +}; + +/***/ }), + +/***/ "./src/bitap/bitap_pattern_alphabet.js": +/*!*********************************************!*\ + !*** ./src/bitap/bitap_pattern_alphabet.js ***! + \*********************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +module.exports = function (pattern) { + var mask = {}; + var len = pattern.length; + + for (var i = 0; i < len; i += 1) { + mask[pattern.charAt(i)] = 0; + } + + for (var _i = 0; _i < len; _i += 1) { + mask[pattern.charAt(_i)] |= 1 << len - _i - 1; + } + + return mask; +}; + +/***/ }), + +/***/ "./src/bitap/bitap_regex_search.js": +/*!*****************************************!*\ + !*** ./src/bitap/bitap_regex_search.js ***! + \*****************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +var SPECIAL_CHARS_REGEX = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g; + +module.exports = function (text, pattern) { + var tokenSeparator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : / +/g; + var regex = new RegExp(pattern.replace(SPECIAL_CHARS_REGEX, '\\$&').replace(tokenSeparator, '|')); + var matches = text.match(regex); + var isMatch = !!matches; + var matchedIndices = []; + + if (isMatch) { + for (var i = 0, matchesLen = matches.length; i < matchesLen; i += 1) { + var match = matches[i]; + matchedIndices.push([text.indexOf(match), match.length - 1]); + } + } + + return { + // TODO: revisit this score + score: isMatch ? 0.5 : 1, + isMatch: isMatch, + matchedIndices: matchedIndices + }; +}; + +/***/ }), + +/***/ "./src/bitap/bitap_score.js": +/*!**********************************!*\ + !*** ./src/bitap/bitap_score.js ***! + \**********************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +module.exports = function (pattern, _ref) { + var _ref$errors = _ref.errors, + errors = _ref$errors === void 0 ? 0 : _ref$errors, + _ref$currentLocation = _ref.currentLocation, + currentLocation = _ref$currentLocation === void 0 ? 0 : _ref$currentLocation, + _ref$expectedLocation = _ref.expectedLocation, + expectedLocation = _ref$expectedLocation === void 0 ? 0 : _ref$expectedLocation, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? 100 : _ref$distance; + var accuracy = errors / pattern.length; + var proximity = Math.abs(expectedLocation - currentLocation); + + if (!distance) { + // Dodge divide by zero error. + return proximity ? 1.0 : accuracy; + } + + return accuracy + proximity / distance; +}; + +/***/ }), + +/***/ "./src/bitap/bitap_search.js": +/*!***********************************!*\ + !*** ./src/bitap/bitap_search.js ***! + \***********************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +var bitapScore = __webpack_require__(/*! ./bitap_score */ "./src/bitap/bitap_score.js"); + +var matchedIndices = __webpack_require__(/*! ./bitap_matched_indices */ "./src/bitap/bitap_matched_indices.js"); + +module.exports = function (text, pattern, patternAlphabet, _ref) { + var _ref$location = _ref.location, + location = _ref$location === void 0 ? 0 : _ref$location, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? 100 : _ref$distance, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? 0.6 : _ref$threshold, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? false : _ref$findAllMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? 1 : _ref$minMatchCharLeng; + var expectedLocation = location; // Set starting location at beginning text and initialize the alphabet. + + var textLen = text.length; // Highest score beyond which we give up. + + var currentThreshold = threshold; // Is there a nearby exact match? (speedup) + + var bestLocation = text.indexOf(pattern, expectedLocation); + var patternLen = pattern.length; // a mask of the matches + + var matchMask = []; + + for (var i = 0; i < textLen; i += 1) { + matchMask[i] = 0; + } + + if (bestLocation !== -1) { + var score = bitapScore(pattern, { + errors: 0, + currentLocation: bestLocation, + expectedLocation: expectedLocation, + distance: distance + }); + currentThreshold = Math.min(score, currentThreshold); // What about in the other direction? (speed up) + + bestLocation = text.lastIndexOf(pattern, expectedLocation + patternLen); + + if (bestLocation !== -1) { + var _score = bitapScore(pattern, { + errors: 0, + currentLocation: bestLocation, + expectedLocation: expectedLocation, + distance: distance + }); + + currentThreshold = Math.min(_score, currentThreshold); + } + } // Reset the best location + + + bestLocation = -1; + var lastBitArr = []; + var finalScore = 1; + var binMax = patternLen + textLen; + var mask = 1 << patternLen - 1; + + for (var _i = 0; _i < patternLen; _i += 1) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from the match location we can stray + // at this error level. + var binMin = 0; + var binMid = binMax; + + while (binMin < binMid) { + var _score3 = bitapScore(pattern, { + errors: _i, + currentLocation: expectedLocation + binMid, + expectedLocation: expectedLocation, + distance: distance + }); + + if (_score3 <= currentThreshold) { + binMin = binMid; + } else { + binMax = binMid; + } + + binMid = Math.floor((binMax - binMin) / 2 + binMin); + } // Use the result from this iteration as the maximum for the next. + + + binMax = binMid; + var start = Math.max(1, expectedLocation - binMid + 1); + var finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array + + var bitArr = Array(finish + 2); + bitArr[finish + 1] = (1 << _i) - 1; + + for (var j = finish; j >= start; j -= 1) { + var currentLocation = j - 1; + var charMatch = patternAlphabet[text.charAt(currentLocation)]; + + if (charMatch) { + matchMask[currentLocation] = 1; + } // First pass: exact match + + + bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match + + if (_i !== 0) { + bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; + } + + if (bitArr[j] & mask) { + finalScore = bitapScore(pattern, { + errors: _i, + currentLocation: currentLocation, + expectedLocation: expectedLocation, + distance: distance + }); // This match will almost certainly be better than any existing match. + // But check anyway. + + if (finalScore <= currentThreshold) { + // Indeed it is + currentThreshold = finalScore; + bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. + + if (bestLocation <= expectedLocation) { + break; + } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. + + + start = Math.max(1, 2 * expectedLocation - bestLocation); + } + } + } // No hope for a (better) match at greater error levels. + + + var _score2 = bitapScore(pattern, { + errors: _i + 1, + currentLocation: expectedLocation, + expectedLocation: expectedLocation, + distance: distance + }); // console.log('score', score, finalScore) + + + if (_score2 > currentThreshold) { + break; + } + + lastBitArr = bitArr; + } // console.log('FINAL SCORE', finalScore) + // Count exact matches (those with a score of 0) to be "almost" exact + + + return { + isMatch: bestLocation >= 0, + score: finalScore === 0 ? 0.001 : finalScore, + matchedIndices: matchedIndices(matchMask, minMatchCharLength) + }; +}; + +/***/ }), + +/***/ "./src/bitap/index.js": +/*!****************************!*\ + !*** ./src/bitap/index.js ***! + \****************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var bitapRegexSearch = __webpack_require__(/*! ./bitap_regex_search */ "./src/bitap/bitap_regex_search.js"); + +var bitapSearch = __webpack_require__(/*! ./bitap_search */ "./src/bitap/bitap_search.js"); + +var patternAlphabet = __webpack_require__(/*! ./bitap_pattern_alphabet */ "./src/bitap/bitap_pattern_alphabet.js"); + +var Bitap = +/*#__PURE__*/ +function () { + function Bitap(pattern, _ref) { + var _ref$location = _ref.location, + location = _ref$location === void 0 ? 0 : _ref$location, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? 100 : _ref$distance, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? 0.6 : _ref$threshold, + _ref$maxPatternLength = _ref.maxPatternLength, + maxPatternLength = _ref$maxPatternLength === void 0 ? 32 : _ref$maxPatternLength, + _ref$isCaseSensitive = _ref.isCaseSensitive, + isCaseSensitive = _ref$isCaseSensitive === void 0 ? false : _ref$isCaseSensitive, + _ref$tokenSeparator = _ref.tokenSeparator, + tokenSeparator = _ref$tokenSeparator === void 0 ? / +/g : _ref$tokenSeparator, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? false : _ref$findAllMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? 1 : _ref$minMatchCharLeng; + + _classCallCheck(this, Bitap); + + this.options = { + location: location, + distance: distance, + threshold: threshold, + maxPatternLength: maxPatternLength, + isCaseSensitive: isCaseSensitive, + tokenSeparator: tokenSeparator, + findAllMatches: findAllMatches, + minMatchCharLength: minMatchCharLength + }; + this.pattern = this.options.isCaseSensitive ? pattern : pattern.toLowerCase(); + + if (this.pattern.length <= maxPatternLength) { + this.patternAlphabet = patternAlphabet(this.pattern); + } + } + + _createClass(Bitap, [{ + key: "search", + value: function search(text) { + if (!this.options.isCaseSensitive) { + text = text.toLowerCase(); + } // Exact match + + + if (this.pattern === text) { + return { + isMatch: true, + score: 0, + matchedIndices: [[0, text.length - 1]] + }; + } // When pattern length is greater than the machine word length, just do a a regex comparison + + + var _this$options = this.options, + maxPatternLength = _this$options.maxPatternLength, + tokenSeparator = _this$options.tokenSeparator; + + if (this.pattern.length > maxPatternLength) { + return bitapRegexSearch(text, this.pattern, tokenSeparator); + } // Otherwise, use Bitap algorithm + + + var _this$options2 = this.options, + location = _this$options2.location, + distance = _this$options2.distance, + threshold = _this$options2.threshold, + findAllMatches = _this$options2.findAllMatches, + minMatchCharLength = _this$options2.minMatchCharLength; + return bitapSearch(text, this.pattern, this.patternAlphabet, { + location: location, + distance: distance, + threshold: threshold, + findAllMatches: findAllMatches, + minMatchCharLength: minMatchCharLength + }); + } + }]); + + return Bitap; +}(); // let x = new Bitap("od mn war", {}) +// let result = x.search("Old Man's War") +// console.log(result) + + +module.exports = Bitap; + +/***/ }), + +/***/ "./src/helpers/deep_value.js": +/*!***********************************!*\ + !*** ./src/helpers/deep_value.js ***! + \***********************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +var isArray = __webpack_require__(/*! ./is_array */ "./src/helpers/is_array.js"); + +var deepValue = function deepValue(obj, path, list) { + if (!path) { + // If there's no path left, we've gotten to the object we care about. + list.push(obj); + } else { + var dotIndex = path.indexOf('.'); + var firstSegment = path; + var remaining = null; + + if (dotIndex !== -1) { + firstSegment = path.slice(0, dotIndex); + remaining = path.slice(dotIndex + 1); + } + + var value = obj[firstSegment]; + + if (value !== null && value !== undefined) { + if (!remaining && (typeof value === 'string' || typeof value === 'number')) { + list.push(value.toString()); + } else if (isArray(value)) { + // Search each item in the array. + for (var i = 0, len = value.length; i < len; i += 1) { + deepValue(value[i], remaining, list); + } + } else if (remaining) { + // An object. Recurse further. + deepValue(value, remaining, list); + } + } + } + + return list; +}; + +module.exports = function (obj, path) { + return deepValue(obj, path, []); +}; + +/***/ }), + +/***/ "./src/helpers/is_array.js": +/*!*********************************!*\ + !*** ./src/helpers/is_array.js ***! + \*********************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +module.exports = function (obj) { + return !Array.isArray ? Object.prototype.toString.call(obj) === '[object Array]' : Array.isArray(obj); +}; + +/***/ }), + +/***/ "./src/index.js": +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var Bitap = __webpack_require__(/*! ./bitap */ "./src/bitap/index.js"); + +var deepValue = __webpack_require__(/*! ./helpers/deep_value */ "./src/helpers/deep_value.js"); + +var isArray = __webpack_require__(/*! ./helpers/is_array */ "./src/helpers/is_array.js"); + +var Fuse = +/*#__PURE__*/ +function () { + function Fuse(list, _ref) { + var _ref$location = _ref.location, + location = _ref$location === void 0 ? 0 : _ref$location, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? 100 : _ref$distance, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? 0.6 : _ref$threshold, + _ref$maxPatternLength = _ref.maxPatternLength, + maxPatternLength = _ref$maxPatternLength === void 0 ? 32 : _ref$maxPatternLength, + _ref$caseSensitive = _ref.caseSensitive, + caseSensitive = _ref$caseSensitive === void 0 ? false : _ref$caseSensitive, + _ref$tokenSeparator = _ref.tokenSeparator, + tokenSeparator = _ref$tokenSeparator === void 0 ? / +/g : _ref$tokenSeparator, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? false : _ref$findAllMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? 1 : _ref$minMatchCharLeng, + _ref$id = _ref.id, + id = _ref$id === void 0 ? null : _ref$id, + _ref$keys = _ref.keys, + keys = _ref$keys === void 0 ? [] : _ref$keys, + _ref$shouldSort = _ref.shouldSort, + shouldSort = _ref$shouldSort === void 0 ? true : _ref$shouldSort, + _ref$getFn = _ref.getFn, + getFn = _ref$getFn === void 0 ? deepValue : _ref$getFn, + _ref$sortFn = _ref.sortFn, + sortFn = _ref$sortFn === void 0 ? function (a, b) { + return a.score - b.score; + } : _ref$sortFn, + _ref$tokenize = _ref.tokenize, + tokenize = _ref$tokenize === void 0 ? false : _ref$tokenize, + _ref$matchAllTokens = _ref.matchAllTokens, + matchAllTokens = _ref$matchAllTokens === void 0 ? false : _ref$matchAllTokens, + _ref$includeMatches = _ref.includeMatches, + includeMatches = _ref$includeMatches === void 0 ? false : _ref$includeMatches, + _ref$includeScore = _ref.includeScore, + includeScore = _ref$includeScore === void 0 ? false : _ref$includeScore, + _ref$verbose = _ref.verbose, + verbose = _ref$verbose === void 0 ? false : _ref$verbose; + + _classCallCheck(this, Fuse); + + this.options = { + location: location, + distance: distance, + threshold: threshold, + maxPatternLength: maxPatternLength, + isCaseSensitive: caseSensitive, + tokenSeparator: tokenSeparator, + findAllMatches: findAllMatches, + minMatchCharLength: minMatchCharLength, + id: id, + keys: keys, + includeMatches: includeMatches, + includeScore: includeScore, + shouldSort: shouldSort, + getFn: getFn, + sortFn: sortFn, + verbose: verbose, + tokenize: tokenize, + matchAllTokens: matchAllTokens + }; + this.setCollection(list); + } + + _createClass(Fuse, [{ + key: "setCollection", + value: function setCollection(list) { + this.list = list; + return list; + } + }, { + key: "search", + value: function search(pattern) { + var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + limit: false + }; + + this._log("---------\nSearch pattern: \"".concat(pattern, "\"")); + + var _this$_prepareSearche = this._prepareSearchers(pattern), + tokenSearchers = _this$_prepareSearche.tokenSearchers, + fullSearcher = _this$_prepareSearche.fullSearcher; + + var _this$_search = this._search(tokenSearchers, fullSearcher), + weights = _this$_search.weights, + results = _this$_search.results; + + this._computeScore(weights, results); + + if (this.options.shouldSort) { + this._sort(results); + } + + if (opts.limit && typeof opts.limit === 'number') { + results = results.slice(0, opts.limit); + } + + return this._format(results); + } + }, { + key: "_prepareSearchers", + value: function _prepareSearchers() { + var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var tokenSearchers = []; + + if (this.options.tokenize) { + // Tokenize on the separator + var tokens = pattern.split(this.options.tokenSeparator); + + for (var i = 0, len = tokens.length; i < len; i += 1) { + tokenSearchers.push(new Bitap(tokens[i], this.options)); + } + } + + var fullSearcher = new Bitap(pattern, this.options); + return { + tokenSearchers: tokenSearchers, + fullSearcher: fullSearcher + }; + } + }, { + key: "_search", + value: function _search() { + var tokenSearchers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var fullSearcher = arguments.length > 1 ? arguments[1] : undefined; + var list = this.list; + var resultMap = {}; + var results = []; // Check the first item in the list, if it's a string, then we assume + // that every item in the list is also a string, and thus it's a flattened array. + + if (typeof list[0] === 'string') { + // Iterate over every item + for (var i = 0, len = list.length; i < len; i += 1) { + this._analyze({ + key: '', + value: list[i], + record: i, + index: i + }, { + resultMap: resultMap, + results: results, + tokenSearchers: tokenSearchers, + fullSearcher: fullSearcher + }); + } + + return { + weights: null, + results: results + }; + } // Otherwise, the first item is an Object (hopefully), and thus the searching + // is done on the values of the keys of each item. + + + var weights = {}; + + for (var _i = 0, _len = list.length; _i < _len; _i += 1) { + var item = list[_i]; // Iterate over every key + + for (var j = 0, keysLen = this.options.keys.length; j < keysLen; j += 1) { + var key = this.options.keys[j]; + + if (typeof key !== 'string') { + weights[key.name] = { + weight: 1 - key.weight || 1 + }; + + if (key.weight <= 0 || key.weight > 1) { + throw new Error('Key weight has to be > 0 and <= 1'); + } + + key = key.name; + } else { + weights[key] = { + weight: 1 + }; + } + + this._analyze({ + key: key, + value: this.options.getFn(item, key), + record: item, + index: _i + }, { + resultMap: resultMap, + results: results, + tokenSearchers: tokenSearchers, + fullSearcher: fullSearcher + }); + } + } + + return { + weights: weights, + results: results + }; + } + }, { + key: "_analyze", + value: function _analyze(_ref2, _ref3) { + var key = _ref2.key, + _ref2$arrayIndex = _ref2.arrayIndex, + arrayIndex = _ref2$arrayIndex === void 0 ? -1 : _ref2$arrayIndex, + value = _ref2.value, + record = _ref2.record, + index = _ref2.index; + var _ref3$tokenSearchers = _ref3.tokenSearchers, + tokenSearchers = _ref3$tokenSearchers === void 0 ? [] : _ref3$tokenSearchers, + _ref3$fullSearcher = _ref3.fullSearcher, + fullSearcher = _ref3$fullSearcher === void 0 ? [] : _ref3$fullSearcher, + _ref3$resultMap = _ref3.resultMap, + resultMap = _ref3$resultMap === void 0 ? {} : _ref3$resultMap, + _ref3$results = _ref3.results, + results = _ref3$results === void 0 ? [] : _ref3$results; + + // Check if the texvaluet can be searched + if (value === undefined || value === null) { + return; + } + + var exists = false; + var averageScore = -1; + var numTextMatches = 0; + + if (typeof value === 'string') { + this._log("\nKey: ".concat(key === '' ? '-' : key)); + + var mainSearchResult = fullSearcher.search(value); + + this._log("Full text: \"".concat(value, "\", score: ").concat(mainSearchResult.score)); + + if (this.options.tokenize) { + var words = value.split(this.options.tokenSeparator); + var scores = []; + + for (var i = 0; i < tokenSearchers.length; i += 1) { + var tokenSearcher = tokenSearchers[i]; + + this._log("\nPattern: \"".concat(tokenSearcher.pattern, "\"")); // let tokenScores = [] + + + var hasMatchInText = false; + + for (var j = 0; j < words.length; j += 1) { + var word = words[j]; + var tokenSearchResult = tokenSearcher.search(word); + var obj = {}; + + if (tokenSearchResult.isMatch) { + obj[word] = tokenSearchResult.score; + exists = true; + hasMatchInText = true; + scores.push(tokenSearchResult.score); + } else { + obj[word] = 1; + + if (!this.options.matchAllTokens) { + scores.push(1); + } + } + + this._log("Token: \"".concat(word, "\", score: ").concat(obj[word])); // tokenScores.push(obj) + + } + + if (hasMatchInText) { + numTextMatches += 1; + } + } + + averageScore = scores[0]; + var scoresLen = scores.length; + + for (var _i2 = 1; _i2 < scoresLen; _i2 += 1) { + averageScore += scores[_i2]; + } + + averageScore = averageScore / scoresLen; + + this._log('Token score average:', averageScore); + } + + var finalScore = mainSearchResult.score; + + if (averageScore > -1) { + finalScore = (finalScore + averageScore) / 2; + } + + this._log('Score average:', finalScore); + + var checkTextMatches = this.options.tokenize && this.options.matchAllTokens ? numTextMatches >= tokenSearchers.length : true; + + this._log("\nCheck Matches: ".concat(checkTextMatches)); // If a match is found, add the item to , including its score + + + if ((exists || mainSearchResult.isMatch) && checkTextMatches) { + // Check if the item already exists in our results + var existingResult = resultMap[index]; + + if (existingResult) { + // Use the lowest score + // existingResult.score, bitapResult.score + existingResult.output.push({ + key: key, + arrayIndex: arrayIndex, + value: value, + score: finalScore, + matchedIndices: mainSearchResult.matchedIndices + }); + } else { + // Add it to the raw result list + resultMap[index] = { + item: record, + output: [{ + key: key, + arrayIndex: arrayIndex, + value: value, + score: finalScore, + matchedIndices: mainSearchResult.matchedIndices + }] + }; + results.push(resultMap[index]); + } + } + } else if (isArray(value)) { + for (var _i3 = 0, len = value.length; _i3 < len; _i3 += 1) { + this._analyze({ + key: key, + arrayIndex: _i3, + value: value[_i3], + record: record, + index: index + }, { + resultMap: resultMap, + results: results, + tokenSearchers: tokenSearchers, + fullSearcher: fullSearcher + }); + } + } + } + }, { + key: "_computeScore", + value: function _computeScore(weights, results) { + this._log('\n\nComputing score:\n'); + + for (var i = 0, len = results.length; i < len; i += 1) { + var output = results[i].output; + var scoreLen = output.length; + var currScore = 1; + var bestScore = 1; + + for (var j = 0; j < scoreLen; j += 1) { + var weight = weights ? weights[output[j].key].weight : 1; + var score = weight === 1 ? output[j].score : output[j].score || 0.001; + var nScore = score * weight; + + if (weight !== 1) { + bestScore = Math.min(bestScore, nScore); + } else { + output[j].nScore = nScore; + currScore *= nScore; + } + } + + results[i].score = bestScore === 1 ? currScore : bestScore; + + this._log(results[i]); + } + } + }, { + key: "_sort", + value: function _sort(results) { + this._log('\n\nSorting....'); + + results.sort(this.options.sortFn); + } + }, { + key: "_format", + value: function _format(results) { + var finalOutput = []; + + if (this.options.verbose) { + var cache = []; + + this._log('\n\nOutput:\n\n', JSON.stringify(results, function (key, value) { + if (_typeof(value) === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) { + // Circular reference found, discard key + return; + } // Store value in our collection + + + cache.push(value); + } + + return value; + })); + + cache = null; + } + + var transformers = []; + + if (this.options.includeMatches) { + transformers.push(function (result, data) { + var output = result.output; + data.matches = []; + + for (var i = 0, len = output.length; i < len; i += 1) { + var item = output[i]; + + if (item.matchedIndices.length === 0) { + continue; + } + + var obj = { + indices: item.matchedIndices, + value: item.value + }; + + if (item.key) { + obj.key = item.key; + } + + if (item.hasOwnProperty('arrayIndex') && item.arrayIndex > -1) { + obj.arrayIndex = item.arrayIndex; + } + + data.matches.push(obj); + } + }); + } + + if (this.options.includeScore) { + transformers.push(function (result, data) { + data.score = result.score; + }); + } + + for (var i = 0, len = results.length; i < len; i += 1) { + var result = results[i]; + + if (this.options.id) { + result.item = this.options.getFn(result.item, this.options.id)[0]; + } + + if (!transformers.length) { + finalOutput.push(result.item); + continue; + } + + var data = { + item: result.item + }; + + for (var j = 0, _len2 = transformers.length; j < _len2; j += 1) { + transformers[j](result, data); + } + + finalOutput.push(data); + } + + return finalOutput; + } + }, { + key: "_log", + value: function _log() { + if (this.options.verbose) { + var _console; + + (_console = console).log.apply(_console, arguments); + } + } + }]); + + return Fuse; +}(); + +module.exports = Fuse; + +/***/ }) + +/******/ }); +}); + +/***/ }), +/* 12 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +var isMergeableObject = function isMergeableObject(value) { + return isNonNullObject(value) + && !isSpecial(value) +}; + +function isNonNullObject(value) { + return !!value && typeof value === 'object' +} + +function isSpecial(value) { + var stringValue = Object.prototype.toString.call(value); + + return stringValue === '[object RegExp]' + || stringValue === '[object Date]' + || isReactElement(value) +} + +// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25 +var canUseSymbol = typeof Symbol === 'function' && Symbol.for; +var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7; + +function isReactElement(value) { + return value.$$typeof === REACT_ELEMENT_TYPE +} + +function emptyTarget(val) { + return Array.isArray(val) ? [] : {} +} + +function cloneUnlessOtherwiseSpecified(value, options) { + return (options.clone !== false && options.isMergeableObject(value)) + ? deepmerge(emptyTarget(value), value, options) + : value +} + +function defaultArrayMerge(target, source, options) { + return target.concat(source).map(function(element) { + return cloneUnlessOtherwiseSpecified(element, options) + }) +} + +function mergeObject(target, source, options) { + var destination = {}; + if (options.isMergeableObject(target)) { + Object.keys(target).forEach(function(key) { + destination[key] = cloneUnlessOtherwiseSpecified(target[key], options); + }); + } + Object.keys(source).forEach(function(key) { + if (!options.isMergeableObject(source[key]) || !target[key]) { + destination[key] = cloneUnlessOtherwiseSpecified(source[key], options); + } else { + destination[key] = deepmerge(target[key], source[key], options); + } + }); + return destination +} + +function deepmerge(target, source, options) { + options = options || {}; + options.arrayMerge = options.arrayMerge || defaultArrayMerge; + options.isMergeableObject = options.isMergeableObject || isMergeableObject; + + var sourceIsArray = Array.isArray(source); + var targetIsArray = Array.isArray(target); + var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray; + + if (!sourceAndTargetTypesMatch) { + return cloneUnlessOtherwiseSpecified(source, options) + } else if (sourceIsArray) { + return options.arrayMerge(target, source, options) + } else { + return mergeObject(target, source, options) + } +} + +deepmerge.all = function deepmergeAll(array, options) { + if (!Array.isArray(array)) { + throw new Error('first argument should be an array') + } + + return array.reduce(function(prev, next) { + return deepmerge(prev, next, options) + }, {}) +}; + +var deepmerge_1 = deepmerge; + +/* harmony default export */ __webpack_exports__["default"] = (deepmerge_1); + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _redux = __webpack_require__(6); + +var _index = _interopRequireDefault(__webpack_require__(15)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var Store = +/*#__PURE__*/ +function () { + function Store() { + _classCallCheck(this, Store); + + this._store = (0, _redux.createStore)(_index.default, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); + } + /** + * Subscribe store to function call (wrapped Redux method) + * @param {Function} onChange Function to trigger when state changes + * @return + */ + + + _createClass(Store, [{ + key: "subscribe", + value: function subscribe(onChange) { + this._store.subscribe(onChange); + } + /** + * Dispatch event to store (wrapped Redux method) + * @param {Function} action Action function to trigger + * @return + */ + + }, { + key: "dispatch", + value: function dispatch(action) { + this._store.dispatch(action); + } + /** + * Get store object (wrapping Redux method) + * @return {Object} State + */ + + }, { + key: "isLoading", + + /** + * Get loading state from store + * @return {Boolean} Loading State + */ + value: function isLoading() { + return this.state.general.loading; + } + /** + * Get single choice by it's ID + * @return {Object} Found choice + */ + + }, { + key: "getChoiceById", + value: function getChoiceById(id) { + if (id) { + var choices = this.activeChoices; + var foundChoice = choices.find(function (choice) { + return choice.id === parseInt(id, 10); + }); + return foundChoice; + } + + return false; + } + /** + * Get group by group id + * @param {Number} id Group ID + * @return {Object} Group data + */ + + }, { + key: "getGroupById", + value: function getGroupById(id) { + return this.groups.find(function (group) { + return group.id === parseInt(id, 10); + }); + } + }, { + key: "state", + get: function get() { + return this._store.getState(); + } + /** + * Get items from store + * @return {Array} Item objects + */ + + }, { + key: "items", + get: function get() { + return this.state.items; + } + /** + * Get active items from store + * @return {Array} Item objects + */ + + }, { + key: "activeItems", + get: function get() { + return this.items.filter(function (item) { + return item.active === true; + }); + } + /** + * Get highlighted items from store + * @return {Array} Item objects + */ + + }, { + key: "highlightedActiveItems", + get: function get() { + return this.items.filter(function (item) { + return item.active && item.highlighted; + }); + } + /** + * Get choices from store + * @return {Array} Option objects + */ + + }, { + key: "choices", + get: function get() { + return this.state.choices; + } + /** + * Get active choices from store + * @return {Array} Option objects + */ + + }, { + key: "activeChoices", + get: function get() { + var choices = this.choices; + var values = choices.filter(function (choice) { + return choice.active === true; + }); + return values; + } + /** + * Get selectable choices from store + * @return {Array} Option objects + */ + + }, { + key: "selectableChoices", + get: function get() { + return this.choices.filter(function (choice) { + return choice.disabled !== true; + }); + } + /** + * Get choices that can be searched (excluding placeholders) + * @return {Array} Option objects + */ + + }, { + key: "searchableChoices", + get: function get() { + return this.selectableChoices.filter(function (choice) { + return choice.placeholder !== true; + }); + } + /** + * Get placeholder choice from store + * @return {Object} Found placeholder + */ + + }, { + key: "placeholderChoice", + get: function get() { + return [].concat(this.choices).reverse().find(function (choice) { + return choice.placeholder === true; + }); + } + /** + * Get groups from store + * @return {Array} Group objects + */ + + }, { + key: "groups", + get: function get() { + return this.state.groups; + } + /** + * Get active groups from store + * @return {Array} Group objects + */ + + }, { + key: "activeGroups", + get: function get() { + var groups = this.groups; + var choices = this.choices; + return groups.filter(function (group) { + var isActive = group.active === true && group.disabled === false; + var hasActiveOptions = choices.some(function (choice) { + return choice.active === true && choice.disabled === false; + }); + return isActive && hasActiveOptions; + }, []); + } + }]); + + return Store; +}(); + +exports.default = Store; + +/***/ }), +/* 14 */ +/***/ (function(module, exports) { + +module.exports = function(originalModule) { + if (!originalModule.webpackPolyfill) { + var module = Object.create(originalModule); + // module.parent = undefined by default + if (!module.children) module.children = []; + Object.defineProperty(module, "loaded", { + enumerable: true, + get: function() { + return module.l; + } + }); + Object.defineProperty(module, "id", { + enumerable: true, + get: function() { + return module.i; + } + }); + Object.defineProperty(module, "exports", { + enumerable: true + }); + module.webpackPolyfill = 1; + } + return module; +}; + + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _redux = __webpack_require__(6); + +var _items = _interopRequireDefault(__webpack_require__(16)); + +var _groups = _interopRequireDefault(__webpack_require__(17)); + +var _choices = _interopRequireDefault(__webpack_require__(18)); + +var _general = _interopRequireDefault(__webpack_require__(19)); + +var _utils = __webpack_require__(0); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var appReducer = (0, _redux.combineReducers)({ + items: _items.default, + groups: _groups.default, + choices: _choices.default, + general: _general.default +}); + +var rootReducer = function rootReducer(passedState, action) { + var state = passedState; // If we are clearing all items, groups and options we reassign + // state and then pass that state to our proper reducer. This isn't + // mutating our actual state + // See: http://stackoverflow.com/a/35641992 + + if (action.type === 'CLEAR_ALL') { + state = undefined; + } else if (action.type === 'RESET_TO') { + return (0, _utils.cloneObject)(action.state); + } + + return appReducer(state, action); +}; + +var _default = rootReducer; +exports.default = _default; + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = items; +exports.defaultState = void 0; +var defaultState = []; +exports.defaultState = defaultState; + +function items() { + var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultState; + var action = arguments.length > 1 ? arguments[1] : undefined; + + switch (action.type) { + case 'ADD_ITEM': + { + // Add object to items array + var newState = [].concat(state, [{ + id: action.id, + choiceId: action.choiceId, + groupId: action.groupId, + value: action.value, + label: action.label, + active: true, + highlighted: false, + customProperties: action.customProperties, + placeholder: action.placeholder || false, + keyCode: null + }]); + return newState.map(function (obj) { + var item = obj; + item.highlighted = false; + return item; + }); + } + + case 'REMOVE_ITEM': + { + // Set item to inactive + return state.map(function (obj) { + var item = obj; + + if (item.id === action.id) { + item.active = false; + } + + return item; + }); + } + + case 'HIGHLIGHT_ITEM': + { + return state.map(function (obj) { + var item = obj; + + if (item.id === action.id) { + item.highlighted = action.highlighted; + } + + return item; + }); + } + + default: + { + return state; + } + } +} + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = groups; +exports.defaultState = void 0; +var defaultState = []; +exports.defaultState = defaultState; + +function groups() { + var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultState; + var action = arguments.length > 1 ? arguments[1] : undefined; + + switch (action.type) { + case 'ADD_GROUP': + { + return [].concat(state, [{ + id: action.id, + value: action.value, + active: action.active, + disabled: action.disabled + }]); + } + + case 'CLEAR_CHOICES': + { + return []; + } + + default: + { + return state; + } + } +} + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = choices; +exports.defaultState = void 0; +var defaultState = []; +exports.defaultState = defaultState; + +function choices() { + var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultState; + var action = arguments.length > 1 ? arguments[1] : undefined; + + switch (action.type) { + case 'ADD_CHOICE': + { + /* + A disabled choice appears in the choice dropdown but cannot be selected + A selected choice has been added to the passed input's value (added as an item) + An active choice appears within the choice dropdown + */ + return [].concat(state, [{ + id: action.id, + elementId: action.elementId, + groupId: action.groupId, + value: action.value, + label: action.label || action.value, + disabled: action.disabled || false, + selected: false, + active: true, + score: 9999, + customProperties: action.customProperties, + placeholder: action.placeholder || false, + keyCode: null + }]); + } + + case 'ADD_ITEM': + { + // If all choices need to be activated + if (action.activateOptions) { + return state.map(function (obj) { + var choice = obj; + choice.active = action.active; + return choice; + }); + } // When an item is added and it has an associated choice, + // we want to disable it so it can't be chosen again + + + if (action.choiceId > -1) { + return state.map(function (obj) { + var choice = obj; + + if (choice.id === parseInt(action.choiceId, 10)) { + choice.selected = true; + } + + return choice; + }); + } + + return state; + } + + case 'REMOVE_ITEM': + { + // When an item is removed and it has an associated choice, + // we want to re-enable it so it can be chosen again + if (action.choiceId > -1) { + return state.map(function (obj) { + var choice = obj; + + if (choice.id === parseInt(action.choiceId, 10)) { + choice.selected = false; + } + + return choice; + }); + } + + return state; + } + + case 'FILTER_CHOICES': + { + return state.map(function (obj) { + var choice = obj; // Set active state based on whether choice is + // within filtered results + + choice.active = action.results.some(function (_ref) { + var item = _ref.item, + score = _ref.score; + + if (item.id === choice.id) { + choice.score = score; + return true; + } + + return false; + }); + return choice; + }); + } + + case 'ACTIVATE_CHOICES': + { + return state.map(function (obj) { + var choice = obj; + choice.active = action.active; + return choice; + }); + } + + case 'CLEAR_CHOICES': + { + return defaultState; + } + + default: + { + return state; + } + } +} + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = exports.defaultState = void 0; +var defaultState = { + loading: false +}; +exports.defaultState = defaultState; + +var general = function general() { + var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultState; + var action = arguments.length > 1 ? arguments[1] : undefined; + + switch (action.type) { + case 'SET_IS_LOADING': + { + return { + loading: action.isLoading + }; + } + + default: + { + return state; + } + } +}; + +var _default = general; +exports.default = _default; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "Dropdown", { + enumerable: true, + get: function get() { + return _dropdown.default; + } +}); +Object.defineProperty(exports, "Container", { + enumerable: true, + get: function get() { + return _container.default; + } +}); +Object.defineProperty(exports, "Input", { + enumerable: true, + get: function get() { + return _input.default; + } +}); +Object.defineProperty(exports, "List", { + enumerable: true, + get: function get() { + return _list.default; + } +}); +Object.defineProperty(exports, "WrappedInput", { + enumerable: true, + get: function get() { + return _wrappedInput.default; + } +}); +Object.defineProperty(exports, "WrappedSelect", { + enumerable: true, + get: function get() { + return _wrappedSelect.default; + } +}); + +var _dropdown = _interopRequireDefault(__webpack_require__(21)); + +var _container = _interopRequireDefault(__webpack_require__(22)); + +var _input = _interopRequireDefault(__webpack_require__(23)); + +var _list = _interopRequireDefault(__webpack_require__(24)); + +var _wrappedInput = _interopRequireDefault(__webpack_require__(25)); + +var _wrappedSelect = _interopRequireDefault(__webpack_require__(26)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var Dropdown = +/*#__PURE__*/ +function () { + function Dropdown(_ref) { + var element = _ref.element, + type = _ref.type, + classNames = _ref.classNames; + + _classCallCheck(this, Dropdown); + + Object.assign(this, { + element: element, + type: type, + classNames: classNames + }); + this.isActive = false; + } + /** + * Determine how far the top of our element is from + * the top of the window + * @return {Number} Vertical position + */ + + + _createClass(Dropdown, [{ + key: "distanceFromTopWindow", + value: function distanceFromTopWindow() { + this.dimensions = this.element.getBoundingClientRect(); + this.position = Math.ceil(this.dimensions.top + window.pageYOffset + this.element.offsetHeight); + return this.position; + } + /** + * Find element that matches passed selector + * @return {HTMLElement} + */ + + }, { + key: "getChild", + value: function getChild(selector) { + return this.element.querySelector(selector); + } + /** + * Show dropdown to user by adding active state class + * @return {Object} Class instance + * @public + */ + + }, { + key: "show", + value: function show() { + this.element.classList.add(this.classNames.activeState); + this.element.setAttribute('aria-expanded', 'true'); + this.isActive = true; + return this; + } + /** + * Hide dropdown from user + * @return {Object} Class instance + * @public + */ + + }, { + key: "hide", + value: function hide() { + this.element.classList.remove(this.classNames.activeState); + this.element.setAttribute('aria-expanded', 'false'); + this.isActive = false; + return this; + } + }]); + + return Dropdown; +}(); + +exports.default = Dropdown; + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _utils = __webpack_require__(0); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var Container = +/*#__PURE__*/ +function () { + function Container(_ref) { + var element = _ref.element, + type = _ref.type, + classNames = _ref.classNames, + position = _ref.position; + + _classCallCheck(this, Container); + + Object.assign(this, { + element: element, + classNames: classNames, + type: type, + position: position + }); + this.isOpen = false; + this.isFlipped = false; + this.isFocussed = false; + this.isDisabled = false; + this.isLoading = false; + this._onFocus = this._onFocus.bind(this); + this._onBlur = this._onBlur.bind(this); + } + /** + * Add event listeners + */ + + + _createClass(Container, [{ + key: "addEventListeners", + value: function addEventListeners() { + this.element.addEventListener('focus', this._onFocus); + this.element.addEventListener('blur', this._onBlur); + } + /** + * Remove event listeners + */ + + /** */ + + }, { + key: "removeEventListeners", + value: function removeEventListeners() { + this.element.removeEventListener('focus', this._onFocus); + this.element.removeEventListener('blur', this._onBlur); + } + /** + * Determine whether container should be flipped + * based on passed dropdown position + * @param {Number} dropdownPos + * @returns + */ + + }, { + key: "shouldFlip", + value: function shouldFlip(dropdownPos) { + var windowHeight = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : (0, _utils.getWindowHeight)(); + + if (dropdownPos === undefined) { + return false; + } // If flip is enabled and the dropdown bottom position is + // greater than the window height flip the dropdown. + + + var shouldFlip = false; + + if (this.position === 'auto') { + shouldFlip = dropdownPos >= windowHeight; + } else if (this.position === 'top') { + shouldFlip = true; + } + + return shouldFlip; + } + /** + * Set active descendant attribute + * @param {Number} activeDescendant ID of active descendant + */ + + }, { + key: "setActiveDescendant", + value: function setActiveDescendant(activeDescendantID) { + this.element.setAttribute('aria-activedescendant', activeDescendantID); + } + /** + * Remove active descendant attribute + */ + + }, { + key: "removeActiveDescendant", + value: function removeActiveDescendant() { + this.element.removeAttribute('aria-activedescendant'); + } + }, { + key: "open", + value: function open(dropdownPos) { + this.element.classList.add(this.classNames.openState); + this.element.setAttribute('aria-expanded', 'true'); + this.isOpen = true; + + if (this.shouldFlip(dropdownPos)) { + this.element.classList.add(this.classNames.flippedState); + this.isFlipped = true; + } + } + }, { + key: "close", + value: function close() { + this.element.classList.remove(this.classNames.openState); + this.element.setAttribute('aria-expanded', 'false'); + this.removeActiveDescendant(); + this.isOpen = false; // A dropdown flips if it does not have space within the page + + if (this.isFlipped) { + this.element.classList.remove(this.classNames.flippedState); + this.isFlipped = false; + } + } + }, { + key: "focus", + value: function focus() { + if (!this.isFocussed) { + this.element.focus(); + } + } + }, { + key: "addFocusState", + value: function addFocusState() { + this.element.classList.add(this.classNames.focusState); + } + }, { + key: "removeFocusState", + value: function removeFocusState() { + this.element.classList.remove(this.classNames.focusState); + } + /** + * Remove disabled state + */ + + }, { + key: "enable", + value: function enable() { + this.element.classList.remove(this.classNames.disabledState); + this.element.removeAttribute('aria-disabled'); + + if (this.type === 'select-one') { + this.element.setAttribute('tabindex', '0'); + } + + this.isDisabled = false; + } + /** + * Set disabled state + */ + + }, { + key: "disable", + value: function disable() { + this.element.classList.add(this.classNames.disabledState); + this.element.setAttribute('aria-disabled', 'true'); + + if (this.type === 'select-one') { + this.element.setAttribute('tabindex', '-1'); + } + + this.isDisabled = true; + } + }, { + key: "wrap", + value: function wrap(element) { + (0, _utils.wrap)(element, this.element); + } + }, { + key: "unwrap", + value: function unwrap(element) { + // Move passed element outside this element + this.element.parentNode.insertBefore(element, this.element); // Remove this element + + this.element.parentNode.removeChild(this.element); + } + /** + * Add loading state to element + */ + + }, { + key: "addLoadingState", + value: function addLoadingState() { + this.element.classList.add(this.classNames.loadingState); + this.element.setAttribute('aria-busy', 'true'); + this.isLoading = true; + } + /** + * Remove loading state from element + */ + + }, { + key: "removeLoadingState", + value: function removeLoadingState() { + this.element.classList.remove(this.classNames.loadingState); + this.element.removeAttribute('aria-busy'); + this.isLoading = false; + } + /** + * Set focussed state + */ + + }, { + key: "_onFocus", + value: function _onFocus() { + this.isFocussed = true; + } + /** + * Remove blurred state + */ + + }, { + key: "_onBlur", + value: function _onBlur() { + this.isFocussed = false; + } + }]); + + return Container; +}(); + +exports.default = Container; + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _utils = __webpack_require__(0); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var Input = +/*#__PURE__*/ +function () { + function Input(_ref) { + var element = _ref.element, + type = _ref.type, + classNames = _ref.classNames, + placeholderValue = _ref.placeholderValue; + + _classCallCheck(this, Input); + + Object.assign(this, { + element: element, + type: type, + classNames: classNames, + placeholderValue: placeholderValue + }); + this.element = element; + this.classNames = classNames; + this.isFocussed = this.element === document.activeElement; + this.isDisabled = false; + this._onPaste = this._onPaste.bind(this); + this._onInput = this._onInput.bind(this); + this._onFocus = this._onFocus.bind(this); + this._onBlur = this._onBlur.bind(this); + } + + _createClass(Input, [{ + key: "addEventListeners", + value: function addEventListeners() { + this.element.addEventListener('input', this._onInput); + this.element.addEventListener('paste', this._onPaste); + this.element.addEventListener('focus', this._onFocus); + this.element.addEventListener('blur', this._onBlur); + + if (this.element.form) { + this.element.form.addEventListener('reset', this._onFormReset); + } + } + }, { + key: "removeEventListeners", + value: function removeEventListeners() { + this.element.removeEventListener('input', this._onInput); + this.element.removeEventListener('paste', this._onPaste); + this.element.removeEventListener('focus', this._onFocus); + this.element.removeEventListener('blur', this._onBlur); + + if (this.element.form) { + this.element.form.removeEventListener('reset', this._onFormReset); + } + } + }, { + key: "enable", + value: function enable() { + this.element.removeAttribute('disabled'); + this.isDisabled = false; + } + }, { + key: "disable", + value: function disable() { + this.element.setAttribute('disabled', ''); + this.isDisabled = true; + } + }, { + key: "focus", + value: function focus() { + if (!this.isFocussed) { + this.element.focus(); + } + } + }, { + key: "blur", + value: function blur() { + if (this.isFocussed) { + this.element.blur(); + } + } + /** + * Set value of input to blank + * @return {Object} Class instance + * @public + */ + + }, { + key: "clear", + value: function clear() { + var setWidth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + + if (this.element.value) { + this.element.value = ''; + } + + if (setWidth) { + this.setWidth(); + } + + return this; + } + /** + * Set the correct input width based on placeholder + * value or input value + * @return + */ + + }, { + key: "setWidth", + value: function setWidth(enforceWidth) { + var _this = this; + + var callback = function callback(width) { + _this.element.style.width = width; + }; + + if (this._placeholderValue) { + // If there is a placeholder, we only want to set the width of the input when it is a greater + // length than 75% of the placeholder. This stops the input jumping around. + var valueHasDesiredLength = this.element.value.length >= this._placeholderValue.length / 1.25; + + if (this.element.value && valueHasDesiredLength || enforceWidth) { + this.calcWidth(callback); + } + } else { + // If there is no placeholder, resize input to contents + this.calcWidth(callback); + } + } + }, { + key: "calcWidth", + value: function calcWidth(callback) { + return (0, _utils.calcWidthOfInput)(this.element, callback); + } + }, { + key: "setActiveDescendant", + value: function setActiveDescendant(activeDescendantID) { + this.element.setAttribute('aria-activedescendant', activeDescendantID); + } + }, { + key: "removeActiveDescendant", + value: function removeActiveDescendant() { + this.element.removeAttribute('aria-activedescendant'); + } + }, { + key: "_onInput", + value: function _onInput() { + if (this.type !== 'select-one') { + this.setWidth(); + } + } + }, { + key: "_onPaste", + value: function _onPaste(event) { + var target = event.target; + + if (target === this.element && this.preventPaste) { + event.preventDefault(); + } + } + }, { + key: "_onFocus", + value: function _onFocus() { + this.isFocussed = true; + } + }, { + key: "_onBlur", + value: function _onBlur() { + this.isFocussed = false; + } + }, { + key: "placeholder", + set: function set(placeholder) { + this.element.placeholder = placeholder; + } + }, { + key: "value", + set: function set(value) { + this.element.value = value; + }, + get: function get() { + return (0, _utils.sanitise)(this.element.value); + } + }]); + + return Input; +}(); + +exports.default = Input; + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _constants = __webpack_require__(1); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var List = +/*#__PURE__*/ +function () { + function List(_ref) { + var element = _ref.element; + + _classCallCheck(this, List); + + Object.assign(this, { + element: element + }); + this.scrollPos = this.element.scrollTop; + this.height = this.element.offsetHeight; + this.hasChildren = !!this.element.children; + } + + _createClass(List, [{ + key: "clear", + value: function clear() { + this.element.innerHTML = ''; + } + }, { + key: "append", + value: function append(node) { + this.element.appendChild(node); + } + }, { + key: "getChild", + value: function getChild(selector) { + return this.element.querySelector(selector); + } + }, { + key: "scrollToTop", + value: function scrollToTop() { + this.element.scrollTop = 0; + } + }, { + key: "scrollToChoice", + value: function scrollToChoice(choice, direction) { + var _this = this; + + if (!choice) { + return; + } + + var dropdownHeight = this.element.offsetHeight; + var choiceHeight = choice.offsetHeight; // Distance from bottom of element to top of parent + + var choicePos = choice.offsetTop + choiceHeight; // Scroll position of dropdown + + var containerScrollPos = this.element.scrollTop + dropdownHeight; // Difference between the choice and scroll position + + var endpoint = direction > 0 ? this.element.scrollTop + choicePos - containerScrollPos : choice.offsetTop; + requestAnimationFrame(function (time) { + _this._animateScroll(time, endpoint, direction); + }); + } + }, { + key: "_scrollDown", + value: function _scrollDown(scrollPos, strength, endpoint) { + var easing = (endpoint - scrollPos) / strength; + var distance = easing > 1 ? easing : 1; + this.element.scrollTop = scrollPos + distance; + } + }, { + key: "_scrollUp", + value: function _scrollUp(scrollPos, strength, endpoint) { + var easing = (scrollPos - endpoint) / strength; + var distance = easing > 1 ? easing : 1; + this.element.scrollTop = scrollPos - distance; + } + }, { + key: "_animateScroll", + value: function _animateScroll(time, endpoint, direction) { + var _this2 = this; + + var strength = _constants.SCROLLING_SPEED; + var choiceListScrollTop = this.element.scrollTop; + var continueAnimation = false; + + if (direction > 0) { + this._scrollDown(choiceListScrollTop, strength, endpoint); + + if (choiceListScrollTop < endpoint) { + continueAnimation = true; + } + } else { + this._scrollUp(choiceListScrollTop, strength, endpoint); + + if (choiceListScrollTop > endpoint) { + continueAnimation = true; + } + } + + if (continueAnimation) { + requestAnimationFrame(function () { + _this2._animateScroll(time, endpoint, direction); + }); + } + } + }]); + + return List; +}(); + +exports.default = List; + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _wrappedElement = _interopRequireDefault(__webpack_require__(4)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var WrappedInput = +/*#__PURE__*/ +function (_WrappedElement) { + _inherits(WrappedInput, _WrappedElement); + + function WrappedInput(_ref) { + var _this; + + var element = _ref.element, + classNames = _ref.classNames, + delimiter = _ref.delimiter; + + _classCallCheck(this, WrappedInput); + + _this = _possibleConstructorReturn(this, _getPrototypeOf(WrappedInput).call(this, { + element: element, + classNames: classNames + })); + _this.delimiter = delimiter; + return _this; + } + + _createClass(WrappedInput, [{ + key: "value", + set: function set(items) { + var itemValues = items.map(function (_ref2) { + var value = _ref2.value; + return value; + }); + var joinedValues = itemValues.join(this.delimiter); + this.element.setAttribute('value', joinedValues); + this.element.value = joinedValues; + } // @todo figure out why we need this? Perhaps a babel issue + , + get: function get() { + return _get(_getPrototypeOf(WrappedInput.prototype), "value", this); + } + }]); + + return WrappedInput; +}(_wrappedElement.default); + +exports.default = WrappedInput; + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _wrappedElement = _interopRequireDefault(__webpack_require__(4)); + +var _templates = _interopRequireDefault(__webpack_require__(5)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var WrappedSelect = +/*#__PURE__*/ +function (_WrappedElement) { + _inherits(WrappedSelect, _WrappedElement); + + function WrappedSelect(_ref) { + var element = _ref.element, + classNames = _ref.classNames; + + _classCallCheck(this, WrappedSelect); + + return _possibleConstructorReturn(this, _getPrototypeOf(WrappedSelect).call(this, { + element: element, + classNames: classNames + })); + } + + _createClass(WrappedSelect, [{ + key: "appendDocFragment", + value: function appendDocFragment(fragment) { + this.element.innerHTML = ''; + this.element.appendChild(fragment); + } + }, { + key: "placeholderOption", + get: function get() { + return this.element.querySelector('option[placeholder]'); + } + }, { + key: "optionGroups", + get: function get() { + return Array.from(this.element.getElementsByTagName('OPTGROUP')); + } + }, { + key: "options", + get: function get() { + return Array.from(this.element.options); + }, + set: function set(options) { + var fragment = document.createDocumentFragment(); + + var addOptionToFragment = function addOptionToFragment(data) { + // Create a standard select option + var template = _templates.default.option(data); // Append it to fragment + + + fragment.appendChild(template); + }; // Add each list item to list + + + options.forEach(function (optionData) { + return addOptionToFragment(optionData); + }); + this.appendDocFragment(fragment); + } + }]); + + return WrappedSelect; +}(_wrappedElement.default); + +exports.default = WrappedSelect; + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! + Copyright (c) 2017 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +/* global define */ + +(function () { + 'use strict'; + + var hasOwn = {}.hasOwnProperty; + + function classNames () { + var classes = []; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if (!arg) continue; + + var argType = typeof arg; + + if (argType === 'string' || argType === 'number') { + classes.push(arg); + } else if (Array.isArray(arg) && arg.length) { + var inner = classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } else if (argType === 'object') { + for (var key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + + return classes.join(' '); + } + + if ( true && module.exports) { + classNames.default = classNames; + module.exports = classNames; + } else if (true) { + // register as 'classnames', consistent with npm package name + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = (function () { + return classNames; + }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else {} +}()); + + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.clearChoices = exports.activateChoices = exports.filterChoices = exports.addChoice = void 0; + +var _constants = __webpack_require__(1); + +var addChoice = function addChoice(_ref) { + var value = _ref.value, + label = _ref.label, + id = _ref.id, + groupId = _ref.groupId, + disabled = _ref.disabled, + elementId = _ref.elementId, + customProperties = _ref.customProperties, + placeholder = _ref.placeholder, + keyCode = _ref.keyCode; + return { + type: _constants.ACTION_TYPES.ADD_CHOICE, + value: value, + label: label, + id: id, + groupId: groupId, + disabled: disabled, + elementId: elementId, + customProperties: customProperties, + placeholder: placeholder, + keyCode: keyCode + }; +}; + +exports.addChoice = addChoice; + +var filterChoices = function filterChoices(results) { + return { + type: _constants.ACTION_TYPES.FILTER_CHOICES, + results: results + }; +}; + +exports.filterChoices = filterChoices; + +var activateChoices = function activateChoices() { + var active = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + return { + type: _constants.ACTION_TYPES.ACTIVATE_CHOICES, + active: active + }; +}; + +exports.activateChoices = activateChoices; + +var clearChoices = function clearChoices() { + return { + type: _constants.ACTION_TYPES.CLEAR_CHOICES + }; +}; + +exports.clearChoices = clearChoices; + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.highlightItem = exports.removeItem = exports.addItem = void 0; + +var _constants = __webpack_require__(1); + +var addItem = function addItem(_ref) { + var value = _ref.value, + label = _ref.label, + id = _ref.id, + choiceId = _ref.choiceId, + groupId = _ref.groupId, + customProperties = _ref.customProperties, + placeholder = _ref.placeholder, + keyCode = _ref.keyCode; + return { + type: _constants.ACTION_TYPES.ADD_ITEM, + value: value, + label: label, + id: id, + choiceId: choiceId, + groupId: groupId, + customProperties: customProperties, + placeholder: placeholder, + keyCode: keyCode + }; +}; + +exports.addItem = addItem; + +var removeItem = function removeItem(id, choiceId) { + return { + type: _constants.ACTION_TYPES.REMOVE_ITEM, + id: id, + choiceId: choiceId + }; +}; + +exports.removeItem = removeItem; + +var highlightItem = function highlightItem(id, highlighted) { + return { + type: _constants.ACTION_TYPES.HIGHLIGHT_ITEM, + id: id, + highlighted: highlighted + }; +}; + +exports.highlightItem = highlightItem; + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.addGroup = void 0; + +var _constants = __webpack_require__(1); + +/* eslint-disable import/prefer-default-export */ +var addGroup = function addGroup(value, id, active, disabled) { + return { + type: _constants.ACTION_TYPES.ADD_GROUP, + value: value, + id: id, + active: active, + disabled: disabled + }; +}; + +exports.addGroup = addGroup; + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.resetTo = exports.clearAll = void 0; + +var clearAll = function clearAll() { + return { + type: 'CLEAR_ALL' + }; +}; + +exports.clearAll = clearAll; + +var resetTo = function resetTo(state) { + return { + type: 'RESET_TO', + state: state + }; +}; + +exports.resetTo = resetTo; + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.setIsLoading = void 0; + +/* eslint-disable import/prefer-default-export */ +var setIsLoading = function setIsLoading(isLoading) { + return { + type: 'SET_IS_LOADING', + isLoading: isLoading + }; +}; + +exports.setIsLoading = setIsLoading; + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/backoffice/vendors/choices/choices.min.css b/backoffice/vendors/choices/choices.min.css new file mode 100644 index 00000000..d4268fbe --- /dev/null +++ b/backoffice/vendors/choices/choices.min.css @@ -0,0 +1 @@ +.choices{position:relative;margin-bottom:24px;font-size:16px}.choices:focus{outline:none}.choices:last-child{margin-bottom:0}.choices.is-disabled .choices__inner,.choices.is-disabled .choices__input{background-color:#eaeaea;cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.choices.is-disabled .choices__item{cursor:not-allowed}.choices[data-type*=select-one]{cursor:pointer}.choices[data-type*=select-one] .choices__inner{padding-bottom:7.5px}.choices[data-type*=select-one] .choices__input{display:block;width:100%;padding:10px;border-bottom:1px solid #ddd;background-color:#fff;margin:0}.choices[data-type*=select-one] .choices__button{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==);padding:0;background-size:8px;position:absolute;top:50%;right:0;margin-top:-10px;margin-right:25px;height:20px;width:20px;border-radius:10em;opacity:.5}.choices[data-type*=select-one] .choices__button:focus,.choices[data-type*=select-one] .choices__button:hover{opacity:1}.choices[data-type*=select-one] .choices__button:focus{box-shadow:0 0 0 2px #00bcd4}.choices[data-type*=select-one]:after{content:"";height:0;width:0;border-style:solid;border-color:#333 transparent transparent transparent;border-width:5px;position:absolute;right:11.5px;top:50%;margin-top:-2.5px;pointer-events:none}.choices[data-type*=select-one].is-open:after{border-color:transparent transparent #333 transparent;margin-top:-7.5px}.choices[data-type*=select-one][dir=rtl]:after{left:11.5px;right:auto}.choices[data-type*=select-one][dir=rtl] .choices__button{right:auto;left:0;margin-left:25px;margin-right:0}.choices[data-type*=select-multiple] .choices__inner,.choices[data-type*=text] .choices__inner{cursor:text}.choices[data-type*=select-multiple] .choices__button,.choices[data-type*=text] .choices__button{position:relative;display:inline-block;margin:0 -4px 0 8px;padding-left:16px;border-left:1px solid #008fa1;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==);background-size:8px;width:8px;line-height:1;opacity:.75;border-radius:0}.choices[data-type*=select-multiple] .choices__button:focus,.choices[data-type*=select-multiple] .choices__button:hover,.choices[data-type*=text] .choices__button:focus,.choices[data-type*=text] .choices__button:hover{opacity:1}.choices__inner{display:inline-block;vertical-align:top;width:100%;background-color:#f9f9f9;padding:7.5px 7.5px 3.75px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;min-height:44px;overflow:hidden}.is-focused .choices__inner,.is-open .choices__inner{border-color:#b7b7b7}.is-open .choices__inner{border-radius:2.5px 2.5px 0 0}.is-flipped.is-open .choices__inner{border-radius:0 0 2.5px 2.5px}.choices__list{margin:0;padding-left:0;list-style:none}.choices__list--single{display:inline-block;padding:4px 16px 4px 4px;width:100%}[dir=rtl] .choices__list--single{padding-right:4px;padding-left:16px}.choices__list--single .choices__item{width:100%}.choices__list--multiple{display:inline}.choices__list--multiple .choices__item{display:inline-block;vertical-align:middle;border-radius:20px;padding:4px 10px;font-size:12px;font-weight:500;margin-right:3.75px;margin-bottom:3.75px;background-color:#00bcd4;border:1px solid #00a5bb;color:#fff;word-break:break-all}.choices__list--multiple .choices__item[data-deletable]{padding-right:5px}[dir=rtl] .choices__list--multiple .choices__item{margin-right:0;margin-left:3.75px}.choices__list--multiple .choices__item.is-highlighted{background-color:#00a5bb;border:1px solid #008fa1}.is-disabled .choices__list--multiple .choices__item{background-color:#aaa;border:1px solid #919191}.choices__list--dropdown{display:none;z-index:1;position:absolute;width:100%;background-color:#fff;border:1px solid #ddd;top:100%;margin-top:-1px;border-bottom-left-radius:2.5px;border-bottom-right-radius:2.5px;overflow:hidden;word-break:break-all}.choices__list--dropdown.is-active{display:block}.is-open .choices__list--dropdown{border-color:#b7b7b7}.is-flipped .choices__list--dropdown{top:auto;bottom:100%;margin-top:0;margin-bottom:-1px;border-radius:.25rem .25rem 0 0}.choices__list--dropdown .choices__list{position:relative;max-height:300px;overflow:auto;-webkit-overflow-scrolling:touch;will-change:scroll-position}.choices__list--dropdown .choices__item{position:relative;padding:10px;font-size:14px}[dir=rtl] .choices__list--dropdown .choices__item{text-align:right}@media (min-width:640px){.choices__list--dropdown .choices__item--selectable{padding-right:100px}.choices__list--dropdown .choices__item--selectable:after{content:attr(data-select-text);font-size:12px;opacity:0;position:absolute;right:10px;top:50%;transform:translateY(-50%)}[dir=rtl] .choices__list--dropdown .choices__item--selectable{text-align:right;padding-left:100px;padding-right:10px}[dir=rtl] .choices__list--dropdown .choices__item--selectable:after{right:auto;left:10px}}.choices__list--dropdown .choices__item--selectable.is-highlighted{background-color:#f2f2f2}.choices__list--dropdown .choices__item--selectable.is-highlighted:after{opacity:.5}.choices__item{cursor:default}.choices__item--selectable{cursor:pointer}.choices__item--disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:.5}.choices__heading{font-weight:600;font-size:12px;padding:10px;border-bottom:1px solid #f7f7f7;color:gray}.choices__button{text-indent:-9999px;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;background-color:transparent;background-repeat:no-repeat;background-position:center;cursor:pointer}.choices__button:focus{outline:none}.choices__input{display:inline-block;vertical-align:baseline;background-color:#f9f9f9;font-size:14px;margin-bottom:5px;border:0;border-radius:0;max-width:100%;padding:4px 0 4px 2px}.choices__input:focus{outline:0}[dir=rtl] .choices__input{padding-right:2px;padding-left:0}.choices__placeholder{opacity:.5}.choices[data-type*=select-multiple] .choices__input.is-hidden,.choices[data-type*=select-one] .choices__input.is-hidden,.choices__input.is-hidden{display:none} \ No newline at end of file diff --git a/backoffice/vendors/choices/choices.min.js b/backoffice/vendors/choices/choices.min.js new file mode 100644 index 00000000..5d105dc8 --- /dev/null +++ b/backoffice/vendors/choices/choices.min.js @@ -0,0 +1,58 @@ +/*! choices.js v7.0.0 | (c) 2019 Josh Johnson | https://github.com/jshjohnson/Choices#readme */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Choices=t():e.Choices=t()}(window,function(){return function(e){var t={};function i(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,i),o.l=!0,o.exports}return i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)i.d(n,o,function(t){return e[t]}.bind(null,o));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/public/assets/scripts/",i(i.s=9)}([function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.diff=t.cloneObject=t.existsInArray=t.isIE11=t.fetchFromObject=t.getWindowHeight=t.dispatchEvent=t.sortByScore=t.sortByAlpha=t.calcWidthOfInput=t.strToEl=t.sanitise=t.isScrolledIntoView=t.getAdjacentEl=t.findAncestorByAttrName=t.wrap=t.isElement=t.isType=t.getType=t.generateId=t.generateChars=t.getRandomNumber=void 0;var n=function(e,t){return Math.floor(Math.random()*(t-e)+e)};t.getRandomNumber=n;var o=function(e){for(var t="",i=0;i1&&void 0!==arguments[1]?arguments[1]:document.createElement("div");return e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.appendChild(e)};t.findAncestorByAttrName=function(e,t){for(var i=e;i;){if(i.hasAttribute(t))return i;i=i.parentElement}return null};t.getAdjacentEl=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;if(e&&t){var n=e.parentNode.parentNode,o=Array.from(n.querySelectorAll(t)),r=o.indexOf(e);return o[r+(i>0?1:-1)]}};t.isScrolledIntoView=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;if(e)return i>0?t.scrollTop+t.offsetHeight>=e.offsetTop+e.offsetHeight:e.offsetTop>=t.scrollTop};var a=function(e){return s("String",e)?e.replace(/&/g,"&").replace(/>/g,"&rt;").replace(/".concat(a(i),""));if(o.style.position="absolute",o.style.padding="0",o.style.top="-9999px",o.style.left="-9999px",o.style.width="auto",o.style.whiteSpace="pre",document.body.contains(e)&&window.getComputedStyle){var r=window.getComputedStyle(e);r&&(o.style.fontSize=r.fontSize,o.style.fontFamily=r.fontFamily,o.style.fontWeight=r.fontWeight,o.style.fontStyle=r.fontStyle,o.style.letterSpacing=r.letterSpacing,o.style.textTransform=r.textTransform,o.style.padding=r.padding)}document.body.appendChild(o),requestAnimationFrame(function(){i&&o.offsetWidth!==e.offsetWidth&&(n=o.offsetWidth+4),document.body.removeChild(o),t.call(void 0,"".concat(n,"px"))})}else t.call(void 0,"".concat(n,"px"))};t.sortByAlpha=function(e,t){var i="".concat(e.label||e.value).toLowerCase(),n="".concat(t.label||t.value).toLowerCase();return in?1:0};t.sortByScore=function(e,t){return e.score-t.score};t.dispatchEvent=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,n=new CustomEvent(t,{detail:i,bubbles:!0,cancelable:!0});return e.dispatchEvent(n)};t.getWindowHeight=function(){var e=document.body,t=document.documentElement;return Math.max(e.scrollHeight,e.offsetHeight,t.clientHeight,t.scrollHeight,t.offsetHeight)};t.fetchFromObject=function e(t,i){var n=i.indexOf(".");return n>-1?e(t[i.substring(0,n)],i.substr(n+1)):t[i]};t.isIE11=function(){return!(!navigator.userAgent.match(/Trident/)||!navigator.userAgent.match(/rv[ :]11/))};t.existsInArray=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"value";return e.some(function(e){return s("String",t)?e[i]===t.trim():e[i]===t})};t.cloneObject=function(e){return JSON.parse(JSON.stringify(e))};t.diff=function(e,t){var i=Object.keys(e).sort(),n=Object.keys(t).sort();return i.filter(function(e){return n.indexOf(e)<0})}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SCROLLING_SPEED=t.KEY_CODES=t.ACTION_TYPES=t.EVENTS=t.DEFAULT_CONFIG=t.DEFAULT_CLASSNAMES=void 0;var n=i(0),o={containerOuter:"choices",containerInner:"choices__inner",input:"choices__input",inputCloned:"choices__input--cloned",list:"choices__list",listItems:"choices__list--multiple",listSingle:"choices__list--single",listDropdown:"choices__list--dropdown",item:"choices__item",itemSelectable:"choices__item--selectable",itemDisabled:"choices__item--disabled",itemChoice:"choices__item--choice",placeholder:"choices__placeholder",group:"choices__group",groupHeading:"choices__heading",button:"choices__button",activeState:"is-active",focusState:"is-focused",openState:"is-open",disabledState:"is-disabled",highlightedState:"is-highlighted",hiddenState:"is-hidden",flippedState:"is-flipped",loadingState:"is-loading",noResults:"has-no-results",noChoices:"has-no-choices"};t.DEFAULT_CLASSNAMES=o;var r={items:[],choices:[],silent:!1,renderChoiceLimit:-1,maxItemCount:-1,addItems:!0,addItemFilterFn:null,removeItems:!0,removeItemButton:!1,editItems:!1,duplicateItemsAllowed:!0,delimiter:",",paste:!0,searchEnabled:!0,searchChoices:!0,searchFloor:1,searchResultLimit:4,searchFields:["label","value"],position:"auto",resetScrollPosition:!0,shouldSort:!0,shouldSortItems:!1,sortFn:n.sortByAlpha,placeholder:!0,placeholderValue:null,searchPlaceholderValue:null,prependValue:null,appendValue:null,renderSelectedChoices:"auto",loadingText:"Loading...",noResultsText:"No results found",noChoicesText:"No choices to choose from",itemSelectText:"Press to select",uniqueItemText:"Only unique values can be added",customAddItemText:"Only values matching specific conditions can be added",addItemText:function(e){return'Press Enter to add "'.concat((0,n.sanitise)(e),'"')},maxItemText:function(e){return"Only ".concat(e," values can be added")},itemComparer:function(e,t){return e===t},fuseOptions:{includeScore:!0},callbackOnInit:null,callbackOnCreateTemplates:null,classNames:o};t.DEFAULT_CONFIG=r;t.EVENTS={showDropdown:"showDropdown",hideDropdown:"hideDropdown",change:"change",choice:"choice",search:"search",addItem:"addItem",removeItem:"removeItem",highlightItem:"highlightItem",highlightChoice:"highlightChoice"};t.ACTION_TYPES={ADD_CHOICE:"ADD_CHOICE",FILTER_CHOICES:"FILTER_CHOICES",ACTIVATE_CHOICES:"ACTIVATE_CHOICES",CLEAR_CHOICES:"CLEAR_CHOICES",ADD_GROUP:"ADD_GROUP",ADD_ITEM:"ADD_ITEM",REMOVE_ITEM:"REMOVE_ITEM",HIGHLIGHT_ITEM:"HIGHLIGHT_ITEM",CLEAR_ALL:"CLEAR_ALL"};t.KEY_CODES={BACK_KEY:46,DELETE_KEY:8,ENTER_KEY:13,A_KEY:65,ESC_KEY:27,UP_KEY:38,DOWN_KEY:40,PAGE_UP_KEY:33,PAGE_DOWN_KEY:34};t.SCROLLING_SPEED=4},function(e,t,i){"use strict";(function(e,n){var o,r=i(7);o="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==e?e:n;var s=Object(r.a)(o);t.a=s}).call(this,i(3),i(14)(e))},function(e,t){var i;i=function(){return this}();try{i=i||new Function("return this")()}catch(e){"object"==typeof window&&(i=window)}e.exports=i},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=i(0);function o(e,t){for(var i=0;i