diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index c757adb8..b76239c3 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -7,6 +7,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use App\Classes\LDAP\Schema\AttributeType; +use App\Classes\Template; use App\Exceptions\InvalidUsage; use App\Ldap\Entry; @@ -326,10 +327,10 @@ class Attribute implements \Countable, \ArrayAccess * @param bool $old Use old value * @param bool $new Enable adding values * @param bool $updated Has the entry been updated (uses rendering highlights)) - * @param string|null $template + * @param Template|null $template * @return View */ - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { if ($this->is_internal) // @note Internal attributes cannot be edited diff --git a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php index e738611d..ca4ac18d 100644 --- a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php +++ b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php @@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Binary; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute\Binary; -use App\Ldap\Entry; +use App\Classes\Template; use App\Traits\MD5Updates; /** @@ -15,7 +15,7 @@ final class JpegPhoto extends Binary { use MD5Updates; - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { return view('components.attribute.binary.jpegphoto') ->with('o',$this) diff --git a/app/Classes/LDAP/Attribute/CertificateList.php b/app/Classes/LDAP/Attribute/CertificateList.php index 8a56beaf..8701243a 100644 --- a/app/Classes/LDAP/Attribute/CertificateList.php +++ b/app/Classes/LDAP/Attribute/CertificateList.php @@ -2,9 +2,6 @@ namespace App\Classes\LDAP\Attribute; -use Carbon\Carbon; -use Illuminate\Support\Arr; - use App\Classes\LDAP\Attribute; use App\Traits\MD5Updates; diff --git a/app/Classes/LDAP/Attribute/Factory.php b/app/Classes/LDAP/Attribute/Factory.php index a4ee42e4..29dd58cf 100644 --- a/app/Classes/LDAP/Attribute/Factory.php +++ b/app/Classes/LDAP/Attribute/Factory.php @@ -3,7 +3,6 @@ namespace App\Classes\LDAP\Attribute; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Log; use App\Classes\LDAP\Attribute; diff --git a/app/Classes/LDAP/Attribute/Internal/Timestamp.php b/app/Classes/LDAP/Attribute/Internal/Timestamp.php index 5486846d..136080f7 100644 --- a/app/Classes/LDAP/Attribute/Internal/Timestamp.php +++ b/app/Classes/LDAP/Attribute/Internal/Timestamp.php @@ -5,14 +5,14 @@ namespace App\Classes\LDAP\Attribute\Internal; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute\Internal; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents an attribute whose values are timestamps */ final class Timestamp extends Internal { - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { // @note Internal attributes cannot be edited return view('components.attribute.internal.timestamp') diff --git a/app/Classes/LDAP/Attribute/KrbPrincipalKey.php b/app/Classes/LDAP/Attribute/KrbPrincipalKey.php index ec07e9df..5f7e3749 100644 --- a/app/Classes/LDAP/Attribute/KrbPrincipalKey.php +++ b/app/Classes/LDAP/Attribute/KrbPrincipalKey.php @@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute; -use App\Ldap\Entry; +use App\Classes\Template; use App\Traits\MD5Updates; /** @@ -17,7 +17,7 @@ final class KrbPrincipalKey extends Attribute protected(set) bool $no_attr_tags = TRUE; - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { return view('components.attribute.krbprincipalkey') ->with('o',$this) diff --git a/app/Classes/LDAP/Attribute/KrbTicketFlags.php b/app/Classes/LDAP/Attribute/KrbTicketFlags.php index 5f6e11fe..37547e87 100644 --- a/app/Classes/LDAP/Attribute/KrbTicketFlags.php +++ b/app/Classes/LDAP/Attribute/KrbTicketFlags.php @@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Support\Collection; use App\Classes\LDAP\Attribute; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents an attribute whose value is a Kerberos Ticket Flag @@ -50,7 +50,7 @@ final class KrbTicketFlags extends Attribute return $helpers; } - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { return view('components.attribute.krbticketflags') ->with('o',$this) diff --git a/app/Classes/LDAP/Attribute/ObjectClass.php b/app/Classes/LDAP/Attribute/ObjectClass.php index 8f850dc9..4dd1ea41 100644 --- a/app/Classes/LDAP/Attribute/ObjectClass.php +++ b/app/Classes/LDAP/Attribute/ObjectClass.php @@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Support\Collection; use App\Classes\LDAP\Attribute; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents an ObjectClass Attribute @@ -70,7 +70,7 @@ final class ObjectClass extends Attribute ->contains($value); } - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { return view('components.attribute.objectclass') ->with('o',$this) diff --git a/app/Classes/LDAP/Attribute/Password.php b/app/Classes/LDAP/Attribute/Password.php index 7a35f4d2..be7f8399 100644 --- a/app/Classes/LDAP/Attribute/Password.php +++ b/app/Classes/LDAP/Attribute/Password.php @@ -7,7 +7,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use App\Classes\LDAP\Attribute; -use App\Ldap\Entry; +use App\Classes\Template; use App\Traits\MD5Updates; /** @@ -80,7 +80,7 @@ final class Password extends Attribute return ($helpers=static::helpers())->has($id) ? new ($helpers->get($id)) : NULL; } - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { return view('components.attribute.password') ->with('o',$this) diff --git a/app/Classes/LDAP/Attribute/RDN.php b/app/Classes/LDAP/Attribute/RDN.php index be62c5e8..1995a0d6 100644 --- a/app/Classes/LDAP/Attribute/RDN.php +++ b/app/Classes/LDAP/Attribute/RDN.php @@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Support\Collection; use App\Classes\LDAP\Attribute; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents the RDN for an Entry @@ -35,7 +35,7 @@ final class RDN extends Attribute ]); } - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { return view('components.attribute.rdn') ->with('o',$this); diff --git a/app/Classes/LDAP/Attribute/Schema.php b/app/Classes/LDAP/Attribute/Schema.php index 5f2adef8..162cb9c6 100644 --- a/app/Classes/LDAP/Attribute/Schema.php +++ b/app/Classes/LDAP/Attribute/Schema.php @@ -2,12 +2,10 @@ namespace App\Classes\LDAP\Attribute; -use Illuminate\Contracts\View\View; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; use App\Classes\LDAP\Attribute; -use App\Ldap\Entry; /** * Represents an attribute whose values are schema related diff --git a/app/Classes/LDAP/Attribute/Schema/Generic.php b/app/Classes/LDAP/Attribute/Schema/Generic.php index 18b91804..77709e87 100644 --- a/app/Classes/LDAP/Attribute/Schema/Generic.php +++ b/app/Classes/LDAP/Attribute/Schema/Generic.php @@ -5,14 +5,14 @@ namespace App\Classes\LDAP\Attribute\Schema; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute\Schema; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents a Generic Schema Attribute */ class Generic extends Schema { - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { // @note Schema attributes cannot be edited return view('components.attribute.schema.generic') diff --git a/app/Classes/LDAP/Attribute/Schema/Mechanisms.php b/app/Classes/LDAP/Attribute/Schema/Mechanisms.php index d0c34519..6721390d 100644 --- a/app/Classes/LDAP/Attribute/Schema/Mechanisms.php +++ b/app/Classes/LDAP/Attribute/Schema/Mechanisms.php @@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Schema; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute\Schema; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents a Mechanisms Attribute @@ -34,7 +34,7 @@ final class Mechanisms extends Schema return parent::_get(config_path('ldap_supported_saslmechanisms.txt'),$string,$key); } - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { // @note Schema attributes cannot be edited return view('components.attribute.schema.mechanisms') diff --git a/app/Classes/LDAP/Attribute/Schema/OID.php b/app/Classes/LDAP/Attribute/Schema/OID.php index c1bcf269..42d59410 100644 --- a/app/Classes/LDAP/Attribute/Schema/OID.php +++ b/app/Classes/LDAP/Attribute/Schema/OID.php @@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Schema; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute\Schema; -use App\Ldap\Entry; +use App\Classes\Template; /** * Represents an OID Attribute @@ -35,7 +35,7 @@ final class OID extends Schema return parent::_get(config_path('ldap_supported_oids.txt'),$string,$key); } - public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View { // @note Schema attributes cannot be edited return view('components.attribute.schema.oid') diff --git a/app/Classes/Template.php b/app/Classes/Template.php index 97468e81..5e42909a 100644 --- a/app/Classes/Template.php +++ b/app/Classes/Template.php @@ -20,6 +20,8 @@ class Template $this->file = $file; try { + // @todo Load in the proper attribute objects and objectclass objects + // @todo Make sure we have a structural objectclass, or make the template invalid $this->template = json_decode($td->get($file),null,512,JSON_OBJECT_AS_ARRAY|JSON_THROW_ON_ERROR); } catch (\JsonException $e) { @@ -47,8 +49,159 @@ class Template return array_key_exists($key,$this->template); } - public function __toString(): string + private function autofill() { - return $this->invalid ? '' : Arr::get($this->template,'title','No Template Name'); + /* + autoFill:string + string is a literal string, and 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. + + 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. + K: 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 + */ + if (! preg_match('/;/',$arg)) { + system_message(array( + 'title'=>_('Problem with autoFill() in template'), + 'body'=>sprintf('%s (%s)',_('There is only 1 argument, when there should be two'),$attribute->getName(false)), + 'type'=>'warn')); + + return; + } + + list($attr,$string) = preg_split('(([^,]+);(.*))',$arg,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + preg_match_all('/%(\w+)(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?(?:\|(.))?%/U',$string,$matchall); + //print"
";print_r($matchall); //0 = highlevel match, 1 = attr, 2 = subst, 3 = mod, 4 = delimiter + + if (! isset($attribute->js['autoFill'])) + $attribute->js['autoFill'] = ''; + + $formula = $string; + $formula = preg_replace('/^([^%])/','\'$1',$formula); + $formula = preg_replace('/([^%])$/','$1\'',$formula); + + # Check that our attributes match our schema attributes. + foreach ($matchall[1] as $index => $checkattr) { + $sattr = $this->getServer()->getSchemaAttribute($checkattr); + + # If the attribute is the same as in the XML file, then dont need to do anything. + if (! $sattr || ! strcasecmp($sattr->getName(),$checkattr)) + continue; + + $formula = preg_replace("/$checkattr/",$sattr->getName(),$formula); + $matchall[1][$index] = $sattr->getName(); + } + + $elem_id = 0; + + foreach ($matchall[0] as $index => $null) { + $match_attr = strtolower($matchall[1][$index]); + $match_subst = $matchall[2][$index]; + $match_mod = $matchall[3][$index]; + $match_delim = $matchall[4][$index]; + + $substrarray = array(); + + if (! isset($varcount[$match_attr])) + $varcount[$match_attr] = 0; + else + $varcount[$match_attr]++; + + $js_match_attr = $match_attr; + $match_attr = $js_match_attr.'xx'.$varcount[$match_attr]; + + $formula = preg_replace('/%'.$js_match_attr.'([|\/%])/i','%'.$match_attr.'$1',$formula,1); + + $attribute->js['autoFill'] .= sprintf(" var %s;\n",$match_attr); + $attribute->js['autoFill'] .= sprintf( + " var elem$elem_id = document.getElementById(pre+'%s'+suf);\n". + " if (!elem$elem_id) return;\n", $js_match_attr); + + if (strstr($match_mod,'T')) { + $attribute->js['autoFill'] .= sprintf(" %s = elem$elem_id.options[elem$elem_id.selectedIndex].text;\n", + $match_attr); + } else { + $attribute->js['autoFill'] .= sprintf(" %s = elem$elem_id.value;\n",$match_attr); + } + + $elem_id++; + + if (strstr($match_mod,'k')) { + preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray); + if (isset($substrarray[1][0])) { + $tok_idx = $substrarray[1][0]; + } else { + $tok_idx = '0'; + } + $attribute->js['autoFill'] .= sprintf(" %s = %s.split(':')[%s];\n",$match_attr,$match_attr,$tok_idx); + + } elseif (strstr($match_mod,'K')) { + preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray); + if (isset($substrarray[1][0])) { + $tok_idx = $substrarray[1][0]; + } else { + $tok_idx = '0'; + } + + if ($match_delim == '') { + $delimiter = ' '; + } else { + $delimiter = preg_quote($match_delim); + } + $attribute->js['autoFill'] .= sprintf(" %s = %s.split('%s')[%s];\n",$match_attr,$match_attr,$delimiter,$tok_idx); + + } else { + preg_match_all('/([0-9]*)-([0-9]*)/',trim($match_subst),$substrarray); + if ((isset($substrarray[1][0]) && $substrarray[1][0]) || (isset($substrarray[2][0]) && $substrarray[2][0])) { + $attribute->js['autoFill'] .= sprintf(" %s = %s.substr(%s,%s);\n", + $match_attr,$match_attr, + $substrarray[1][0] ? $substrarray[1][0] : '0', + $substrarray[2][0] ? $substrarray[2][0] : sprintf('%s.length',$match_attr)); + } + } + + if (strstr($match_mod,'l')) { + $attribute->js['autoFill'] .= sprintf(" %s = %s.toLowerCase();\n",$match_attr,$match_attr); + } + if (strstr($match_mod,'U')) { + $attribute->js['autoFill'] .= sprintf(" %s = %s.toUpperCase();\n",$match_attr,$match_attr); + } + if (strstr($match_mod,'A')) { + $attribute->js['autoFill'] .= sprintf(" %s = toAscii(%s);\n",$match_attr,$match_attr); + } + + # Matchfor only entry without modifiers. + $formula = preg_replace('/^%('.$match_attr.')%$/U','$1 + \'\'',$formula); + # Matchfor only entry with modifiers. + $formula = preg_replace('/^%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?(?:\|(.))?%$/U','$1 + \'\'',$formula); + # Matchfor begining entry. + $formula = preg_replace('/^%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?(?:\|(.))?%/U','$1 + \'',$formula); + # Matchfor ending entry. + $formula = preg_replace('/%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?(?:\|(.))?%$/U','\' + $1 ',$formula); + # Match for entries not at begin/end. + $formula = preg_replace('/%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?(?:\|(.))?%/U','\' + $1 + \'',$formula); + $attribute->js['autoFill'] .= "\n"; + } + + $attribute->js['autoFill'] .= sprintf(" fillRec(pre+'%s'+suf, %s); // %s\n",strtolower($attr),$formula,$string); + $attribute->js['autoFill'] .= "\n"; } } \ No newline at end of file diff --git a/app/View/Components/Attribute.php b/app/View/Components/Attribute.php index 94694fc0..50ba0775 100644 --- a/app/View/Components/Attribute.php +++ b/app/View/Components/Attribute.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\View\View; use Illuminate\View\Component; use App\Classes\LDAP\Attribute as LDAPAttribute; +use App\Classes\Template; class Attribute extends Component { @@ -14,12 +15,12 @@ class Attribute extends Component public bool $new; public bool $old; public bool $updated; - public bool $template; + public ?Template $template; /** * Create a new component instance. */ - public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,string $template=NULL) + public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL) { $this->o = $o; $this->edit = $edit; @@ -38,7 +39,12 @@ class Attribute extends Component { return $this->o ? $this->o - ->render(edit: $this->edit,old: $this->old,new: $this->new,template: $this->template,updated: $this->updated) + ->render( + edit: $this->edit, + old: $this->old, + new: $this->new, + updated: $this->updated, + template: $this->template) : __('Unknown'); } } \ No newline at end of file diff --git a/resources/views/components/attribute/password.blade.php b/resources/views/components/attribute/password.blade.php index 6e6de066..de11ae20 100644 --- a/resources/views/components/attribute/password.blade.php +++ b/resources/views/components/attribute/password.blade.php @@ -4,7 +4,7 @@ @foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value) @if($edit)-diff --git a/resources/views/frames/create.blade.php b/resources/views/frames/create.blade.php index 2454da57..fb919d6d 100644 --- a/resources/views/frames/create.blade.php +++ b/resources/views/frames/create.blade.php @@ -66,7 +66,7 @@+ ($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value),'bg-success-subtle'=>$updated]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)> diff --git a/resources/views/fragment/template/dn.blade.php b/resources/views/fragment/template/dn.blade.php index db69f9e6..fc0961a3 100644 --- a/resources/views/fragment/template/dn.blade.php +++ b/resources/views/fragment/template/dn.blade.php @@ -9,7 +9,7 @@ @php($up=(session()->pull('updated') ?: collect())) @foreach($o->getVisibleAttributes()->filter(fn($item)=>$template->attributes->map('strtolower')->contains($item->name_lc)) as $ao) -+ @endforeach @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 e8438d8a..5d039073 100644 --- a/resources/views/frames/dn.blade.php +++ b/resources/views/frames/dn.blade.php @@ -75,8 +75,8 @@ - @foreach($o->templates as $template => $name) - $loop->index === 0])> {{ $name }} + @foreach($o->templates as $template) + $loop->index === 0])> {{ $template->title }} @endforeach @if($o->templates->count()) (! $o->templates->count())])>{{ __('LDAP Entry') }} @@ -85,9 +85,9 @@- @foreach($o->templates as $template => $name) -$loop->index === 0]) id="template-{{$template}}" role="tabpanel"> - @include('fragment.template.dn',['template'=>$o->template($template)]) + @foreach($o->templates as $template) +$loop->index === 0]) id="template-{{ $template->name }}" role="tabpanel"> + @include('fragment.template.dn',['template'=>$template])@endforeach