system/modules/pct_customelements/PCT/CustomElements/Core/Attribute.php line 136

Open in your IDE?
  1. <?php
  2. /**
  3.  * Contao Open Source CMS
  4.  * 
  5.  * Copyright (C) 2005-2013 Leo Feyer
  6.  * 
  7.  * @copyright    Tim Gatzky 2013, Premium Contao Webworks, Premium Contao Themes
  8.  * @author        Tim Gatzky <info@tim-gatzky.de>
  9.  * @package        pct_customelements
  10.  * @link        http://contao.org
  11.  */
  12. /**
  13.  * Namespace
  14.  */
  15. namespace PCT\CustomElements\Core;
  16. /**
  17.  * Imports
  18.  */
  19. use Contao\StringUtil;
  20. use PCT\CustomElements\Core\Vault as Vault;
  21. use PCT\CustomElements\Helper\ControllerHelper as ControllerHelper;
  22. use PCT\CustomElements\Core\FrontendTemplate as FrontendTemplate;
  23. use PCT\CustomElements\Core\Cache as Cache;
  24. /**
  25.  * Class file
  26.  * Attribute
  27.  */
  28. class Attribute extends \PCT\CustomElements\Core\Controller implements \PCT\CustomElements\Interfaces\Attribute
  29. {
  30.     /**
  31.      * Widget
  32.      * @var object
  33.      */
  34.     // !$objWidget
  35.     protected $objWidget;
  36.     
  37.     /**
  38.      * Origin object
  39.      * @var object
  40.      */
  41.     // !$objOrigin
  42.     protected $objOrigin null;
  43.     
  44.     /**
  45.      * Child attributes array
  46.      * @var array
  47.      */
  48.     // !$arrChildAttributes
  49.     protected $arrChildAttributes = array();
  50.     
  51.     /**
  52.      * Template for a single field
  53.      * @var string
  54.      */
  55.     // !$strFieldTemplate
  56.     protected $strFieldTemplate 'be_field_customelement';
  57.     
  58.     /**
  59.      * Value
  60.      * @var mixed
  61.      */
  62.     // !$varValue
  63.     protected $varValue null;
  64.     
  65.     /**
  66.      * Modified array
  67.      * @var array
  68.      */
  69.     // !$arrModified
  70.     protected $arrModified = array();
  71.     
  72.     /**
  73.      * Options values array
  74.      * @var array
  75.      */
  76.     // !$arrOptionValues
  77.     protected $arrOptionValues = array();
  78.     
  79.     /**
  80.      * Translated labels array
  81.      * @var array
  82.      * 
  83.      */
  84.     // !$arrTranslatedLabel
  85.     protected $arrTranslations = array();
  86.     /**
  87.      * Flag that a custom template is used
  88.      * @protected boolean
  89.      */
  90.     protected $isCustomTemplate false;
  91.     /**
  92.      * Uuid of the attribute
  93.      * @protected boolean
  94.      */
  95.     public $uuid '';
  96.     /**
  97.      * Flag if an attribute is a copy
  98.      * @protected boolean
  99.      */
  100.     public $isCopy false;
  101.     /**
  102.      * Flag if an attribute should be copied
  103.      * @protected boolean
  104.      */
  105.     public $createCopy false;
  106.     /**
  107.      * Counter of copy
  108.      * @protected boolean
  109.      */
  110.     public $numCopy 0;
  111.     /**
  112.      * Flag if an attribute is sortable
  113.      * @protected boolean
  114.      */
  115.     public $sortable false;
  116.     
  117.     /**
  118.      * Create a new instance
  119.      * @param array
  120.      */
  121.     public function __construct($arrData=array())
  122.     {
  123.         $this->setData($arrData);
  124.         $this->Session \Contao\System::getContainer()->get('session');
  125.         $this->isCopy false;
  126.     }
  127.     
  128.     
  129.     /**
  130.      * Generate the attribute
  131.      * @return object Attribute
  132.      */
  133.     public function generate()
  134.     {
  135.         $arrData $this->getData();
  136.         
  137.         if(empty( $arrData ))
  138.         {
  139.             return null;
  140.         }
  141.         
  142.         return \PCT\CustomElements\Core\AttributeFactory::findById$this->get('id') );
  143.     }
  144.     
  145.     
  146. //! --- Interface ---    
  147.     
  148.     
  149.     /**
  150.      * @inheritdoc
  151.      */
  152.     public function getFieldDefinition()
  153.     {
  154.         return array();
  155.     }    
  156.     
  157. //! --- Vault ---    
  158.     /**
  159.      * Set value for this attribute
  160.      * @param mixed
  161.      */
  162.     public function setValue($varValue)
  163.     {
  164.         $this->set('varValue',$varValue);
  165.         
  166.         // mark this variable as modified
  167.         $this->setModified('varValue');
  168.     }
  169.     
  170.     
  171.     /**
  172.      * Return the value
  173.      * @return mixed
  174.      */
  175.     public function getValue()
  176.     {
  177.         $varValue $this->get('varValue');
  178.         
  179.         // load value hook
  180.         $varValue \PCT\CustomElements\Core\Hooks::callstatic('loadValueHook',array($varValue,$this));
  181.         
  182.         return $varValue;
  183.     }
  184.     
  185.     
  186.     /**
  187.      * Store an attribute value to the vault
  188.      * @param mixed        Value
  189.      * @param string    Name of the attribute (uuid)
  190.      * @param integer    Id of the (contao) element
  191.      * @param string    Source table e.g. tl_content
  192.      * @param array        Optional settings array
  193.      */
  194.     protected function saveValue($varValue,$strField,$intPid=0,$strTable='',$arrOptions=array())
  195.     {
  196.         // set the current value in object scope when it is not a child
  197.         if(!$this->isChild($strField))
  198.         {
  199.             $this->setValue($varValue);
  200.         }
  201.     }
  202.     
  203.     
  204.     /**
  205.      * Public shortcut to saveValue
  206.      * @see saveValue
  207.      */
  208.     public function save($varValue,$intPid=0,$strTable='',$arrOptions=array())
  209.     {
  210.         $strField $this->uuid $this->uuid $this->get('uuid');
  211.         $this->saveValue($varValue,$strField,$intPid,$strTable,$arrOptions);
  212.     }
  213.     
  214.     /**
  215.      * Load the attribute value from the vault
  216.      * @param string    Name of the attribute (uuid)
  217.      * @param integer    Id of the (contao) element
  218.      * @param string    Source table e.g. tl_content
  219.      * @param array        Optional settings array
  220.      * @return string
  221.      */
  222.     protected function loadValue($strField,$intPid=0,$strTable='',$arrOptions=array())
  223.     {
  224.         // return the value if set from outside
  225.         if($this->isModified('varValue') && !$this->isChild($strField))
  226.         {
  227.             $varValue $this->getValue();
  228.         }
  229.         else
  230.         {
  231.             $objOrigin $this->get('objOrigin');
  232.             if( $objOrigin === null )
  233.             {
  234.                 $objOrigin Origin::getInstance();
  235.             }
  236.             
  237.             if($intPid 1) {$intPid $objOrigin::get('pid');}
  238.             if(strlen($strTable) < 1) {$strTable $objOrigin::get('table');}
  239.             
  240.             $this->set('objOrigin',$objOrigin);
  241.             
  242.             if(empty($arrOptions))
  243.             {
  244.                 $arrOptions $this->getData();
  245.             }
  246.             
  247.             $objCustomElement $this->getCustomElement();
  248.             $arrOptions['saveDataAs'] = isset($arrOptions['saveDataAs']) ? $arrOptions['saveDataAs'] : $this->get('saveDataAs');
  249.             $arrOptions['attr_id'] = isset($arrOptions['attr_id']) ? $arrOptions['attr_id'] : $this->get('id');
  250.             $arrOptions['type'] = isset($arrOptions['type']) ? $arrOptions['type'] : $this->get('type');
  251.             $arrOptions['genericAttribute'] = $objCustomElement->getGenericAttribute();
  252.             
  253.             // fetch the value from the vault
  254.             $varValue Vault::getAttributeValueByUuid($strField,$intPid,$strTable,$arrOptions);
  255.             
  256.             // set the current value in object scope when it is not a child
  257.             if(!$this->isChild($strField))
  258.             {
  259.                 $this->setValue($varValue);
  260.             }
  261.         }
  262.         
  263.         // load value hook
  264.         $varValue \PCT\CustomElements\Core\Hooks::callstatic('loadValueHook',array($varValue,$this));
  265.         
  266.         return $varValue;
  267.     }
  268.     
  269.     
  270.     /**
  271.      * Public shortcut to saveValue
  272.      * @see saveValue
  273.      */
  274.     public function load($intPid=0,$strTable='',$arrOptions=array())
  275.     {
  276.         $strField $this->uuid $this->uuid $this->get('uuid');
  277.         return $this->loadValue($strField,$intPid,$strTable,$arrOptions);
  278.     }
  279.     
  280.     
  281.     /**
  282.      * Load an options value
  283.      * @param string    Name of the option key
  284.      * @param integer
  285.      * @param string
  286.      * @param array
  287.      * @return mixed
  288.      */
  289.     public function loadOptionValue($strOption,$intPid=0,$strTable='',$arrOptions=array())
  290.     {
  291.         if($this->isModified('arrOptionValues'))
  292.         {
  293.             return $this->arrOptionValues[$strOption] ?? null;
  294.         }
  295.         
  296.         $strField $this->uuid $this->uuid $this->get('uuid');
  297.         $strField .= '_'.$strOption;
  298.         return $this->loadValue($strField,$intPid,$strTable,$arrOptions);
  299.     }
  300.     
  301.     
  302.     /**
  303.      * Load all option values and return them as associated array
  304.      * @param string
  305.      * @param integer
  306.      * @param string
  307.      * @return array
  308.      */
  309.     public function loadOptionValues($strField,$intPid=0,$strTable='')
  310.     {
  311.         if($this->isModified('arrOptionValues'))
  312.         {
  313.             return $this->get('arrOptionValues');
  314.         }
  315.         
  316.         $objOrigin $this->get('objOrigin');
  317.         if($intPid && isset($objOrigin)) {$intPid $objOrigin::get('pid');}
  318.         if(strlen($strTable) < && isset($objOrigin)) {$strTable $objOrigin::get('table');}
  319.         
  320.         if($intPid && strlen($strTable) < 1)
  321.         {
  322.             return array();
  323.         }
  324.         
  325.         // get the values from the wizard
  326.         $arrWizard Vault::getWizardData($intPid,$strTable);
  327.         $arrOptions StringUtil::deserialize($this->get('options'));
  328.         
  329.         if(!is_array($arrOptions))
  330.         {
  331.             $arrOptions array_filter(explode(','$arrOptions));
  332.         }
  333.         
  334.         if(!empty($arrWizard['values']) && !empty($arrOptions) && is_array($arrOptions) && !in_array($this->get('type'), $GLOBALS['PCT_CUSTOMELEMENTS']['ignoreOptionFields']))
  335.         {
  336.             $arrOptionValues = array();
  337.             foreach($arrOptions as $option)
  338.             {
  339.                 if(isset($arrWizard['values'][$strField.'_'.$option]))
  340.                 {
  341.                     $arrOptionValues[] = $arrWizard['values'][$strField.'_'.$option];
  342.                 }
  343.             }
  344.             
  345.             $this->setOptionValues($arrOptionValues);
  346.         
  347.             return $arrOptionValues;
  348.         }
  349.         return array();
  350.     }
  351.     
  352.     
  353.     /**
  354.      * Allow to set the options value array from outside
  355.      * @param array
  356.      */
  357.     public function setOptionValues($arrValues)
  358.     {
  359.         $this->set('arrOptionValues',$arrValues);
  360.         
  361.         // flag this variable to be modified
  362.         $this->setModified('arrOptionValues');
  363.     }
  364.     
  365.     
  366.     /**
  367.      * (DEPRECATED)
  368.      * Load the attribute value from the vault
  369.      * @param string
  370.      * @param integer
  371.      * @param string
  372.      * @return mixed
  373.      */
  374.     public function findValueByField($strField,$strSaveDataAs='')
  375.     {
  376.         return $this->loadValue($strField,null,null,array('saveDataAs'=>$strSaveDataAs));
  377.     }
  378.     
  379.     /**
  380.      * Return the active record from the origin
  381.      * @param boolean    Flag to create a pseudo record
  382.      * @param string    The table to create the record from
  383.      * @return object
  384.      */
  385.     public function getActiveRecord($bolIsPseudo=false$strTable='tl_content')
  386.     {
  387.         if($this->get('objActiveRecord'))
  388.         {
  389.             $objReturn $this->get('objActiveRecord');
  390.             $objReturn = clone($objReturn);
  391.             if( $objReturn->id === null || (int)$this->get('objActiveRecord')->id )
  392.             {
  393.                 $objReturn->id $this->get('objActiveRecord')->id;
  394.             }
  395.             return $objReturn;
  396.         }
  397.         
  398.         if($this->get('objOrigin') && !$bolIsPseudo)
  399.         {
  400.             $objReturn $this->get('objOrigin')->get('activeRecord');
  401.             if( $objReturn->id === null )
  402.             {
  403.                 $objReturn->id $this->get('objOrigin')->get('activeRecord')->id;
  404.             }
  405.         }
  406.         else if($bolIsPseudo)
  407.         {
  408.             $objReturn \Contao\Database::getInstance()->prepare("SELECT * FROM ".$strTable." WHERE id=?")->limit(1)->execute(-1);
  409.             $this->set('objActiveRecord',$objReturn);
  410.         }
  411.         else
  412.         {
  413.             return null;
  414.         }
  415.         
  416.         // strip the active record from contao fields
  417.         if(!empty($objReturn) && is_array($GLOBALS['PCT_CUSTOMELEMENTS']['fieldnamesSharedWithContao']) && count($GLOBALS['PCT_CUSTOMELEMENTS']['fieldnamesSharedWithContao']) > 0)
  418.         {
  419.             foreach($GLOBALS['PCT_CUSTOMELEMENTS']['fieldnamesSharedWithContao'] as $f)
  420.             {
  421.                 if(isset($objReturn->{$f}))
  422.                 {
  423.                     $objReturn->{$f} = null;
  424.                 }
  425.             }
  426.         }
  427.         
  428.         if( $objReturn === null )
  429.         {
  430.             return null;
  431.         }
  432.         
  433.         return clone( $objReturn );
  434.     }
  435.     
  436.      
  437. //! --- DCA handling ---    
  438.     /**
  439.      * Generate the field and return html string
  440.      * @param array
  441.      * @param object
  442.      * @return string
  443.      */
  444.     public function generateWidget($objDC)
  445.     {
  446.         $varValue '';
  447.         $strBuffer '';
  448.         $objDC = clone($objDC);
  449.         
  450.         // get attribute session
  451.         if( !isset($this->Session) || $this->Session === null )
  452.         {
  453.             $this->Session \Contao\System::getContainer()->get('session');
  454.         }
  455.         $arrSession $this->Session->get('pct_customelement_attribute');
  456.         if( \is_array($arrSession) === false )
  457.         {
  458.             $arrSession = array();
  459.         }
  460.         // get attribute registry session
  461.         $arrCeSession $this->Session->get('pct_customelements');
  462.         if( \is_array($arrCeSession) === false )
  463.         {
  464.             $arrCeSession = array();
  465.         }
  466.         $arrRegistry $arrCeSession['registry'][$objDC->table][$objDC->id] ?? array();
  467.         if(!is_array($arrRegistry))
  468.         {
  469.             $arrRegistry = array();
  470.         }
  471.         
  472.         // set name / uuid
  473.         $this->set('uuid',$objDC->field);
  474.         
  475.         // get the field definition for this attribute
  476.         $arrFieldDef $this->getFieldDefinition();
  477.         $this->set('dataContainer'$objDC);
  478.         #$arrFieldDef['dataContainer'] = $objDC;
  479.         // translate the labels and descriptions
  480.         $arrTranslatedLabel $this->getTranslatedLabel();
  481.         if(is_array($arrTranslatedLabel) && !empty($arrTranslatedLabel)) 
  482.         {
  483.             $arrFieldDef['label'] = $arrTranslatedLabel;
  484.         }
  485.         
  486.         // TL_DCA, dcaconfig.php
  487.         $strAlias $this->get('alias');
  488.         #if($this->createCopy || $this->isCopy)
  489.         #{
  490.         #    $strAlias = $this->get('alias').'#'.$this->numCopy;
  491.         #}
  492.         if( isset($GLOBALS['CE_DCA'][$objDC->table]['fields'][$strAlias]) || isset($GLOBALS['CE_DCA'][$objDC->table]['fields'][$objDC->field]) )
  493.         {
  494.             $f $strAlias;
  495.             if( isset($GLOBALS['CE_DCA'][$objDC->table]['fields'][$objDC->field]) )
  496.             {
  497.                 $f $objDC->field;
  498.             }
  499.             $label $arrFieldDef['label'];
  500.             $arrFieldDef \array_replace_recursive($arrFieldDef,$GLOBALS['CE_DCA'][$objDC->table]['fields'][$f]);
  501.             if( empty($arrFieldDef['label']) )
  502.             {
  503.                 $arrFieldDef['label'] = $label;
  504.             }
  505.         }
  506.         
  507.         // mark attribute as sortable
  508.         $this->sortable false;
  509.         if( isset($arrFieldDef['sortable']) )
  510.         {
  511.             $this->sortable $arrFieldDef['sortable'];
  512.         }
  513.         
  514.         // put the field in the data container
  515.         $GLOBALS['TL_DCA'][$objDC->table]['fields'][$objDC->field] = $arrFieldDef;
  516.         
  517.         // catch the submitted value
  518.         if(\Contao\Input::post('FORM_SUBMIT') == $objDC->table && isset($_POST[$objDC->field]) && !$this->get('doNotSave'))
  519.         {
  520.             $varValue \Contao\Input::post($objDC->field);
  521.             if( isset($arrFieldDef['eval']['rte']) && $arrFieldDef['eval']['rte'] || isset($arrFieldDef['eval']['allowHtml']) && $arrFieldDef['eval']['allowHtml'])
  522.             {
  523.                 $varValue \Contao\Input::postRaw($objDC->field);
  524.             }
  525.             
  526.             if($this->sortable && \Contao\Input::post('orderSRC_'.$objDC->field))
  527.             {
  528.                 $varValue \Contao\Input::post('orderSRC_'.$objDC->field);
  529.             }
  530.             
  531.             // remove the temp. value when user submitted the widget
  532.             if(isset($arrSession[$objDC->field]))
  533.             {
  534.                 unset($arrSession[$objDC->field]);
  535.                 $this->Session->set('pct_customelement_attribute',$arrSession);
  536.             }
  537.         }
  538.         else
  539.         {
  540.             $varValue $this->load($objDC->id,$objDC->table);
  541.             
  542.             // store field value from parent in session when created via ajax
  543.             if($this->isCopy && (\Contao\Input::get('ajax') || \Contao\Input::post('ajax')))
  544.             {
  545.                   $this->set('uuid',$objDC->pattr_uuid);
  546.                 $varValue $this->load($objDC->id,$objDC->table);
  547.                 $session = array($objDC->field => array('value'=>$varValue));
  548.                 $this->Session->set('pct_customelement_attribute',$session);
  549.             }
  550.         }
  551.         
  552.         // load the temp. value from the session if it exists
  553.         if( isset($arrSession[$objDC->field]) && $arrSession[$objDC->field])
  554.         {
  555.             $varValue $arrSession[$objDC->field]['value'];
  556.         }
  557.         
  558.         // load default value
  559.         if(!isset($varValue) && $this->get('defaultValue') || $varValue == false)
  560.         {
  561.             // double check if the field really has no entry in the vault yet
  562.             if(!Vault::fieldExists($objDC->field,$objDC->id,$objDC->table))
  563.             {
  564.                 $varValue $this->get('defaultValue');
  565.             }
  566.         }
  567.         
  568.         // trigger load callback
  569.         if( isset($arrFieldDef['load_callback']) && is_array($arrFieldDef['load_callback']))
  570.         {
  571.             $objDC->objAttribute $this;
  572.             foreach($arrFieldDef['load_callback'] as $callback)
  573.             {
  574.                 if (is_array($callback))
  575.                 {
  576.                    $varValue ControllerHelper::importStatic($callback[0])->{$callback[1]}($varValue,$objDC,$this);    
  577.                 }
  578.                 else if(is_callable($callback))
  579.                 {
  580.                    $varValue $callback($varValue,$objDC,$this);
  581.                 }
  582.             }
  583.         }
  584.         
  585.         // Store the field definitions in the session for further use
  586.         $arrRegistry[$objDC->field] = array
  587.         (
  588.             'id'        => $this->get('id'),
  589.             'name'        => $objDC->field,
  590.             'fieldDef'    => $arrFieldDef,
  591.         );
  592.         $arrCeSession['registry'][$objDC->table][$objDC->id] = $arrRegistry
  593.         $this->Session->set('pct_customelements',$arrCeSession);
  594.         //--
  595.         
  596.         $objActiveRecord $objDC->activeRecord;
  597.         $strModel \Contao\Model::getClassFromTable($objDC->table) ?? '';
  598.         if($objActiveRecord === null && class_exists($strModel))
  599.         {
  600.             $objActiveRecord $strModel::findByPk($objDC->id);
  601.         }
  602.         else if($objActiveRecord === null && !class_exists($strModel))
  603.         {
  604.             $objActiveRecord \Contao\Database::getInstance()->prepare("SELECT * FROM ".$objDC->table." WHERE id=?")->limit(1)->execute($objDC->id);
  605.         }
  606.         
  607.         // create the widget
  608.         $strClass $GLOBALS['BE_FFL'][$arrFieldDef['inputType']];
  609.         if(strlen($strClass) < || !class_exists($strClass))
  610.         {
  611.             return '';
  612.         }
  613.         
  614.         $arrFieldDef['activeRecord'] = $objActiveRecord;
  615.         
  616.         // replace null values with empty arrays because contaos validator might run in foreach loops (e.g. Contao\InputUnit)
  617.         if($varValue === null)
  618.         {
  619.             $varValue '';
  620.         }
  621.         if( !isset($arrFieldDef['label'][0]) )
  622.         {
  623.             $arrFieldDef['label'][0] = $this->get('title');
  624.         }
  625.         if( !isset($arrFieldDef['label'][1]) )
  626.         {
  627.             $arrFieldDef['label'][1] = $this->get('description');
  628.         }
  629.         
  630.         $arrAttributes $strClass::getAttributesFromDca($arrFieldDef,$objDC->field,$varValue,$objDC->field,$objDC->table,$objDC);
  631.         
  632.         $objWidget = new $strClass($arrAttributes);
  633.         $objWidget->__set('activeRecord',$objActiveRecord);
  634.         // handle binary data
  635.         #if(!empty($varValue) && !is_array($varValue))
  636.         #{
  637.         #    if($this->saveDataAs == 'binary' || \Contao\Validator::isBinaryUuid($varValue))
  638.         #    {
  639.         #        $objWidget->__set('value',\Contao\StringUtil::binToUuid($varValue));
  640.         #    }
  641.         #}
  642.         
  643.         // trigger save callbacks on submit
  644.         if(\Contao\Input::post('FORM_SUBMIT') == $objDC->table && !$this->get('doNotSave'))
  645.         {
  646.             $submitted_value $varValue;
  647.             // store sorted values
  648.             if($this->sortable && \Contao\Input::post('orderSRC_'.$objDC->field))
  649.             {
  650.                 $varValue \Contao\Input::post('orderSRC_'.$objDC->field);
  651.             }
  652.             // trigger save callback
  653.             if( isset($arrFieldDef['save_callback']) && is_array($arrFieldDef['save_callback']))
  654.             {
  655.                 $objDC->objAttribute $this;
  656.                 foreach($arrFieldDef['save_callback'] as $callback)
  657.                 {
  658.                     if (is_array($callback))
  659.                     {
  660.                        $varValue ControllerHelper::importStatic($callback[0])->{$callback[1]}($varValue,$objDC,$this);    
  661.                     }
  662.                     else if(is_callable($callback))
  663.                     {
  664.                        $varValue $callback($varValue,$objDC,$this);
  665.                     }
  666.                 }
  667.             }
  668.             
  669.             // do not save the value if return value is an object (exception) 
  670.             if(is_object($varValue))
  671.             {
  672.                 $error $varValue->getMessage();
  673.                 
  674.                 $objWidget->addError($error);
  675.                 
  676.                 // reset to the input value and store error in session for reload
  677.                 $varValue $submitted_value;
  678.             }
  679.         }
  680.         
  681.         // trigger load callback
  682.         if(!\Contao\Input::post('FORM_SUBMIT'))
  683.         {
  684.             if( isset($arrFieldDef['load_callback']) && is_array($arrFieldDef['load_callback']))
  685.             {
  686.                 $objDC->objAttribute $this;
  687.                 foreach($arrFieldDef['load_callback'] as $callback)
  688.                 {
  689.                     if (is_array($callback))
  690.                     {
  691.                        $varValue ControllerHelper::importStatic($callback[0])->{$callback[1]}($varValue,$objDC,$this);    
  692.                     }
  693.                     else if(is_callable($callback))
  694.                     {
  695.                        $varValue $callback($varValue,$objDC,$this);
  696.                     }
  697.                 }
  698.             }
  699.         }
  700.         
  701.         if( isset($arrSession[$objDC->field]['error']) &&  $arrSession[$objDC->field]['error'])
  702.         {
  703.             $objWidget->addError($arrSession[$objDC->field]['error']);
  704.         }
  705.         
  706.         // set value as POST value for the validator
  707.         if( !\Contao\Environment::get('isAjaxRequest') )
  708.         {
  709.             $v $objWidget->__get('value');
  710.             if(!is_array($v) && $v !== null)
  711.             {
  712.                 if(\Contao\Validator::isBinaryUuid($v))
  713.                 {
  714.                     $v \Contao\StringUtil::binToUuid($v);
  715.                 }
  716.                 $objWidget->__set('value',$v);
  717.             }
  718.             
  719.             \Contao\Input::setPost($objDC->field,$objWidget->__get('value'));
  720.             
  721.             #if(\Contao\Input::post('REQUEST_TOKEN') == '')
  722.             #{
  723.             #    \Contao\Input::setPost('REQUEST_TOKEN',REQUEST_TOKEN);
  724.             #}
  725.         }
  726.         
  727.         // decode entities
  728.         if( isset($arrFieldDef['eval']['decodeEntities']) && $arrFieldDef['eval']['decodeEntities'] )
  729.         {
  730.             $objWidget->__set('value'\Contao\StringUtil::decodeEntities($objWidget->__get('value')) );
  731.         }
  732.         
  733.         // check if the attribute itself calls its generateWidget function
  734.         if(method_exists($this,'parseWidgetCallback'))
  735.         {
  736.             if( !isset($arrFieldDef['dataContainer']) || !$arrFieldDef['dataContainer'])
  737.             {
  738.                 $arrFieldDef['dataContainer'] = $objDC;
  739.             }
  740.             $strBuffer \call_user_func_array(array($this'parseWidgetCallback'), array($objWidget,$objDC->field,$arrFieldDef,$objDC,$objWidget->__get('value')) );
  741.         }
  742.         else
  743.         {
  744.             // validate the input
  745.             if( \Contao\Input::post($objDC->field) !== null )
  746.             {
  747.                 $objWidget->validate();
  748.             }
  749.             
  750.             if($objWidget->hasErrors())
  751.             {
  752.                 $objWidget->class 'error';
  753.             }
  754.             
  755.             $strBuffer $objWidget->parse();
  756.         }
  757.         
  758.         
  759.         // if the widget has an error store the value in the session, do not save in the vault
  760.         if($objWidget->hasErrors())
  761.         {
  762.             $objDC->noReload true;
  763.             $session = array($objDC->field => array('value'=>$varValue,'error'=>$error));
  764.             $this->Session->set('pct_customelement_attribute',$session);
  765.         }
  766.         else
  767.         {
  768.             if($varValue != '' || (isset($arrFieldDef['eval']['doNotSaveEmpty']) && !$arrFieldDef['eval']['doNotSaveEmpty']) || !$this->get('doNotSave'))
  769.             {
  770.                 if($varValue === '')
  771.                 {
  772.                     $varValue \Contao\Widget::getEmptyValueByFieldType($arrFieldDef['sql']);
  773.                 }
  774.             }
  775.             
  776.             // save value
  777.             $this->save($varValue,$objDC->id,$objDC->table);    
  778.         }
  779.         
  780.         // do not render the field at all when the output is none
  781.         if(strlen($strBuffer) < 1)
  782.         {
  783.             return '';
  784.         }
  785.         
  786.         // make the field sortable
  787.         if($this->sortable && strlen(strpos($strBuffer'orderSRC_'.$objDC->field)) < 1)
  788.         {
  789.             $doc = new \DOMDocument();
  790.             @$doc->loadHTML(preg_replace("/&(?!(?:apos|quot|[gl]t|amp);|#)/""&amp;",$strBuffer));
  791.             $elem $doc->getElementById('sort_'.$objDC->field);
  792.             
  793.             if($elem)
  794.             {
  795.                 $class $doc->createAttribute('class');
  796.                 $class->value 'sortable' . ($arrFieldDef['eval']['isGallery'] ? ' sgallery':'');
  797.                 $elem->appendChild($class);
  798.                 
  799.                 $str '<p class="sort_hint">' $GLOBALS['TL_LANG']['MSC']['dragItemsHint'] . '</p>';
  800.                 $str .= $elem->C14N();
  801.                 $str .= '<input type="hidden" id="ctrl_orderSRC_'.$objDC->field.'" name="orderSRC_'.$objDC->field.'" value="'.$varValue.'">';
  802.                 $str .= '<script>Backend.makeMultiSrcSortable("sort_'.$objDC->field.'", "ctrl_orderSRC_'.$objDC->field.'","ctrl_orderSRC_'.$objDC->field.'")</script>';
  803.                 // replace sort container
  804.                 $preg preg_match('/<ul(.*?)\/ul>/'$strBuffer,$result); 
  805.                 if($preg)
  806.                 {
  807.                     $strBuffer str_replace($result[0], $str$strBuffer);
  808.                 }
  809.             }
  810.             // fallback if DOMDocument fails
  811.             else
  812.             {
  813.                 $preg preg_match('/<ul id="sort_'.$objDC->field.'"(.*?)\/ul>/'$strBuffer,$result); 
  814.                 if($preg)
  815.                 {
  816.                     $tmp $result[0];
  817.                     // inject the class
  818.                     $elem preg_replace('/class="/''class="sortable '$tmp1);
  819.                     
  820.                     $str '<p class="sort_hint">' $GLOBALS['TL_LANG']['MSC']['dragItemsHint'] . '</p>';
  821.                     $str .= $elem;
  822.                     $str .= '<input type="hidden" id="ctrl_orderSRC_'.$objDC->field.'" name="orderSRC_'.$objDC->field.'" value="'.$varValue.'">';
  823.                     $str .= '<script>Backend.makeMultiSrcSortable("sort_'.$objDC->field.'", "ctrl_orderSRC_'.$objDC->field.'")</script>';
  824.                     
  825.                     $strBuffer str_replace($result[0], $str$strBuffer);
  826.                 }
  827.             }
  828.         }
  829.         
  830.         // handle wizards (taken from DataContainer.php)
  831.         $wizard '';
  832.         if ( isset($arrFieldDef['wizard']) && is_array($arrFieldDef['wizard']))
  833.         {
  834.             foreach ($arrFieldDef['wizard'] as $callback)
  835.             {
  836.                 $dc = clone($objDC);
  837.                 $dc->strField $objDC->field;
  838.                 $dc->strInputName $objDC->field;
  839.                 
  840.                 if (is_array($callback))
  841.                 {
  842.                     $objCallback = new $callback[0];
  843.                     $wizard .= $objCallback->{$callback[1]}($dc);
  844.                 }
  845.                 elseif (is_callable($callback))
  846.                 {
  847.                     $wizard .= $callback($dc);
  848.                 }
  849.             }
  850.             
  851.             if(strlen($wizard) > 0)
  852.             {
  853.                 $objWidget->wizard $wizard;
  854.                 $strBuffer .= $wizard;
  855.             }
  856.         }
  857.         
  858.         if(strlen($strBuffer) > 0)
  859.         {
  860.             if(!strlen(strpos('tl_help tl_tip'$strBuffer)) && strlen($objWidget->description) > 0)
  861.             {
  862.                 $strBuffer .= '<p class="tl_help tl_tip">'.$objWidget->description.'</p>';
  863.             }
  864.         }
  865.         
  866.         // add the loadCustomElementFields flag if the attribute opens popups
  867.         if( (isset($arrFieldDef['isPopup']) && $arrFieldDef['isPopup'] == true) || in_array($arrFieldDef['inputType'], array('fileTree''pageTree')) )
  868.         {
  869.             $objSession \Contao\System::getContainer()->get('session');
  870.             $arrSession $objSession->get('loadCustomElementFields');
  871.             if(!is_array($arrSession))
  872.             {
  873.                 $arrSession = array();
  874.             }
  875.             $arrSession[] = $objDC->field;
  876.             
  877.             $objSession->set('loadCustomElementFields'array_uniquearray_filter$arrSession) ) );
  878.         }
  879.         
  880.         
  881.         $objFieldTemplate = new \Contao\BackendTemplate($this->strFieldTemplate);
  882.         $objFieldTemplate->setData($this->getData());
  883.         $arrClass = array($arrFieldDef['eval']['tl_class'] ?? '' );
  884.         #$arrClass[] = $objDC->field;
  885.         $arrClass[] = 'uuid_'.$objDC->field;
  886.         if( isset($arrFieldDef['eval']['chosen']) ) {$arrClass[] = 'chosen';}
  887.         $objFieldTemplate->cssID 'id="'.$objDC->field.'"';
  888.         $objFieldTemplate->class trim(implode(' '$arrClass));
  889.         $objFieldTemplate->html $strBuffer;
  890.         
  891.         // add data-attributes
  892.         $arrDataAttributes = array
  893.         (
  894.             'data-attr_id'        => $this->get('id'),
  895.             'data-attr_uuid'    => $this->get('uuid'),
  896.         );
  897.         $strDataAttributes '';
  898.         foreach($arrDataAttributes as $k => $v)
  899.         {
  900.             $strDataAttributes .= $k.'="'.$v.'" ';
  901.         }
  902.         $objFieldTemplate->data_attributes $strDataAttributes;
  903.         
  904.         // add to output list
  905.         $arrOutput[] = $objFieldTemplate->parse();
  906.         
  907.         //! generate the child attributes
  908.         if($this->hasChilds())
  909.         {
  910.             $arrChilds $this->get('arrChildAttributes');
  911.             foreach($arrChilds as $name => $objChildWidget)
  912.             {
  913.                 $objChildWidget->value $this->loadValue($objChildWidget->name,$objDC->id,$objDC->table,$objChildWidget->fieldDef);
  914.                 
  915.                 // save child widget information
  916.                 if(isset($_POST[$objChildWidget->name]) && !$this->get('doNotSave'))
  917.                 {
  918.                     $objChildWidget->validate();
  919.                     if($objChildWidget->hasErrors())
  920.                     {
  921.                         $objDC->noReload true;
  922.                     }
  923.                     else
  924.                     {
  925.                         // save the value
  926.                         $varValue \Contao\Input::post($objChildWidget->name);
  927.                         $this->saveValue($varValue,$objChildWidget->name,$objDC->id,$objDC->table,$objChildWidget->fieldDef);
  928.                     }
  929.                 }
  930.                 
  931.                 $strChild $objChildWidget->parse();
  932.                 
  933.                 // handle wizards in child attributes
  934.                 if(!empty($objChildWidget->fieldDef['wizard']) && is_array($objChildWidget->fieldDef['wizard']))
  935.                 {
  936.                     foreach($objChildWidget->fieldDef['wizard'] as $callback)
  937.                     {
  938.                         $objCaller = new $callback[0];
  939.                         $objGenericDC = clone($objDC);
  940.                         $objGenericDC->field $objChildWidget->name;
  941.                         $strChild .= $objCaller->{$callback[1]}($objGenericDC);
  942.                     }
  943.                 }
  944.                 
  945.                 if(strlen($objChildWidget->description) > 0)
  946.                 {
  947.                     $strChild .= '<p class="tl_help tl_tip">'.$objChildWidget->description.'</p>';
  948.                 }
  949.                 
  950.                 $objFieldTemplate = new \Contao\BackendTemplate($this->strFieldTemplate);
  951.                 $arrClass = array($objChildWidget->fieldDef['eval']['tl_class']);
  952.                 $objFieldTemplate->class trim(implode(' '$arrClass));
  953.                 $objFieldTemplate->html $strChild;
  954.                 
  955.                 // add data-attributes
  956.                 $arrDataAttributes = array
  957.                 (
  958.                     'data-attr_id'        => $this->get('id'),
  959.                     'data-attr_uuid'    => $objChildWidget->name,
  960.                 );
  961.                 $strDataAttributes '';
  962.                 foreach($arrDataAttributes as $k => $v)
  963.                 {
  964.                     $strDataAttributes .= $k.'="'.$v.'" ';
  965.                 }
  966.                 $objFieldTemplate->data_attributes $strDataAttributes;
  967.                 
  968.                 $arrOutput[] = $objFieldTemplate->parse();
  969.             }
  970.         }
  971.         
  972.         $strBuffer implode(''$arrOutput);
  973.         
  974.         // wrap widgets with child attributes in a seperate div
  975.         if($this->hasChilds())
  976.         {
  977.             $strBuffer '<div class="field_group '.$this->get('uuid').'">'.$strBuffer.'<div class="clear"></div></div>';
  978.         }
  979.         
  980.         $objWidget->buffer $strBuffer;        
  981.     
  982.         // allow manipulations on the widget after it gets parsed
  983.         $strBufferFromHook \PCT\CustomElements\Core\Hooks::callstatic('parseWidgetHook',array($objWidget,$objDC->field,$arrFieldDef,$objDC));
  984.         if(strlen($strBufferFromHook))
  985.         {
  986.             $strBuffer $strBufferFromHook;
  987.         }
  988.         
  989.         return $strBuffer;
  990.     }
  991.         
  992.     /**
  993.      * Prepare an attribute for DCA
  994.      * @param object
  995.      * @return array
  996.      */
  997.     public function prepareForDca($objDC)
  998.     {
  999.         // create a clone of the current datacontainer object to allow custom manipulations on the data container without interfering other fields
  1000.         $objDC = clone($objDC);
  1001.         
  1002.         $strName $this->get('uuid');
  1003.         $strAlias $this->get('alias');
  1004.         
  1005.         // generate a new unique id for a cloned attribute
  1006.         if( isset($this->createCopy) && (boolean)$this->createCopy === true )
  1007.         {
  1008.             $this->isCopy 1;
  1009.             $numCopy $this->numCopy $this->numCopy 1;
  1010.             $objDC->pattr_uuid $strName;
  1011.             $objDC->ref_table 'tl_pct_customelement_attribute';
  1012.             // check if a uuid has been created by javascript already
  1013.             if(\Contao\Input::get('ajax') && \Contao\Input::get('uuids') )
  1014.             {
  1015.                 $arrUuids \json_decode(StringUtil::decodeEntities(\Contao\Input::get('uuids')),true);
  1016.                 $strName $arrUuids[$this->get('id')];
  1017.             }
  1018.             else
  1019.             {
  1020.                 $strName AttributeFactory::generateUuid($objDC);
  1021.             }
  1022.             
  1023.             $strAlias $this->get('alias').'#'.$numCopy;
  1024.         }
  1025.         
  1026.         // set the new field name
  1027.         $objDC->field $strName;
  1028.         
  1029.         // get the field definition for this attribute
  1030.         $arrData $this->getFieldDefinition();
  1031.         
  1032.         // translate the labels and descriptions
  1033.         $arrTranslatedLabel $this->getTranslatedLabel();
  1034.         if(is_array($arrTranslatedLabel)) 
  1035.         {
  1036.             $arrData['label'] = $arrTranslatedLabel;
  1037.         }
  1038.         
  1039.         // HOOK: allow other extensions to manupulate the attribute Data Container array
  1040.         $arrData \PCT\CustomElements\Core\Hooks::callstatic('getDcaHook',array($arrData,$this,$objDC));
  1041.         
  1042.         $arrReturn = array
  1043.         (
  1044.             'name'        => $strName,
  1045.             'alias'        => $strAlias,
  1046.             'id'        => $this->get('id'),
  1047.             'fieldDef'    => $arrData,
  1048.         );
  1049.         if( !is_array($arrReturn) )
  1050.         {
  1051.             $arrReturn = (array)$arrReturn;
  1052.         }
  1053.         
  1054.         // mark field as copy
  1055.         if( isset($this->createCopy) || isset($this->isCopy) )
  1056.         {
  1057.             $arrReturn['isCopy'] = 1;
  1058.             $arrReturn['pattr_uuid'] = $this->get('uuid');
  1059.         }
  1060.         
  1061.         // generate the html output
  1062.         if(!\Contao\Input::get('switch') && !\Contao\Input::get('show') && \Contao\Input::get('act') == 'edit')
  1063.         {
  1064.             $arrReturn['html'] = $this->generateWidget($objDC);
  1065.         }
  1066.         
  1067.         // HOOK: allow other extensions to manupulate the attribute output
  1068.         $arrReturn \PCT\CustomElements\Core\Hooks::callstatic('prepareForDcaHook',array($arrReturn,$this,$objDC));
  1069.         
  1070.         return $arrReturn;
  1071.     }
  1072.     
  1073.     
  1074.     /**
  1075.      * Return true if the attribute has child attributes
  1076.      * @return boolean
  1077.      */
  1078.     public function hasChilds()
  1079.     {
  1080.         $arrChilds $this->get('arrChildAttributes');
  1081.         if(!empty($arrChilds)) {return true;}
  1082.         return false;
  1083.     }
  1084.     
  1085.     
  1086.     /**
  1087.      * Return true if the attribute is a child attribute
  1088.      * @return boolean
  1089.      */
  1090.     public function isChild($strField)
  1091.     {
  1092.         // custom catalog field without underscore
  1093.         if($strField == $this->get('alias'))
  1094.         {
  1095.             return false;
  1096.         }
  1097.         
  1098.         if(strlen(strpos($strField'_')) > 0)
  1099.         {
  1100.             $arrField explode('_'$strField);
  1101.             // custom catalog field with underscore
  1102.             if(in_array($this->get('alias'), $arrField))
  1103.             {
  1104.                 return false;
  1105.             }
  1106.                         
  1107.             return true;
  1108.         }
  1109.         return false;
  1110.     }
  1111.     
  1112.     
  1113.     /**
  1114.      * Return the custom element object
  1115.      * @return object
  1116.      */
  1117.     public function getCustomElement()
  1118.     {
  1119.         if($this->get('objCustomElement'))
  1120.         {
  1121.             return $this->get('objCustomElement');
  1122.         }
  1123.         
  1124.         $objOrigin $this->getOrigin();
  1125.         if($objOrigin)
  1126.         {
  1127.             $objCustomElement $objOrigin->getCustomElement();
  1128.         }
  1129.         
  1130.         if(!isset($objCustomElement))
  1131.         {
  1132.             $objCustomElement \PCT\CustomElements\Core\CustomElementFactory::findByAttributeId($this->get('id'));
  1133.         }
  1134.         
  1135.         $this->set('objCustomElement',$objCustomElement);
  1136.         
  1137.         return $objCustomElement;
  1138.     }
  1139.     /**
  1140.      * Add a child attribute to the list
  1141.      * @param object    Widget
  1142.      * @return array
  1143.      */
  1144.     public function addChildAttribute($objAttribute)
  1145.     {
  1146.         $arrChilds $this->get('arrChildAttributes');
  1147.         $arrChilds[] = $objAttribute;
  1148.         $this->set('arrChildAttributes',$arrChilds);
  1149.     }
  1150.     /**
  1151.      * Add a child attribute to the list
  1152.      * @param array
  1153.      * @return object
  1154.      */
  1155.     public function prepareChildAttribute($arrFieldDef,$strName)
  1156.     {
  1157.         $strClass $GLOBALS['BE_FFL'][$arrFieldDef['inputType']];
  1158.         if($strClass === null)
  1159.         {
  1160.             return null;
  1161.         }
  1162.         $objDC $this->get('dataContainer') ?? null;
  1163.         if( isset($arrFieldDef['dataContainer']) )
  1164.         {
  1165.             $objDC $arrFieldDef['dataContainer'];
  1166.         }
  1167.         
  1168.         $arrAttributes $strClass::getAttributesFromDca($arrFieldDef,$strName,'',$strName,$objDC->table,$objDC);
  1169.         $objWidget = new $strClass($arrAttributes);
  1170.         $objWidget->fieldDef $arrFieldDef;
  1171.         
  1172.         if( isset($arrFieldDef['eval']['dcaPicker']) && strlen($objWidget->wizard) < 1)
  1173.         {
  1174.             $objWidget->wizard \Contao\Backend::getDcaPickerWizard($arrFieldDef['eval']['dcaPicker'], $objDC->table$strName$strName);
  1175.         }
  1176.         // add the attribute to the child list
  1177.         $this->addChildAttribute($objWidget);
  1178.         
  1179.         return $objWidget;
  1180.     }
  1181.     /**
  1182.      * Return the evaluation array
  1183.      * @param array
  1184.      * @return array
  1185.      */
  1186.     public function getEval($arrEvalMerge = array())
  1187.     {
  1188.         $arrReturn = array();
  1189.         $arrSystemCols AttributeFactory::getSystemColumns();
  1190.         
  1191.         $arrTlClass = array();
  1192.         foreach($this->arrData as $strKey => $varValue)
  1193.         {
  1194.             if(in_array($strKey$arrSystemCols) || !$varValue)
  1195.             {
  1196.                 continue;
  1197.             }
  1198.             
  1199.             if(strlen(strpos($strKey,'eval')))
  1200.             {
  1201.                 $k ltrim($strKey,'eval');
  1202.                 $k ltrim($k,'_');
  1203.                 #$k = strtolower($k);
  1204.                 
  1205.                 // handle tl_class
  1206.                 if(strlen(strpos($k,'tl_class')))
  1207.                 {
  1208.                     $varValue str_replace('tl_class_'''$k);
  1209.                     $arrTlClass[] = $varValue;
  1210.                     continue;
  1211.                 }
  1212.                 
  1213.                 // handle tl_style
  1214.                 if(strlen(strpos($k,'tl_style')))
  1215.                 {
  1216.                     $k 'style';
  1217.                 }
  1218.                 
  1219.                 $arrReturn[$k] = $varValue;
  1220.             }
  1221.         }
  1222.         
  1223.         if(count($arrEvalMerge) > 0)
  1224.         {
  1225.             $arrReturn array_unique(array_merge($arrReturn$arrEvalMerge));
  1226.         }
  1227.                 
  1228.         if(count($arrTlClass) > 0)
  1229.         {
  1230.             if( isset($arrEvalMerge['tl_class']) && !empty($arrEvalMerge['tl_class']) )
  1231.             {
  1232.                 $class explode(' '$arrEvalMerge['tl_class']);
  1233.                 $arrTlClass array_unique(array_merge($class));
  1234.             }
  1235.             
  1236.             $arrReturn['tl_class'] = implode(' '$arrTlClass);
  1237.         }
  1238.         if( isset($arrReturn['path']) )
  1239.         {
  1240.             $objFile \Contao\FilesModel::findByPktrim($arrReturn['path']) );
  1241.             if( $objFile !== null )
  1242.             {    
  1243.                 $arrReturn['path'] = $objFile->path;    
  1244.             }
  1245.             else
  1246.             {
  1247.                 unset( $arrReturn['path'] );
  1248.             }
  1249.         }
  1250.         
  1251.         return $arrReturn;
  1252.     }
  1253.     
  1254. //! --- Frontend output ---    
  1255.     /**
  1256.      * Render the attribute and return html string
  1257.      * @return string
  1258.      */
  1259.     public function render()
  1260.     {
  1261.         $objOrigin $this->get('objOrigin');
  1262.         
  1263.         $strField $this->get('uuid');
  1264.         
  1265.         $strTemplate $this->get('template');
  1266.         
  1267.         // set default template
  1268.         if( empty($strTemplate) )
  1269.         {
  1270.             $strTemplate 'customelement_attr_default';
  1271.             $this->set('template',$strTemplate);
  1272.         }
  1273.         
  1274.         // check if we use a regular contao template or custom template
  1275.         if(strlen(strpos($strTemplate'customelement_attr')) < 1)
  1276.         {
  1277.             $this->isCustomTemplate true;
  1278.         }
  1279.         
  1280.         $objTemplate = new FrontendTemplate($strTemplate);
  1281.         $objTemplate->setData($this->get('arrData'));
  1282.         $objTemplate->origin $objOrigin;
  1283.         
  1284.         // check if the attribute has not been modified from outside
  1285.         if($objOrigin != null && !$this->isModified('varValue'))
  1286.         {
  1287.             $varValue $this->loadValue($strField,$objOrigin::get('pid'),$objOrigin::get('table'));
  1288.         }
  1289.         else
  1290.         {
  1291.             $varValue $this->getValue();
  1292.         }
  1293.         
  1294.         $objTemplate->value $varValue;
  1295.         
  1296.         // add raw values
  1297.         $objTemplate->rawValue $varValue;
  1298.         $objTemplate->raw $this;
  1299.         $objTemplate->attribute $this;
  1300.         
  1301.         $arrCssID \Contao\StringUtil::deserialize($this->get('cssID'));
  1302.         $objTemplate->cssID $arrCssID[0] ? 'id="'.$arrCssID[0].'"' '';
  1303.         $arrClass = array('ce_'.$this->get('type'),'attribute',$this->get('type'));
  1304.         if(strlen($arrCssID[1]) > 0)
  1305.         {
  1306.             $arrClass array_merge($arrClass,explode(' ',$arrCssID[1]));
  1307.         }
  1308.         $objTemplate->class implode(' '$arrClass);
  1309.         
  1310.         // HOOK: alternative routine to renderCallback to render an attribute
  1311.         $strBufferFromHook \PCT\CustomElements\Core\Hooks::callstatic('prepareRenderingHook',array($strField,$varValue,$objTemplate,$this));
  1312.         
  1313.         // check if the attribute itself calls its render function
  1314.         if(method_exists($this'renderCallback') && strlen($strBufferFromHook) < 1)
  1315.         {
  1316.             $strBuffer \call_user_func_array(array($this'renderCallback'), array($strField,$varValue,$objTemplate,$this) );
  1317.         }
  1318.         else if(strlen($strBufferFromHook) > 0)
  1319.         {
  1320.             $strBuffer $strBufferFromHook;
  1321.             $strBufferFromHook '';
  1322.         }
  1323.         else
  1324.         {
  1325.             $strBuffer $objTemplate->parse();
  1326.         }
  1327.         
  1328.         // trigger HOOK: allow manipulations on the output
  1329.         $strBufferFromHook \PCT\CustomElements\Core\Hooks::callstatic('renderAttributeHook',array($strBuffer,$strField,$varValue,$this));
  1330.         if(strlen($strBufferFromHook) > 0)
  1331.         {
  1332.             $strBuffer $strBufferFromHook;
  1333.         }
  1334.         
  1335.         return $strBuffer;
  1336.     }
  1337. //! --- translations and multilanguage support ---
  1338.     /**
  1339.      * Find translations for labels in the backend and return them as array
  1340.      * @param string
  1341.      * @param object
  1342.      * @return null|array
  1343.      */
  1344.     public function getTranslatedLabel()
  1345.     {
  1346.         $arrReturn = array();
  1347.         $strKey $this->getCustomElement()->get('alias');
  1348.         $strField $this->get('alias');
  1349.         
  1350.         \Contao\System::loadLanguageFile('default',$GLOBALS['TL_LANGUAGE']);
  1351.         
  1352.         // fallback
  1353.         if( isset($GLOBALS['TL_LANG'][$strKey][$strField]) && is_array($GLOBALS['TL_LANG'][$strKey][$strField]))
  1354.         {
  1355.             $arrReturn = array($GLOBALS['TL_LANG'][$strKey][$strField][0] ?: '',$GLOBALS['TL_LANG'][$strKey][$strField][1] ?: '');
  1356.         }
  1357.         
  1358.         // new structure
  1359.         if( isset($GLOBALS['TL_LANG']['CUSTOMELEMENTS'][$strKey]['fields'][$strField]) && ( is_array($GLOBALS['TL_LANG']['CUSTOMELEMENTS'][$strKey]['fields'][$strField]) || is_array($GLOBALS['TL_LANG']['CUSTOMELEMENTS']['*']['fields'][$strField]) ) )
  1360.         {
  1361.             $arrReturn $GLOBALS['TL_LANG']['CUSTOMELEMENTS'][$strKey]['fields'][$strField] ?: is_array($GLOBALS['TL_LANG']['CUSTOMELEMENTS']['*']['fields'][$strField]);
  1362.         }
  1363.         
  1364.         return $arrReturn;
  1365.     }
  1366.     
  1367.     /**
  1368.      * Return the translations array
  1369.      * @return array
  1370.      */
  1371.     public function getTranslations()
  1372.     {
  1373.         return $this->get('arrTranslations');
  1374.     }
  1375.     
  1376.     
  1377.     /**
  1378.      * Add a translation for a value to the labels array
  1379.      * @param string    Value key
  1380.      * @param string    Label
  1381.      * @param string    The language code e.g. de, en...
  1382.      */
  1383.     public function addTranslation($strValue,$strLabel,$strLanguage)
  1384.     {
  1385.         // mark as modified    
  1386.         $this->setModified('arrTranslations');
  1387.         
  1388.         $arrTranslations $this->get('arrTranslations');
  1389.         
  1390.         // set the translated label for the language
  1391.         $arrTranslations[$strValue][$strLanguage] = $strLabel;
  1392.         
  1393.         $this->set('arrTranslations',$arrTranslations);
  1394.     }
  1395.     
  1396.     
  1397.     
  1398.     /**
  1399.      * Return boolean true if a value has a translation and set the translated value for further use
  1400.      * @param string
  1401.      * @return boolean
  1402.      */
  1403.     public function hasTranslation($strValue,$strLanguage='')
  1404.     {
  1405.         if(strlen($strLanguage) < 1)
  1406.         {
  1407.             $strLanguage \Contao\Input::get('language') ?: \Contao\Input::get('lang') ?: $GLOBALS['TL_LANGUAGE'];
  1408.         }
  1409.         
  1410.         // replace the minus with an underscore
  1411.         if($strLanguage == $GLOBALS['TL_LANGUAGE'] && TL_MODE == 'FE')
  1412.         {
  1413.             $strLanguage str_replace('-','_',$strLanguage);
  1414.         }
  1415.         \Contao\System::loadLanguageFile('default',$strLanguage);
  1416.         
  1417.         $arrTranslations $this->getTranslations();
  1418.         $strKey $this->getCustomElement()->get('alias');
  1419.         $strField $this->get('alias');
  1420.         
  1421.         // look in global language array
  1422.         if(isset($GLOBALS['TL_LANG'][$strKey][$strField][$strValue]))
  1423.         {
  1424.             $this->setTranslatedValue($strValue,$GLOBALS['TL_LANG'][$strKey][$strField][$strValue],$strLanguage);
  1425.             $this->addTranslation($strValue,$GLOBALS['TL_LANG'][$strKey][$strField][$strValue],$strLanguage);
  1426.         }
  1427.         else
  1428.         {
  1429.             // hook here
  1430.             $strTranslation \PCT\CustomElements\Core\Hooks::callstatic('translateValueHook',array($strField,$strValue,$strKey,$this) );
  1431.             if( !empty($strTranslation) )
  1432.             {
  1433.                 $this->setTranslatedValue($strValue,$strTranslation,$strLanguage);
  1434.                 $this->addTranslation($strValue,$GLOBALS['TL_LANG'][$strKey][$strField][$strValue],$strLanguage);
  1435.             }
  1436.         }
  1437.         if(isset($arrTranslations[$strValue][$strLanguage]))
  1438.         {
  1439.             return true;
  1440.         }
  1441.         
  1442.         return false;
  1443.     }
  1444.     
  1445.     
  1446.     /**
  1447.      * Return the translated value
  1448.      * @param string
  1449.      * @param string
  1450.      */
  1451.     public function getTranslatedValue($strValue,$strLanguage='')
  1452.     {
  1453.         if(strlen($strLanguage) < 1)
  1454.         {
  1455.             $strLanguage \Contao\Input::get('language') ?: \Contao\Input::get('lang') ?: $GLOBALS['TL_LANGUAGE'];
  1456.         }
  1457.         
  1458.         // replace the minus with an underscore
  1459.         if($strLanguage == $GLOBALS['TL_LANGUAGE'] && TL_MODE == 'FE')
  1460.         {
  1461.             $strLanguage str_replace('-','_',$strLanguage);
  1462.         }
  1463.         
  1464.         // search for the translation
  1465.         if(!$this->hasTranslation($strValue,$strLanguage))
  1466.         {
  1467.             return $strValue;
  1468.         }
  1469.         
  1470.         $arrTranslations $this->getTranslations();
  1471.         return $arrTranslations[$strValue][$strLanguage];
  1472.     }
  1473.     
  1474.     
  1475.     /**
  1476.      * Set the translated value
  1477.      * @param string
  1478.      * @param string
  1479.      */
  1480.     public function setTranslatedValue($strValue,$strTranslation,$strLanguage='')
  1481.     {
  1482.         if(strlen($strLanguage) < 1)
  1483.         {
  1484.             $strLanguage \Contao\Input::get('language') ?: \Contao\Input::get('lang') ?: $GLOBALS['TL_LANGUAGE'];
  1485.         }
  1486.         $this->addTranslation($strValue,$strTranslation,$strLanguage);
  1487.     }
  1488.     /**
  1489.      * Check the access rights of the user
  1490.      * @return boolean
  1491.      */
  1492.     public function hasAccess()
  1493.     {
  1494.         if(TL_MODE == 'FE' || !$this->get('protected'))
  1495.         {
  1496.             return true;
  1497.         }
  1498.         
  1499.         $objUser \Contao\BackendUser::getInstance();
  1500.         
  1501.         if($objUser->isAdmin)
  1502.         {
  1503.             return true;
  1504.         }
  1505.         
  1506.         // check if user has group access
  1507.         $usergroups \Contao\StringUtil::deserialize($this->get('user_groups'));
  1508.         if(!empty($usergroups) && is_array($usergroups))
  1509.         {
  1510.             foreach($usergroups as $group)
  1511.             {
  1512.                 if(in_array($group\Contao\StringUtil::deserialize($objUser->groups) ?: array()) )
  1513.                 {
  1514.                     return true;
  1515.                 }
  1516.             }
  1517.         }
  1518.         
  1519.         // check if user itself has access
  1520.         $users \Contao\StringUtil::deserialize($this->get('users'));
  1521.         if(!empty($users) && is_array($users))
  1522.         {
  1523.             if(in_array($objUser->id,$users))
  1524.             {
  1525.                 return true;
  1526.             }
  1527.         }
  1528.         
  1529.         return false;
  1530.     }
  1531. }