<?php
require_once 'htmlelements.class.php';
/**
 * ======================================================================
 * 
 * RENDERER FOR ADNINLTE template https://adminlte.io
 * DOCS: https://adminlte.io/docs/3.2/
 *       https://adminlte.io/themes/v3/index3.html
 * 
 * ======================================================================
 *
 * @author Axel
 */
class renderadminlte {

    var $aPresets=[

        'bgcolor'=>[
            'description'=>'background colors',
            'group'=>'styling',
            'values'=>[
                // https://adminlte.io/themes/v3/pages/UI/general.html
                ''=>'no value',
                'indigo'=>'indigo',
                'lightblue'=>'',
                'navy'=>'',
                'purple'=>'',
                'fuchsia'=>'',
                'pink'=>'',
                'maroon'=>'',
                'orange'=>'',
                'lime'=>'',
                'teal'=>'',
                'olive'=>'',
        
                'black'=>'black',
                'dark'=>'dark gray',
                'gray'=>'gray', 
                'light'=>'light gray', 
            ]
        ],
    
        'type'=>[
            'description'=>'type or status like info/ warning/ danger to define a color',
            'group'=>'styling',
            'values'=>[
                ''=>'no value',
                'danger'=>'red',
                'info'=>'aqua',
                'primary'=>'blue',
                'secondary'=>'gray',
                'success'=>'green',
                'warning'=>'yellow',
                'dark'=>'dark gray',
                'gray'=>'gray', 
            ]
        ],
        'shadow'=>[
            'description'=>'use a shadow',
            'group'=>'styling',
            'values'=>[
                ''=>'no value', 
                'none'=>'none',
                'small'=>'small', 
                'regular'=>'regular',
                'large'=>'large'
            ]
        ],
        'size'=>[
            'description'=>'set a size',
            'group'=>'styling',
            'values'=>[
                ''=>'no value',
                'lg'=>'',
                'sm'=>'',
                'xs'=>'',
                'flat'=>'',
            ]
        ],
        'variant'=>[
            'description'=>'coloring style',
            'group'=>'styling',
            'values'=>[
                ''=>'no value',
                'outline'=>'small stripe on top',
                'solid'=>'full filled widget',
                'gradient'=>'full filled with gradient',
            ]
        ],
        'visibility'=>[
            'description'=>'',
            'group'=>'customizing',
            'values'=>[
                ''=>'no value', 
                '0'=>'hide', 
                '1'=>'show',
            ]
        ],
        // for keys: state
        'windowstate'=>[
            'description'=>'state of a resizable widget',
            'group'=>'customizing',
            'values'=>[
                ''=>'no value', 
                'collapsed'=>'header only', 
                'maximized'=>'full window',
            ]
        ],
        // for keys: dismissable
        'yesno'=>[
            'description'=>'',
            'group'=>'customizing',
            'values'=>[
                ''=>'no value', 
                '0'=>'no', 
                '1'=>'yes',
            ]
        ],
    ];

    var $_aValueMappings=[
        'shadow'=>[
            'default'  => '',
            'none'     => 'shadow-none',
            'small'    => 'shadow-small',
            'regular'  => 'shadow',
            'large'    => 'shadow-lg',
        ]
    ];

    var $_aElements=[];
    
    /**
     * instance of htmlelements
     * @var object
     */
    var $_oHtml=false;
    
    
    // ----------------------------------------------------------------------
    // 
    // CONSTRUCTOR
    // 
    // ----------------------------------------------------------------------
    public function __construct() {
        $this->_oHtml=new htmlelements();
        $this->_initElements();
        return true;
    }

    // ----------------------------------------------------------------------
    // 
    // PRIVATE FUNCTIONS 
    // 
    // ----------------------------------------------------------------------
    
    /**
     * verify if an item has a correct value
     * it returns false if a key is not defined to be checked
     * it returns true if it was validated successfully
     * it dies with an errror, if a value check failed
     * 
     * @param string  $sType      type; key in $_aValidItems; one of bgcolor|color|type|size
     * @param string  $sValue     value to check
     * @param string  $sReferrer  optional: method that called this function
     */
    protected function _DELETE_ME___checkValue($sType, $sValue, $sReferrer=false){
        if (!$sValue || !array_key_exists($sType, $this->_aValidItems)){
            return false;
        }
        if(array_search($sValue, $this->_aValidItems[$sType])===false){
            echo "ERROR: ".($sReferrer ? $sReferrer.' - ' : '')."value [$sValue] is not a valid for type [$sType]; it must be one of ".implode("|", $this->_aValidItems[$sType]).'<br>';
        }
        return true;
    }

    /**
     * used in cosntructor
     * initialize all element definitions
     */
    protected function _initElements(){
        $this->_aElements=[

            // ------------------------------------------------------------
            'alert'=>[
                'label'=>'Alert',
                'description'=>'Colored box with title and a text',
                'method'=>'getAlert',
        
                'params'=>[
                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'warning'],
                    'dismissible' => ['select'=>$this->aPresets['yesno'],    'example_value'=>''],
                    'title'       => [
                        'description'=>'Title in a bit bigger font', 
                        'group'=>'content',
                        'example_value'=>'Alert title'
                    ],
                    'text'        => [
                        'description'=>'Message text', 
                        'group'=>'content',
                        'example_value'=>'I am a message. Read me, please.'
                    ],
                ]
            ],
            // ------------------------------------------------------------
            'badge'=>[
                'label'=>'Badge',
                'description'=>'Tiny additional info; mostly as counter',
                'method'=>'getBadge',
        
                'params'=>[
                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'danger'],
                    'bgcolor'     => ['select'=>$this->aPresets['bgcolor'],  'example_value'=>''],
                    'class'       => [
                        'group'=>'styling', 
                        'description'=>'optional: css classes', 
                        'example_value'=>''
                    ],
                    'id'          => [
                        'group'=>'customizing', 
                        'description'=>'optional: id attribute', 
                        'example_value'=>''
                    ],
                    'title'       => [
                        'group'=>'content', 
                        'description'=>'optional: title attribute for mouseover', 
                        'example_value'=>'Errors: 5'
                    ],
                    'text'        => [
                        'group'=>'content', 
                        'description'=>'Text or value in the badge', 
                        'example_value'=>'5'
                    ],
                ]
            ],
            // ------------------------------------------------------------
            'button'=>[
                'label'=>'Button',
                'description'=>'Buttons<br>In this component you can add other parmeter keys too - these will be added as attributes in the button tag.',
                'method'=>'getButton',
        
                'params'=>[
                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'primary'],
                    'size'        => ['select'=>$this->aPresets['size'],     'example_value'=>''],
                    'class'       => [
                        'group'=>'styling', 
                        'description'=>'optional: css classes', 
                        'example_value'=>''
                    ],
                    'text'        => [
                        'group'=>'content', 
                        'description'=>'Text/ html code on the button', 
                        'example_value'=>'Click me'
                    ],
                ]
            ],
            // ------------------------------------------------------------
            'callout'=>[
                'label'=>'Callout',
                'description'=>'Kind of infobox',
                'method'=>'getCallout',
        
                'params'=>[
                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'danger'],
                    'class'       => [
                        'group'=>'styling', 
                        'description'=>'optional: css classes', 
                        'example_value'=>''
                    ],
                    'title'       => [
                        'group'=>'content', 
                        'description'=>'Title in a bit bigger font', 
                        'example_value'=>'I am a callout'
                    ],
                    'text'        => [
                        'group'=>'content',
                        'description'=>'Message text', 
                        'example_value'=>'Here is some description to whatever.'
                    ],
                ]
            ],
            // ------------------------------------------------------------
            'card'=>[
                'label'=>'Card',
                'description'=>'Content box with header, text, footer',
                'method'=>'getCard',
        
                'params'=>[
                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'primary'],
                    'variant'     => ['select'=>$this->aPresets['variant'],  'example_value'=>'outline'],
                    'class'       => [
                        'group'=>'styling', 
                        'description'=>'optional: css classes', 
                        'example_value'=>''
                    ],
                    'state'       => [
                        'group'=>'customizing', 
                        'select'=>$this->aPresets['windowstate'], 
                        'example_value'=>''
                    ],
        
                    'tb-collapse' => ['description'=>'show minus symbol as collapse button', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
                    'tb-expand'   => ['description'=>'show plus symbol to expand card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
                    'tb-maximize' => ['description'=>'show maximize button for fullscreen', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
                    'tb-minimize' => ['description'=>'show minimize button to minimize', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
                    'tb-remove'   => ['description'=>'show cross symbol to remove card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
        
                    'title'       => [
                        'group'=>'content', 
                        'description'=>'Title in the top row', 
                        'example_value'=>'I am a card'
                    ],
                    'tools'       => [
                        'group'=>'content', 
                        'description'=>'Html code for the top right', 
                        'example_value'=>''
                    ],
                    'text'        => [
                        'group'=>'content', 
                        'description'=>'Main content', 
                        'example_value'=>'Here is some beautiful content.'
                    ],
                    'footer'      => [
                        'group'=>'content', 
                        'description'=>'optional: footer content', 
                        'example_value'=>'Footer'
                    ],
                ]
            ],
            // ------------------------------------------------------------
            'infobox'=>[
                'label'=>'Info box',
                'description'=>'Box with icon to highlight a single value; optional with a progress bar',
                'method'=>'getInfobox',
        
                'params'=>[
                    'type'=>['select'=>$this->aPresets['type'],     'example_value'=>''],
                    'iconbg'=>['select'=>$this->aPresets['type'],   'example_value'=>'info'],
                    'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''],
                    'icon'=>[
                        'group'=>'content', 
                        'description'=>'css class for an icon', 
                        'example_value'=>'far fa-thumbs-up'
                    ],
                    'text'=>[
                        'group'=>'content', 
                        'description'=>'short information text', 
                        'example_value'=>'Likes'
                    ],
                    'number'=>[
                        'group'=>'content', 
                        'description'=>'a number to highlight', 
                        'example_value'=>"41,410"
                    ],
                    'progressvalue'=>[
                        'group'=>'content', 
                        'description'=>'optional: progress value 0..100 to draw a progress bar', 
                        'example_value'=>70
                    ],
                    'progresstext'=>[
                        'group'=>'content', 
                        'description'=>'optional: text below progress bar', 
                        'example_value'=>'70% Increase in 30 Days'
                    ]
                ]
            ],
            // ------------------------------------------------------------
            'smallbox'=>[
                'label'=>'Small box',
                'description'=>'Solid colored box to highlight a single value; optional with a link',
                'method'=>'getSmallbox',
        
                'params'=>[
                    'type'=>['select'=>$this->aPresets['type'],     'example_value'=>'info'],
                    'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''],
        
                    'icon'=>['group'=>'content', 'description'=>'css class for an icon', 'example_value'=>'fas fa-shopping-cart'],
        
                    'text'=>['group'=>'content', 'description'=>'short information text', 'example_value'=>'New orders'],
                    'number'=>['group'=>'content', 'description'=>'a number to highlight', 'example_value'=>"150"],
                    'url'=>['group'=>'content', 'description'=>'optional: url to set a link on the bottom', 'example_value'=>'#'],
                    'linktext'=>['group'=>'content', 'optional: description'=>'linktext', 'example_value'=>'More info']
                ]
            ],
        ];
    }
    /**
     * helper function: a shortcut for $this->_oHtml->getTag
     * @param  string  $sTag         name of html tag
     * @param  array   $aAttributes  array of its attributes
     */
    protected function _tag($sTag, $aAttributes, $sContent=false){
        if ($sContent){
            $aAttributes['label']=(isset($aAttributes['label']) ? $aAttributes['label'] : '') . $sContent;
        }
        return $this->_oHtml->getTag($sTag, $aAttributes);
    }
    // ----------------------------------------------------------------------
    // 
    // PUBLIC FUNCTIONS AdminLTE 3.2
    // 
    // ----------------------------------------------------------------------

    /**
     * render a page by using template 
     * @param  string  $stemplate  html template with placeholders
     * @param  array   $aReplace   key = what to replace .. value = new value
     */
    public function render($sTemplate, $aReplace){
        return str_replace(
            array_keys($aReplace),
            array_values($aReplace),
            $sTemplate
        );
    }
    

    /**
     * add a wrapper: wrap some content into a tag
     * 
     * @param  string  $sContent  html content inside
     * @return string
     */
    public function addWrapper($sTag, $aOptions, $sContent){
        $aOptions['label']=$sContent;
        return $this->_tag($sTag, $aOptions)."\n";
    }

    // ----------------------------------------------------------------------
    // 
    // PUBLIC FUNCTIONS :: NAVIGATION
    // 
    // ----------------------------------------------------------------------


    /** 
     * get a single navigation item on top bar
     * 
     * @param  array    $aLink   Array of html attributes; key children can contain subitems
     * @param  integer  $iLevel  Menu level; 1=top bar; 2=pupup menu with subitems
     * @return string
     */
    public function getNavItem($aLink, $iLevel=1){
        static $iCounter;
        if(!isset($iCounter)){
            $iCounter=0;
        }
        
        // special menu entry: horizontal bar (label is "-")
        if($aLink['label']=='-'){
            return '<div class="dropdown-divider"></div>';
        }

        $aChildren=isset($aLink['children']) && is_array($aLink['children']) && count($aLink['children']) ? $aLink['children'] : false;

        $aLink['class']='nav-link'
            . (isset($aLink['class']) ? ' '.$aLink['class'] : '')
            . ($aChildren ? ' dropdown-toggle' : '')
            ;
        if($aChildren){
            $iCounter++;
            $sNavId="navbarDropdown".$iCounter;
            $aLink=array_merge($aLink,[
                'id'=>$sNavId,
                'role'=>"button",
                'data-toggle'=>"dropdown",
                'aria-haspopup'=>"true",
                'aria-expanded'=>"false",
            ]);
            unset($aLink['children']); // remove from html attributes to draw
        }
        $sReturn=$this->_tag('a', $aLink)."\n";
        if($aChildren){ 
            $iLevel++;
            $sReturn.=$this->addWrapper(
                    'div', 
                    [ 
                        'aria-labelledby'=> $sNavId,
                        'class'=>'dropdown-menu'
                    ], 
                    $this->getNavItems($aChildren, $iLevel)
                )."\n";
            $iLevel--;
        }
   
        if($iLevel==1){
            $sLiClass='nav-item'.($aChildren ? ' dropdown' : '');
            $sReturn=$this->addWrapper(
                'li', 
                ['class'=>$sLiClass],
                $sReturn 
            );
        }
        return $sReturn;
    }
    /** 
     * get a navigation item for top bar
     * 
     */
    public function getNavItems($aLinks, $iLevel=1){
        $sReturn='';
        if (!$aLinks || !is_array($aLinks) || !count($aLinks)){
            return false;
        }
        foreach($aLinks as $aLink){
            $sReturn.=$this->getNavItem($aLink, $iLevel);
        }
        return $sReturn;
    }
    /**
     * get a top left navigation for a top navigation bar
     * 
     * @example
     * <code>
     * $aTopnav=[
     *     ['href'=>'/index.php', 'label'=>'MyHome' , 'class'=>'bg-gray'],
     *     ['href'=>'#',          'label'=>'Contact'],
     *     ['href'=>'#',          'label'=>'Help',
     *         'children'=>[
     *             ['href'=>'#',      'label'=>'FAQ'],
     *             ['label'=>'-'],
     *             ['href'=>'#',      'label'=>'Support'],
     *         ]
     *     ]
     * ];
     *  echo '<nav class="main-header navbar navbar-expand navbar-white navbar-light">'
     *     .$renderAdminLTE->getTopNavigation($aTopnav)
     *     .'</nav>'
     * </code>
     * 
     * @param  array  $aNavitems   array of navigation items/ tree
     * @param  array  $aUlOptions  array of html attrubutes for wrapping UL tag
     * @return string
     */
    public function getTopNavigation($aNavItems, $aUlOptions=['class'=>'navbar-nav']){
        array_unshift($aNavItems, ['class'=>'nav-link', 'data-widget'=>'pushmenu', 'href'=>'#', 'role'=>'button', 'label'=>'<i class="fas fa-bars"></i>']);
        return $this->addWrapper('ul', $aUlOptions, $this->getNavItems($aNavItems));
    }

    // ----------------------------------------------------------------------


    /** 
     * get a navigation items for sidebar
     * @param  array  $aLinks  list of link items
     */
    public function getNavi2Items($aLinks){
        $sReturn='';
        if (!$aLinks || !is_array($aLinks) || !count($aLinks)){
            return false;
        }
        foreach($aLinks as $aLink){
            // to render active or open links: 
            $aLink['class']='nav-link' . (isset($aLink['class']) ? ' '.$aLink['class'] : '');

            $aChildren=isset($aLink['children']) ? $aLink['children'] : false;
            $aLiClass='nav-item' . ($aChildren && strstr($aLink['class'], 'active') ? ' menu-open' : '');
            $sSubmenu='';
            if($aChildren){
                unset($aLink['children']);                
                $aLink['label'].='<i class="right fas fa-angle-left"></i>';
                $sSubmenu.=$this->getSidebarNavigation($aChildren, ['class'=>'nav nav-treeview']);
            }
            $aLink['label']=$this->addWrapper('p', [], $aLink['label']);
            $sReturn.=$this->addWrapper(
                'li', ['class'=>$aLiClass],
                $this->_tag('a', $aLink).$sSubmenu
            )."\n";
        }
        return $sReturn;
    }

    /**
     * 
     */
    public function getSidebarNavigation($aNavItems, $aUlOptions=[
            'class'=>'nav nav-pills nav-sidebar flex-column nav-flat_ nav-child-indent',
            'data-widget'=>'treeview',
            'role'=>'menu',
            'data-accordion'=>'false'
            ])
    {
        return $this->addWrapper(
                'ul', $aUlOptions, 
                $this->getNavi2Items($aNavItems)
            )."\n";
    }

    // ----------------------------------------------------------------------
    // 
    // PUBLIC FUNCTIONS :: CONTENT - BASIC FUNCTIONS
    // 
    // ----------------------------------------------------------------------
    
    /**
     * add page row
     * 
     * @param  string  $sContent  html content inside
     * @return string
     */
    public function addRow($sContent){
        return $this->addWrapper('div', ['class'=>'row'], $sContent);
    }

    /**
     * add page column
     * 
     * @param  string   $sContent  html content inside
     * @param  integer  $iCols     column width; 12 = full width
     * @param  string   $sFloat    css value for float attribute; default=false
     * @return string
     */
    public function addCol($sContent, $iCols=6, $sFloat=false){
        return $this->addWrapper('div', ['class'=>'col-md-'.$iCols, 'style'=>'float:'.$sFloat], $sContent);
    }

    // ----------------------------------------------------------------------
    // 
    // PUBLIC FUNCTIONS :: CONTENT - WIDGET HELPERS
    // 
    // ----------------------------------------------------------------------


    /**
     * get a list of all defined components that can be rendered
     * @param  {bool}  $bSendData  flag: send including subkeys of the hash; default: false (keys only)
     * @return {array}
     */
    public function getComponents($bSendData=false){
        return $bSendData
            ? $this->_aElements
            : array_keys($this->_aElements)
        ;
    }
    /**
     * get data of a component
     * @param  {string}  $sComponent  id of the component
     * @return {array}
     */
    public function getComponent($sComponent){
        if(!isset($this->_aElements[$sComponent])){
            return false;
        }
        $aReturn=array_merge(['id'=>$sComponent], $this->_aElements[$sComponent]);
        unset($aReturn['params']);
        return $aReturn;
    }

    /**
     * get parameter keys of a component
     * @param  {string}  $sComponent  id of the component
     * @param  {bool}    $bSendData  flag: send including subkeys of the hash; default: false (keys only)
     * @return {array}
     */
    public function getComponentParamkeys($sComponent, $bSendData=false){
        if(!isset($this->_aElements[$sComponent])){
            return false;
        }
        $aKeys=array_keys($this->_aElements[$sComponent]['params']);
        if(!$bSendData){
            return $aKeys;
        }
        $aReturn=[];
        foreach($aKeys as $sKey){
            $aReturn[$sKey]=$this->getComponentParamkey($sComponent, $sKey);
        }
        // $aReturn=$this->_aElements[$sComponent]['params'];
        return $aReturn;
    }

    /**
     * get information a parameter keys of a component
     * @param  {string}  $sComponent  id of the component
     * @param  {string}  $sKey        key in the options array
     * @return {array}
     */
    public function getComponentParamkey($sComponent, $sKey){
        if(!isset($this->_aElements[$sComponent]['params'][$sKey])){
            return false;
        }
        $aReturn=$this->_aElements[$sComponent]['params'][$sKey];
        // get description from a preset
        if(!isset($aReturn['description']) && isset($aReturn['select']['description'])){
            $aReturn['description']=$aReturn['select']['description'];
        }
        if(!isset($aReturn['group']) && isset($aReturn['select']['group'])){
            $aReturn['group']=$aReturn['select']['group'];
        }
        return $aReturn;
    }

    /**
     * get a flat list of valid parameters for a key in a component
     * @param  {string}  $sComponent  id of the component
     * @param  {string}  $sKey        key in the options array
     * @return {array}
     */
    public function getValidParamValues($sComponent, $sKey){
        $aOptionkey=$this->getComponentParamkey($sComponent, $sKey);
        if(!$aOptionkey || !isset($aOptionkey['select']['values'])){
            return false;
        }
        return array_keys($aOptionkey['select']['values']);
    }


    // ----------------------------------------------------------------------

    /**
     * helper: add a css value with prefix
     * this handles option keys in get[COMPONENT] methods
     * if a value is set then this function returns a space + prefix (param 2) + value
     * @param  {string}  $sValue   option value
     * @param  {string}  $sPrefix  prefix in front of css value
     * @return {string}
     */
    protected function _addClassValue($sValue, $sPrefix=''){
        return $sValue ? ' '.$sPrefix.$sValue : '';
    }

    /**
     * helper function for get[COMPONENTNAME] methods:
     * ensure that all wanted keys exist in an array. Non existing keys will be added with value false
     * @return array
     */
    protected function _ensureOptions($sComponent, $aOptions=[]){

        $aParams=$this->getComponentParamkeys($sComponent, 0);
        if(!$aParams){
            $aOptions['_infos'][]="Warning: no definition was found for component $sComponent.";
            return $aOptions;
        }
        foreach ($aParams as $sKey){
            if(!isset($aOptions) || !isset($aOptions[$sKey])){
                $aOptions[$sKey]=false;
                if(!isset($aOptions['_infos'])){
                    $aOptions['_infos']=[];
                }
                $aOptions['_infos'][]="added missing key: $sKey";
            }

            // $aParamdata
            $aValidvalues=$this->getValidParamValues($sComponent, $sKey);
            if($aValidvalues){
                if(array_search($aOptions[$sKey], $aValidvalues)===false){
                    echo "ERROR: [".$sComponent."] value &quot;".$aOptions[$sKey]."&quot; is not a valid for param key [".$sKey."]; it must be one of ".implode("|", $aValidvalues).'<br>';
                }
            }
    
            // $this->_checkValue($sKey, $aOptions[$sKey], __METHOD__);
        }
        // echo '<pre>' . print_r($aOptions, 1) . '</pre>';
        return $aOptions;
    }
    // ----------------------------------------------------------------------
    // 
    // PUBLIC FUNCTIONS :: CONTENT - WIDGETS
    // 
    // ----------------------------------------------------------------------

    /**
     * return a alert box      
     * https://adminlte.io/themes/v3/pages/UI/general.html
     * 
     * @param type $aOptions  hash with keys for all options
     *                          - type - one of [none]|danger|info|primary|success|warning
     *                          - dismissible - if dismissible - one of true|false; default: false
     *                          - title
     *                          - text
     * @return string
     */
    public function getAlert($aOptions){
        $aOptions=$this->_ensureOptions('alert', $aOptions);
        $aAlertIcons=[
            'danger'=>'icon fas fa-ban',
            'info'=>'icon fas fa-info',
            'warning'=>'icon fas fa-exclamation-triangle',
            'success'=>'icon fas fa-check',
        ];

        $aElement=[
            'class'=>'alert'
                . $this->_addClassValue($aOptions['type'], 'alert-')
                . $this->_addClassValue($aOptions['dismissible'], 'alert-')
                ,
            'label'=>''
                . ($aOptions['dismissible'] ? '<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' : '')
                . $this->_tag('h5', [
                    'label'=> ''
                        .(isset($aAlertIcons[$aOptions['type']]) ? '<i class="'.$aAlertIcons[$aOptions['type']].'"></i> ' : '')
                        .$aOptions['title']
                    ])
                .$aOptions['text']
        ];

        return $this->_tag('div', $aElement);
    }

    /**
     * get html code for a badge
     * 
     * Examples
     * <span class="badge badge-danger navbar-badge">3</span>
     * <span title="3 New Messages" class="badge badge-warning">3</span>
     * <span class="right badge badge-danger">New</span>
     * <span class="badge badge-danger float-right">$350</span>
     * 
     * @param array $aOptions  hash with keys for all options
     *                          - bgcolor - background color (without prefix "bg")
     *                          - class   - css class
     *                          - id      - optional: id attribute
     *                          - text    - visible text
     *                          - title   - optional: title attribute
     *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
     */
    public function getBadge($aOptions){
        $aOptions=$this->_ensureOptions('badge', $aOptions);
        $aElement=[];
        $aElement['class']='badge'
            . $this->_addClassValue($aOptions['class'],   '')
            . $this->_addClassValue($aOptions['type'],    'badge-')
            . $this->_addClassValue($aOptions['bgcolor'], 'bg-')
            ;
        if ($aOptions['id']){
            $aElement['id']=$aOptions['id'];
        }
        $aElement['title']=$aOptions['title'];
        $aElement['label']=$aOptions['text'];

        return $this->_tag('span', $aElement);
    }

    /**
     * Get a button.
     * You can use any other key that are not named here. Those keys will be rendered 
     * as additional html attributes without modification.
     * https://adminlte.io/themes/v3/pages/UI/buttons.html
     * 
     * <button type="button" class="btn btn-block btn-default">Default</button>
     * @param type $aOptions  hash with keys for all options
     *                          - type  - one of [none]|danger|dark|info|primary|secondary|success|warning
     *                          - size  - one of [none]|lg|sm|xs|flat
     *                          - class - any css class for customizing, eg. "disabled"
     *                          - icon  - not supported yet
     *                          - text  - text on button
     * @return string
     */
    public function getButton($aOptions){
        $aOptions=$this->_ensureOptions('button', $aOptions);
        $aElement=$aOptions;
        $aElement['class']='btn'
            . $this->_addClassValue($aOptions['type'],    'btn-')
            . $this->_addClassValue($aOptions['size'],    'btn-')
            . $this->_addClassValue($aOptions['class'],   '')
            ;
        $aElement['label']=$aOptions['text'] ? $aOptions['text'] : '&nbsp;';
        foreach(['_infos', 'type', 'size', 'icon', 'text'] as $sDeleteKey){
            unset($aElement[$sDeleteKey]);
        }
        return $this->_tag('button', $aElement);
    }

    /**
     * get a callout (box with coloered left border; has type, title + text)
     * https://adminlte.io/themes/v3/pages/UI/general.html
     * 
     * @param type $aOptions  hash with keys for all options
     *                        >> styling
     *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
     *                          - class   - optional css class
     * 
     *                        >> texts/ html content
     *                          - title   - text: title of the card
     *                          - text    - text: content of the card
     * @return string
     */
    public function getCallout($aOptions){
        $aOptions=$this->_ensureOptions('callout', $aOptions);
        $sClass='callout'
            . $this->_addClassValue($aOptions['type'],    'callout-')
            . $this->_addClassValue($aOptions['class'],   '')
            ;

        return $this->addWrapper(
            'div', ['class'=>$sClass],
            ($aOptions['title'] ? $this->_tag('h5', ['label'=>$aOptions['title']]) : '')
            .($aOptions['text'] ? $this->_tag('p', ['label'=>$aOptions['text']]) : '')
        );
    }

    /**
     * get a card
     * https://adminlte.io/docs/3.2/components/cards.html
     * https://adminlte.io/docs/3.2/javascript/card-widget.html
     * 
     * @param type $aOptions  hash with keys for all options
     *                        >> styling
     *                          - variant: "default"  - titlebar is colored
     *                                     "outline"  - a small stripe on top border is colored
     *                                     "solid"    - whole card is colored
     *                                     "gradient" - whole card is colored with a gradient
     *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
     *                          - class   - any css class for customizing, eg. "disabled"
     *                          - state   - one of [none]|collapsed|maximized
     * 
     *                        >> toolbar icons - set to true to show it; default: none of it is visible
     *                         - tb-collapse
     *                         - tb-expand    it is added automatically if you set 'state'=>'collapsed'
     *                         - tb-maximize 
     *                         - tb-minimize  it is added automatically if you set 'state'=>'maximized'
     *                         - tb-remove
     * 
     *                        >> texts/ html content
     *                          - title   - text: title of the card
     *                          - tools   - text: titlebar top right elements
     *                          - text    - text: content of the card
     *                          - footer  - text: footer of the card
     * @return string
     */
    public function getCard($aOptions){
        $aOptions=$this->_ensureOptions('card', $aOptions);
        // css class prefixes based on "variant" value
        $aVariants=[
            'default'  => 'card-',
            'outline'  => 'card-outline card-',
            'solid'    => 'bg-',
            'gradient' => 'bg-gradient-',
        ];

        // window states: css class and toolbar buttons to add
        $aStates=[
            'collapsed'=>[ 'class'=>'collapsed-card', 'tool'=>'tb-expand'],
            'maximized'=>[ 'class'=>'maximized-card', 'tool'=>'tb-minimize'],
        ];
        $aTools=[
            'tb-collapse'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>',
            'tb-expand'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-plus"></i></button>',

            'tb-maximize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-expand"></i></button>',
            'tb-minimize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-compress"></i></button>',

            'tb-remove'=>'<button type="button" class="btn btn-tool" data-card-widget="remove"><i class="fas fa-times"></i></button>',
        ];

        // print_r($aOptions);

        $sVariantPrefix=$aVariants[$aOptions['variant']] ? $aVariants[$aOptions['variant']] : $aVariants['default'];
        $sClass='card'
                . $this->_addClassValue($aOptions['type'],    $sVariantPrefix)
                . $this->_addClassValue($aOptions['class'],   '')
                ;

        // check window state
        foreach($aStates as $sStatus=>$aStatus){
            if($aOptions['state']===$sStatus){
                $sClass.=' '.$aStatus['class'];
                $aOptions[$aStatus['tool']]=1;
            }
        }

        // add toolbar buttons - from given options or by window state
        foreach($aTools as $sTool=>$sHtml){
            $aOptions['tools'].=($aOptions[$sTool] ? $sHtml : '');
        }
        // build parts of the card
        $sCardHeader=$this->addWrapper('div', ['class'=>'card-header'],
            $this->_tag('h3', ['class'=>'card-title', 'label'=>$aOptions['title']])
            . ($aOptions['tools'] ? $this->_tag('div', ['class'=>'card-tools', 'label'=>$aOptions['tools']]) : '')
        );

        $sCardBody=$this->_tag('div', ['class'=>'card-body', 'label'=>$aOptions['text']]);
        $sCardFooter=$aOptions['footer'] ? $this->_tag('div', ['class'=>'card-footer', 'label'=>$aOptions['footer']]) : '';

        // merge all
        return $this->addWrapper('div', ['class'=>$sClass], $sCardHeader.$sCardBody.$sCardFooter);
    }


    /**
     * return an info-box:
     * A colored box with large icon, text and a value.
     * https://adminlte.io/docs/3.2/components/boxes.html
     * 
     * @param type $aOptions  hash with keys for all options
     *                        styling:
     *                          - type    - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning
     *                          - iconbg  - background color or type of icon; use it for default type (type="")
     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large 
     * 
     *                        content
     *                          - icon    - icon class
     *                          - text    - information text
     *                          - number  - value (comes in bold text)
     *                          - progressvalue - integer: progress bar; range: 0..100
     *                          - progresstext  - text below progress bar
     * @return string
     */
    public function getInfobox($aOptions){
        $aOptions=$this->_ensureOptions('infobox', $aOptions);

        // print_r($aOptions);
        $sClass='info-box'
                . $this->_addClassValue($aOptions['type'],    'bg-')
                . $this->_addClassValue($aOptions['class'],   '')
                .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) 
                    ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
                ;
        
        // build parts
        $sIcon=$aOptions['icon'] 
            ? $this->addWrapper("span", [
                    'class'=>'info-box-icon'.($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : '')
                ], $this->_tag('i',['class'=>$aOptions['icon']])) 
            : ''
            ;
        $sContent=$this->addWrapper("div", ['class'=>'info-box-content'],
            ''
            . ($aOptions['text']   ? $this->_tag('span', ['class'=>'info-box-text',   'label'=>$aOptions['text']])   : '')
            . ($aOptions['number'] ? $this->_tag('span', ['class'=>'info-box-number', 'label'=>$aOptions['number']]) : '')
            . ($aOptions['progressvalue']!==false && $aOptions['progressvalue']!=='' 
                ? $this->addWrapper('div', ['class'=>'progress'],
                        $this->_tag('div', ['class'=>'progress-bar'. ($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : ''), 'style'=>'width: '.(int)$aOptions['progressvalue'].'%' ]) 
                    )
                    . ($aOptions['progresstext'] ? $this->_tag('span', ['class'=>'progress-description', 'label'=>$aOptions['progresstext']]) : '' )
                : ''    
                ) 
        );

        // merge all
        return $this->_tag('div', ['class'=>$sClass], $sIcon.$sContent);
    }


    /**
     * return an info-box:
     * A colored box with large icon, text and a value.
     * https://adminlte.io/docs/3.2/components/boxes.html
     * https://adminlte.io/themes/v3/pages/widgets.html
     * 
     * @param type $aOptions  hash with keys for all options
     *                        styling:
     *                          - type    - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning

     *                        content
     *                          - icon    - icon class for icon on the right
     *                          - text    - information text
     *                          - number  - value (comes in bold text)
     *                          - url     - integer: progress bar; range: 0..100
     *                          - linktext- text below progress bar
     * @return string
     */
    public function getSmallbox($aOptions){
        $aOptions=$this->_ensureOptions('smallbox', $aOptions);
        $aShadows=[
            'default'  => '',
            'none'     => 'shadow-none',
            'small'    => 'shadow-small',
            'regular'  => 'shadow',
            'large'    => 'shadow-lg',
        ];
        // print_r($aOptions);
        $sClass='small-box'
                . $this->_addClassValue($aOptions['type'],    'bg-')
                .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) 
                    ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
                ;
        
        // build parts
        $sContent=$this->addWrapper("div", ['class'=>'inner'],
            ''
            . ($aOptions['number'] ? $this->_tag('h3', ['label'=>$aOptions['number']]) : '')
            . ($aOptions['text']   ? $this->_tag('p', ['class'=>'info-box-text',   'label'=>$aOptions['text']])   : '')
        );
        $sIcon=$aOptions['icon'] 
            ? $this->addWrapper("div", ['class'=>'icon'], 
                $this->_tag('i',['class'=>$aOptions['icon']])) 
            : ''
            ;
        $sFooter=($aOptions['url']
            ? $this->addWrapper("a", [
                'class'=>'small-box-footer',
                'href'=>$aOptions['url'],
                ],
                ''
                . ($aOptions['linktext'] ? $aOptions['linktext'] : $aOptions['url'])
                . ' '
                . $this->_tag('i',['class'=>'fas fa-arrow-circle-right'])
            )
            : ''
        );

        // merge all
        return $this->_tag('div', ['class'=>$sClass], $sContent.$sIcon.$sFooter);
    }


/*

<div class="info-box">
  <span class="info-box-icon bg-info"><i class="far fa-bookmark"></i></span>
  <div class="info-box-content">
    <span class="info-box-text">Bookmarks</span>
    <span class="info-box-number">41,410</span>
    <div class="progress">
      <div class="progress-bar bg-info" style="width: 70%"></div>
    </div>
    <span class="progress-description">
      70% Increase in 30 Days
    </span>
  </div>
</div>



  <div class="info-box bg-success">
    <span class="info-box-icon"><i class="far fa-thumbs-up"></i></span>
    <div class="info-box-content">
      <span class="info-box-text">Likes</span>
      <span class="info-box-number">41,410</span>
      <div class="progress">
        <div class="progress-bar" style="width: 70%"></div>
      </div>
      <span class="progress-description">
        70% Increase in 30 Days
      </span>
    </div>
  </div>
*/
  








}