diff --git a/app/Classes/Template.php b/app/Classes/Template.php index f1c38ed5..407f4ce0 100644 --- a/app/Classes/Template.php +++ b/app/Classes/Template.php @@ -3,12 +3,16 @@ namespace App\Classes; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class Template { - private string $file; + private const LOGKEY = 'T--'; + + private(set) string $file; private array $template; private(set) bool $invalid = FALSE; private(set) string $reason = ''; @@ -48,4 +52,137 @@ class Template { return array_key_exists($key,$this->template); } + + /** + * Return the onchange JavaScript for attribute + * + * @param string $attribute + * @return Collection + */ + public function onChange(string $attribute): Collection + { + $attr = collect($this->template['attributes']) + ->filter(fn($item,$key)=>! strcasecmp($key,$attribute)) + ->pop(); + + if (! $attr) + return collect(); + + $result = collect(); + + foreach (Arr::get($attr,'onchange',[]) as $item) { + list($command,$args) = preg_split('/^=([a-zA-Z]+)\((.+)\)$/',$item,-1,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); + + switch ($command) { + case 'autoFill': + $result->push($this->autofill($args)); + break; + } + } + + return $result; + } + + /** + * autoFill - javascript to have one attribute fill the value of another + * + * args: is a literal string, with two parts, delimited by a semi-colon ; + * + The first part is the attribute that will be populated + * + The second part may contain many fields like %attr|start-end/flags|additionalcontrolchar% + * to substitute values read from other fields. + * + |start-end is optional, but must be present if the k flag is used + * + /flags is optional + * + |additionalcontrolchar is optional, and specific to a flag being used + * + * + flags may be: + * T:(?) Read display text from selection item (drop-down list), otherwise, read the value of the field + * For fields that aren't selection items, /T shouldn't be used, and the field value will always be read + * k:(?) Tokenize: + * If the "k" flag is not given: + * + A |start-end instruction will perform a sub-string operation upon the value of the attr, passing + * character positions start-end through + * + start can be 0 for first character, or any other integer + * + end can be 0 for last character, or any other integer for a specific position + * If the "k" flag is given: + * + The string read will be split into fields, using : as a delimiter + * + start indicates which field number to pass through + * + * If additionalcontrolchar is given, it will be used as delimiter (e.g. this allows for splitting + * e-mail addresses into domain and domain-local part) + * l: Make the result lower case + * U: Make the result upper case + * A:(?) Remap special characters to their corresponding ASCII value + * + * @note Attributes rendered on the page are lowercase, eg: for gidNumber + * @note JavaScript generated here depends on js/template.js + * (?) = to test + */ + private function autofill(string $arg): string + { + if (! preg_match('/;/',$arg)) { + Log::alert(sprintf('%s:Invalid argument given to autofill [%s]',self::LOGKEY,$arg)); + return ''; + } + + $result = ''; + + // $attr has our attribute to update, $string is the format to use when updating it + list($attr,$string) = preg_split('(([^,]+);(.*))',$arg,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $output = $string; + //$result .= sprintf("\n// %s\n",$arg); + + $m = []; + // MATCH : 0 = highlevel match, 1 = attr, 2 = subst, 3 = mod, 4 = delimiter + preg_match_all('/%(\w+)(?:\|([0-9]*-[0-9])+)?(?:\/([klTUA]+))?(?:\|(.)?)?%/U',$string,$m); + + foreach ($m[0] as $index => $null) { + $match_attr = strtolower($m[1][$index]); + $match_subst = $m[2][$index]; + $match_mod = $m[3][$index]; + $match_delim = $m[4][$index]; + + $substrarray = []; + + $result .= sprintf("var %s;\n",$match_attr); + + if (str_contains($match_mod,'k')) { + preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray); + + $delimiter = ($match_delim === '') ? ' ' : preg_quote($match_delim); + $result .= sprintf(" %s = %s.split('%s')[%s];\n",$match_attr,$match_attr,$delimiter,$substrarray[1][0] ?? '0'); + + } else { + // Work out the start and end chars needed from this value if we have a range specifier + preg_match_all('/([0-9]*)-([0-9]+)/',$match_subst,$substrarray); + if ((isset($substrarray[1][0]) && $substrarray[1][0]) || (isset($substrarray[2][0]) && $substrarray[2][0])) { + $result .= sprintf("%s = get_attribute('%s',%d,%s);\n", + $match_attr,$match_attr, + $substrarray[1][0] ?? '0', + $substrarray[2][0] ?: sprintf('%s.length',$match_attr)); + } else { + $result .= sprintf("%s = get_attribute('%s');\n",$match_attr,$match_attr); + } + } + + if (str_contains($match_mod,'l')) + $result .= sprintf("%s = %s.toLowerCase();\n",$match_attr,$match_attr); + + if (str_contains($match_mod,'U')) + $result .= sprintf("%s = %s.toUpperCase();\n",$match_attr,$match_attr); + + if (str_contains($match_mod,'A')) + $result .= sprintf("%s = toAscii(%s);\n",$match_attr,$match_attr); + + // For debugging + //$result .= sprintf("console.log('%s will return:'+%s);\n",$match_attr,$match_attr); + + // Reformat out output into JS variables + $output = preg_replace('/'.preg_quote($m[0][$index],'/').'/','\'+'.$match_attr.'+\'',$output); + } + + $result .= sprintf("put_attribute('%s','%s');\n",strtolower($attr),$output); + $result .= "\n"; + + return $result; + } } \ No newline at end of file diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index a6d4ad73..7b992b06 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -46,18 +46,12 @@ class HomeController extends Controller $o = new Entry; $o->setRDNBase($key['dn']); - foreach (collect(old())->except(['_token','key','step','rdn','rdn_value','userpassword_hash']) as $old => $value) + foreach (collect(old())->except(['_token','key','step','rdn','rdn_value','_template','userpassword_hash']) as $old => $value) $o->{$old} = array_filter($value); - if (count($x=collect(old('objectclass',$request->validated('objectclass')))->dot()->filter())) { - $o->objectclass = Arr::undot($x); + if (old('_template',$request->validated('template'))) { + $template = $o->template(old('_template',$request->validated('template'))); - // Also add in our required attributes - foreach ($o->getAvailableAttributes()->filter(fn($item)=>$item->is_must) as $ao) - $o->{$ao->name} = [Entry::TAG_NOTAG=>'']; - - } elseif ($request->validated('template')) { - $template = $o->template($request->validated('template')); $o->objectclass = [Entry::TAG_NOTAG=>$template->objectclasses->toArray()]; foreach ($o->getAvailableAttributes() @@ -66,6 +60,13 @@ class HomeController extends Controller { $o->{$ao->name} = [Entry::TAG_NOTAG=>'']; } + + } elseif (count($x=collect(old('objectclass',$request->validated('objectclass')))->dot()->filter())) { + $o->objectclass = Arr::undot($x); + + // Also add in our required attributes + foreach ($o->getAvailableAttributes()->filter(fn($item)=>$item->is_must) as $ao) + $o->{$ao->name} = [Entry::TAG_NOTAG=>'']; } $step = $request->step ? $request->step+1 : old('step'); @@ -104,6 +105,7 @@ class HomeController extends Controller return $view ->with('o',$o) ->with('langtag',Entry::TAG_NOTAG) + ->with('template',NULL) ->with('updated',FALSE); } @@ -116,7 +118,7 @@ class HomeController extends Controller $o = new Entry; $o->setDn($dn); - foreach ($request->except(['_token','key','step','rdn','rdn_value','userpassword_hash']) as $key => $value) + foreach ($request->except(['_token','key','step','rdn','rdn_value','_template','userpassword_hash']) as $key => $value) $o->{$key} = array_filter($value); try { @@ -368,6 +370,7 @@ class HomeController extends Controller * @param Request $request * @param Collection|null $old * @return \Illuminate\View\View + * @throws InvalidUsage */ public function frame(Request $request,?Collection $old=NULL): \Illuminate\View\View { diff --git a/public/js/template.js b/public/js/template.js new file mode 100644 index 00000000..19e8d153 --- /dev/null +++ b/public/js/template.js @@ -0,0 +1,23 @@ +/* JavaScript template engine abstraction layer */ +/* Currently implemented for jquery */ + +// Get a value from an attribute +function get_attribute(attribute,start,end) { + var val = $('#'+attribute).find('input').val(); + + return ((start !== undefined) && (end !== undefined)) + ? val.substring(start,end) + : val; +} + +// Put a value to an attribute +function put_attribute(attribute,result) { + // Get the value, if the value hasnt changed, then we dont need to do anything + if (get_attribute(attribute) === result) + return; + + $('#'+attribute) + .find('input') + .val(result) + .trigger('change'); +} \ No newline at end of file diff --git a/public/js/toAscii.js b/public/js/toAscii.js new file mode 100644 index 00000000..f66b5804 --- /dev/null +++ b/public/js/toAscii.js @@ -0,0 +1,81 @@ +// +// Purpose of this file is to remap characters as ASCII characters +// +// + +var to_ascii_array = new Array(); +to_ascii_array['à'] = 'a'; +to_ascii_array['á'] = 'a'; +to_ascii_array['â'] = 'a'; +to_ascii_array['À'] = 'a'; +to_ascii_array['ã'] = 'a'; +to_ascii_array['Ã¥'] = 'a'; +to_ascii_array['À'] = 'A'; +to_ascii_array['Á'] = 'A'; +to_ascii_array['Ä'] = 'A'; +to_ascii_array['Â'] = 'A'; +to_ascii_array['Ã'] = 'A'; +to_ascii_array['Å'] = 'A'; +to_ascii_array['é'] = 'e'; +to_ascii_array['Ú'] = 'e'; +to_ascii_array['ë'] = 'e'; +to_ascii_array['ê'] = 'e'; +to_ascii_array['€'] = 'E'; +to_ascii_array['ï'] = 'i'; +to_ascii_array['î'] = 'i'; +to_ascii_array['ì'] = 'i'; +to_ascii_array['í'] = 'i'; +to_ascii_array['Ï'] = 'I'; +to_ascii_array['Î'] = 'I'; +to_ascii_array['Ì'] = 'I'; +to_ascii_array['Í'] = 'I'; +to_ascii_array['ò'] = 'o'; +to_ascii_array['ó'] = 'o'; +to_ascii_array['ÃŽ'] = 'o'; +to_ascii_array['õ'] = 'o'; +to_ascii_array['ö'] = 'o'; +to_ascii_array['Þ'] = 'o'; +to_ascii_array['Ò'] = 'O'; +to_ascii_array['Ó'] = 'O'; +to_ascii_array['Ô'] = 'O'; +to_ascii_array['Õ'] = 'O'; +to_ascii_array['Ö'] = 'O'; +to_ascii_array['Ø'] = 'O'; +to_ascii_array['ù'] = 'u'; +to_ascii_array['ú'] = 'u'; +to_ascii_array['ÃŒ'] = 'u'; +to_ascii_array['û'] = 'u'; +to_ascii_array['Ù'] = 'U'; +to_ascii_array['Ú'] = 'U'; +to_ascii_array['Ü'] = 'U'; +to_ascii_array['Û'] = 'U'; +to_ascii_array['Ê'] = 'ae'; +to_ascii_array['Æ'] = 'AE'; +to_ascii_array['Ü'] = 'y'; +to_ascii_array['ÿ'] = 'y'; +to_ascii_array['ß'] = 'SS'; +to_ascii_array['Ç'] = 'C'; +to_ascii_array['ç'] = 'c'; +to_ascii_array['Ñ'] = 'N'; +to_ascii_array['ñ'] = 'n'; +to_ascii_array['¢'] = 'c'; +to_ascii_array['©'] = '(C)'; +to_ascii_array['®'] = '(R)'; +to_ascii_array['«'] = '<<'; +to_ascii_array['»'] = '>>'; + +function toAscii(text) { + //var text = field.value; + var output = ''; + + for (position=0; position < text.length; position++) { + var tmp = text.substring(position,position+1); + + if (to_ascii_array[tmp] !== undefined) + tmp = to_ascii_array[tmp]; + + output += tmp; + } + + return output; +} \ No newline at end of file diff --git a/resources/themes/architect/views/layouts/partials/scripts.blade.php b/resources/themes/architect/views/layouts/partials/scripts.blade.php index e2c0bf45..99bf6794 100644 --- a/resources/themes/architect/views/layouts/partials/scripts.blade.php +++ b/resources/themes/architect/views/layouts/partials/scripts.blade.php @@ -17,4 +17,9 @@ @if(file_exists('js/custom.js')) +@endif + +@if(file_exists('js/template.js')) + + @endif \ No newline at end of file diff --git a/resources/views/components/attribute-type.blade.php b/resources/views/components/attribute-type.blade.php index 1fc305de..8d806bf5 100644 --- a/resources/views/components/attribute-type.blade.php +++ b/resources/views/components/attribute-type.blade.php @@ -7,6 +7,10 @@ {{ $o->name }} + @if($new && $template?->onChange($o->name)->count()) + + @endif + @if($o->hints->count()) [ diff --git a/resources/views/components/attribute.blade.php b/resources/views/components/attribute.blade.php index ad5572b4..435f675b 100644 --- a/resources/views/components/attribute.blade.php +++ b/resources/views/components/attribute.blade.php @@ -33,4 +33,16 @@ @endif - \ No newline at end of file + + +@if($new && ($x=$template?->onChange($o->name))?->count()) + @section('page-scripts') + + + + @append +@endif \ No newline at end of file diff --git a/resources/views/frames/create.blade.php b/resources/views/frames/create.blade.php index fb919d6d..41901d70 100644 --- a/resources/views/frames/create.blade.php +++ b/resources/views/frames/create.blade.php @@ -63,10 +63,11 @@ @break @case(2) - + + @foreach($o->getVisibleAttributes() as $ao) - + @endforeach @if(! $template) diff --git a/resources/views/frames/dn.blade.php b/resources/views/frames/dn.blade.php index 5d039073..1054f4ca 100644 --- a/resources/views/frames/dn.blade.php +++ b/resources/views/frames/dn.blade.php @@ -101,7 +101,7 @@
@php($up=(session()->pull('updated') ?: collect())) @foreach($o->getVisibleAttributes() as $ao) - + @endforeach @include('fragment.dn.add_attr') @@ -124,7 +124,7 @@
@foreach($o->getInternalAttributes() as $ao) - + @endforeach
diff --git a/templates/user_account.json b/templates/user_account.json index 61359c80..951491e0 100644 --- a/templates/user_account.json +++ b/templates/user_account.json @@ -15,7 +15,7 @@ "givenName": { "display": "First Name", "onchange": [ - "=autoFill(cn;%givenName% %sn%)", + "=autoFill(cn;%givenName% %sn/U%)", "=autoFill(uid;%givenName|0-1/l%%sn/l%)" ], "order": 1 @@ -23,7 +23,7 @@ "sn": { "display": "Last Name", "onchange": [ - "=autoFill(cn;%givenName% %sn%)", + "=autoFill(cn;%givenName% %sn/U%)", "=autoFill(uid;%givenName|0-1/l%%sn/l%)" ], "order": 2