system/modules/pct_themer/PCT/ThemeDesigner.php line 1502

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 2016, Premium Contao Themes
  8.  * @author        Tim Gatzky <info@tim-gatzky.de>
  9.  * @package        pct_themer
  10.  */
  11. /**
  12.  * Namespace
  13.  */
  14. namespace PCT;
  15. /**
  16.  * Imports
  17.  */
  18. use Contao\StringUtil;
  19. /**
  20.  * Class file
  21.  * ThemerDesigner
  22.  */
  23. class ThemeDesigner
  24. {
  25.     /**
  26.      * The template file
  27.      * @var string
  28.      */
  29.     protected $strTemplate 'themedesigner';
  30.     
  31.     /**
  32.      * The javascript template file
  33.      * @var string
  34.      */
  35.     protected $strJsTemplate 'js_themedesigner';
  36.     /**
  37.      * The naviagtion template file
  38.      * @var string
  39.      */
  40.     protected $strNavTemplate 'themedesigner';
  41.     
  42.     /**
  43.      * The session name
  44.      * @var string
  45.      */
  46.     protected $strSession 'PCT_THEMEDESIGNER';
  47.     
  48.     /**
  49.      * The config array
  50.      * @var array
  51.      */
  52.     protected $arrConfig = array();
  53.     
  54.     /**
  55.      * The data array
  56.      * @param array
  57.      */
  58.     protected $arrData = array();
  59.     
  60.     /**
  61.      * The css data array
  62.      * @param array
  63.      */
  64.     protected $arrDataCSS = array();
  65.     
  66.     /**
  67.      * Modified array
  68.      * @param array
  69.      */
  70.     protected $arrModified = array();
  71.     
  72.     
  73.     /**
  74.      * Init
  75.      */
  76.     public function __construct($arrData=null)
  77.     {
  78.         if( !isset($GLOBALS['PCT_THEMEDESIGNER_CONFIG']) )
  79.         {
  80.             $GLOBALS['PCT_THEMEDESIGNER_CONFIG'] = array();
  81.         }
  82.         $this->arrConfig $GLOBALS['PCT_THEMEDESIGNER_CONFIG'] ?: array();
  83.         
  84.         // apply data
  85.         if(is_array($arrData))
  86.         {
  87.             $this->setData($arrData);
  88.         }
  89.     }
  90. // TODO: !Config
  91.     /**
  92.      * Set constants and include the preset config
  93.      */
  94.     public function loadConfig($objPage)
  95.     {
  96.         // check if Themer loads a layout
  97.         $objRoot \Contao\PageModel::findByPk($objPage->rootId);
  98.         $strTheme $objRoot->pct_theme ?: 'eclipse_default';
  99.         
  100.         // define constant
  101.         if( \defined('PCT_THEME') === false )
  102.         {
  103.             define('PCT_THEME',$strTheme);
  104.         }
  105.         if(file_exists(TL_ROOT.'/'.PCT_THEMER_PATH.'/config/themedesigner_config.php'))
  106.         {
  107.             require_once TL_ROOT.'/'.PCT_THEMER_PATH.'/config/themedesigner_config.php';
  108.         }
  109.         // include from templates folder
  110.         if(file_exists(TL_ROOT.'/templates/themedesigner/themedesigner_config.php'))
  111.         {
  112.             include_once TL_ROOT.'/templates/themedesigner/themedesigner_config.php';
  113.         }
  114.         
  115.         // load presets
  116.         if(file_exists(TL_ROOT.'/'.PCT_THEMER_PATH.'/config/themedesigner_presets.php'))
  117.         {
  118.             include_once TL_ROOT.'/'.PCT_THEMER_PATH.'/config/themedesigner_presets.php';
  119.         }
  120.         // HOOK to field definitions
  121.         if(is_array($GLOBALS['PCT_THEMEDESIGNER_FIELDS']) && !empty($GLOBALS['PCT_THEMEDESIGNER_FIELDS']))
  122.         {
  123.             foreach($GLOBALS['PCT_THEMEDESIGNER_CONFIG'] as $k => $v)
  124.             {
  125.                 if( empty($v['fields']) )
  126.                 {
  127.                     continue;
  128.                 }
  129.                 
  130.                 foreach($v['fields'] as $kk => $vv)
  131.                 {
  132.                     if( !isset($GLOBALS['PCT_THEMEDESIGNER_FIELDS'][$kk]) )
  133.                     {
  134.                         continue;
  135.                     }
  136.                     $GLOBALS['PCT_THEMEDESIGNER_CONFIG'][$k]['fields'][$kk] = &$GLOBALS['PCT_THEMEDESIGNER_FIELDS'][$kk];
  137.                 }    
  138.             }
  139.         }
  140.         // load custom form templates
  141.         foreach($GLOBALS['PCT_THEMEDESIGNER_CONFIG'] as $arrConfig)
  142.         {
  143.             if(!isset($arrConfig['fields']))
  144.             {
  145.                 continue;
  146.             }
  147.             
  148.             foreach($arrConfig['fields'] as $field)
  149.             {
  150.                 if(strlen($field['template']) > 0)
  151.                 {
  152.                     $path PCT_THEMER_PATH.'/templates/themedesigner/forms';
  153.                     if(file_exists(TL_ROOT.'/'.$path.'/'.$field['template'].'.html5'))
  154.                     {
  155.                         \Contao\TemplateLoader::addFiles(array($field['template'] => $path));
  156.                     }
  157.                 }
  158.             }
  159.         }
  160.     }
  161.     
  162.     
  163.     /**
  164.      * Observe inputs
  165.      * @param object
  166.      */
  167.     public function formListener($objPage)
  168.     {
  169.         // do a reset 
  170.         if((int)\Contao\Input::get('themedesigner_reset') === && (boolean)\Contao\Config::get('pct_themedesigner_hidden') === false)
  171.         {
  172.             $this->reset();
  173.         }
  174.     }
  175.     /**
  176.      * Return the current theme demo name as string
  177.      * @return string
  178.      */
  179.     public function getTheme()
  180.     {
  181.         if(defined('PCT_THEME'))
  182.         {
  183.             return PCT_THEME;
  184.         }
  185.         
  186.         global $objPage;
  187.         // check if Themer loads a layout
  188.         $objRoot \Contao\PageModel::findByPk($objPage->rootId);
  189.         return $objRoot->pct_theme ?: 'eclipse_default';
  190.     }
  191.     
  192.     
  193.     /**
  194.      * Set the current data array
  195.      * @param array
  196.      * @param boolean
  197.      */
  198.     public function setData($arrData,$blnForCSS=false)
  199.     {
  200.         if($blnForCSS)
  201.         {
  202.             $this->arrDataCSS $arrData;
  203.             $this->markAsModified('arrDataCSS');
  204.         }
  205.         else
  206.         {
  207.             $this->arrData $arrData;
  208.             $this->markAsModified('arrData');
  209.         }
  210.     }
  211.     
  212.     
  213.     /**
  214.      * Mark a variable as being modified
  215.      * @param string 
  216.      */
  217.     public function markAsModified($strKey)
  218.     {
  219.         $this->arrModified[$strKey] = true;
  220.     }
  221.     
  222.     
  223.     /**
  224.      * Check if a variable is modified
  225.      * @param string 
  226.      */
  227.     public function isModified($strKey)
  228.     {
  229.         if(array_key_exists($strKey$this->arrModified))
  230.         {
  231.             return true;
  232.         }
  233.         return false;
  234.     }
  235.     
  236.     
  237. // !Ajax    
  238.     
  239.     /**
  240.      * Catch ajax requests
  241.      * @param object
  242.      * @param object
  243.      * @param object
  244.      * called from: generatePage Hook
  245.      */
  246.     public function ajaxListener()
  247.     {
  248.         if( !\Contao\Environment::get('isAjaxRequest') )
  249.         {
  250.             return ;
  251.         }
  252.         
  253.         $strAction '';
  254.         $varValue null;
  255.         $strField '';
  256.         
  257.         // POST listener
  258.         if(\Contao\Input::post('themedesigner'))
  259.         {
  260.             $strAction \Contao\Input::post('action');
  261.             $varValue \Contao\Input::post('value');
  262.             $strField \Contao\Input::post('field');
  263.             
  264.             if($strAction == 'upload')
  265.             {
  266.                 $this->addUpload($strField,$_FILES[$strField]);
  267.             }
  268.         }
  269.         
  270.         // GET listener
  271.         else if(\Contao\Input::get('themedesigner'))
  272.         {
  273.             $strAction \Contao\Input::get('action');
  274.             $varValue \Contao\Input::get('value');
  275.             $strField \Contao\Input::get('field');
  276.         }
  277.         
  278.         // store value in the session
  279.         if(strlen($strField) > 0)
  280.         {
  281.             // @var object \Session
  282.             $objSession \Contao\System::getContainer()->get('session');;
  283.         
  284.             $arrSession $objSession->get$this->strSession );
  285.             $arrSession['VALUES'][$strField] = $varValue;
  286.             $objSession->set$this->strSession,$arrSession );
  287.         }
  288.         
  289.         // multiple fields and values
  290.         if( \Contao\Input::get('fields') && \Contao\Input::get('values') )
  291.         {
  292.             
  293.         }
  294.     }
  295.     
  296.     
  297.     /**
  298.      * Validate a value and return its proper converted value for CSS usage
  299.      * @param string||mixed        The value by the form field
  300.      * @param string            The field name
  301.      * @param array                The field definition
  302.      * @return string            The value proper for CSS usage
  303.      */
  304.     public function validateValueForCSS($varValue$strField=''$arrField=array())
  305.     {
  306.         // decode any html entities coming from ajax
  307.         $varReturn html_entity_decode($varValue);
  308.         
  309.         // font family
  310.         if( isset($arrField['config']['isFontPicker']) )
  311.         {
  312.             // font family is set
  313.             $arrFont $this->getFonts($varValue);
  314.             if( isset($arrFont['family']) )
  315.             {
  316.                 $varReturn $arrFont['family'];
  317.             }
  318.         }
  319.         return $varReturn;
  320.     }
  321. // !Field and value management    
  322.     
  323.     /**
  324.      * Find a value by a field name
  325.      * @param string
  326.      * @return mixed||null
  327.      */
  328.     public function findValueByField($strSelector$arrField=array())
  329.     {
  330.         $arrSession $this->getSession();
  331.         
  332.         // ident for font picker style selection
  333.         $strStyleIdent $GLOBALS['PCT_THEMEDESIGNER']['fontPickerStyleIdent'] ?: '_style';
  334.         $strWeightIdent $GLOBALS['PCT_THEMEDESIGNER']['fontPickerWeightIdent'] ?: '_weight';
  335.         
  336.         $blnIsFontStyle false;
  337.         $blnIsFontWeight false;
  338.         
  339.         if(strlen(strpos($strSelector$strStyleIdent)) > 0)
  340.         {
  341.             $blnIsFontStyle true;
  342.         }
  343.         else if(strlen(strpos($strSelector$strWeightIdent)) > 0)
  344.         {
  345.             $blnIsFontWeight true;
  346.         }
  347.         
  348.         $selector $strSelector;
  349.         if($blnIsFontStyle === true || $blnIsFontWeight === true)
  350.         {
  351.             $tmp explode('_'$strSelector);
  352.             $selector $tmp[0];
  353.             unset($tmp);
  354.         }
  355.         
  356.         if(count($arrField) < 1)
  357.         {
  358.             // field definition
  359.             $arrField $this->getFieldDefinition($selector);
  360.         }
  361.         $varReturn null;
  362.         // value from ajax
  363.         if(\Contao\Environment::get('isAjaxRequest') && \Contao\Input::get('field') == $strSelector)
  364.         {
  365.             $varReturn \Contao\Input::get('value');
  366.         }
  367.         // value from session in Frontend
  368.         else if(isset($arrSession['VALUES'][$strSelector]) && TL_MODE == 'FE')
  369.         {
  370.             $varReturn $arrSession['VALUES'][$strSelector];
  371.         }
  372.         // value from data array
  373.         else if( \array_key_exists($strSelector,$this->arrData) )
  374.         {
  375.             $varReturn $this->arrData[$strSelector];
  376.         }
  377.         // fallback load default value
  378.         else if(isset($arrField['default']))
  379.         {
  380.             $varReturn $arrField['default'];
  381.             
  382.             if( ($blnIsFontStyle === true || $blnIsFontWeight === true) && isset($arrField['default_style']) )
  383.             {
  384.                 $tmp explode(':'$arrField['default_style']);
  385.                 if($blnIsFontStyle)
  386.                 {
  387.                     $varReturn $tmp[0];
  388.                 }
  389.                 if($blnIsFontWeight)
  390.                 {
  391.                     $varReturn $tmp[1];
  392.                 }
  393.                 unset($tmp);
  394.             }
  395.         }
  396.         
  397.         return $varReturn;
  398.     }
  399.     
  400.     
  401.     /**
  402.      * Get a field definition and return its array
  403.      * @param string    Field name or selector
  404.      * @param string    The config section 
  405.      * @return array
  406.      */
  407.     public function getFieldDefinition($strField)
  408.     {
  409.         if(strlen($strField) < 1)
  410.         {
  411.             return array();
  412.         }
  413.         
  414.         $arrConfig $GLOBALS['PCT_THEMEDESIGNER_CONFIG'];
  415.         
  416.         // brute force
  417.         if($GLOBALS['PCT_THEMEDESIGNER']['useFlatSelectors'] === true)
  418.         {
  419.             foreach($arrConfig as $strSection => $arrData)
  420.             {
  421.                 if(!isset($arrData['fields']))
  422.                 {
  423.                     continue;
  424.                 }
  425.                 
  426.                 if(!is_array($arrData['fields']))
  427.                 {
  428.                     continue;
  429.                 }
  430.                 
  431.                 if(!array_key_exists($strField$arrData['fields']))
  432.                 {
  433.                     continue;
  434.                 }
  435.                 
  436.                 foreach($arrData['fields'] as $name => $arrField)
  437.                 {
  438.                     if($name == $strField)
  439.                     {
  440.                         return $arrField;
  441.                     }
  442.                     else if( isset($arrField['name']) && $arrField['name'] == $strField)
  443.                     {
  444.                         return $arrField;
  445.                     }
  446.                 }
  447.             }
  448.         }
  449.         else
  450.         {
  451.             $tmp explode('__'$strField);
  452.             if(count($tmp) > 1)
  453.             {
  454.                 $strSection $tmp[0];
  455.                 $strField $tmp[1];
  456.             }
  457.             
  458.             return isset($arrConfig[$strSection]['fields'][$strField]) ? $arrConfig[$strSection]['fields'][$strField] : array();
  459.         }
  460.     }
  461.     
  462.     
  463.     /**
  464.      * Find a field definition by a field name
  465.      * @param string
  466.      */
  467.     public static function findByName($strField)
  468.     {
  469.         $_this = new static();
  470.         return $_this->getFieldDefinition($strField);
  471.     }
  472. // !Fonts management
  473.     
  474.     
  475.     /**
  476.      * Return all field definitions by a type
  477.      * @return array
  478.      */
  479.     public static function getFontPickers()
  480.     {
  481.         $arrReturn = array();
  482.         
  483.         foreach($GLOBALS['PCT_THEMEDESIGNER_CONFIG'] as $strSection => $arrData)
  484.         {
  485.             if( empty($arrData['fields']) )
  486.             {
  487.                 continue;
  488.             }
  489.             
  490.             foreach($arrData['fields'] as $strField => $arrField)
  491.             {
  492.                 if($GLOBALS['PCT_THEMEDESIGNER']['useFlatSelectors'] === false)
  493.                 {
  494.                     $strField $strSection.'__'.$strField;
  495.                 }
  496.                 
  497.                 if(isset($arrField['config']['isFontPicker']))
  498.                 {
  499.                     $arrReturn[$strField] = $arrField;
  500.                 }
  501.             }
  502.         }
  503.         
  504.         return $arrReturn;
  505.     }
  506.     
  507.     
  508.     /**
  509.      * Return a font definition or all fonts as array
  510.      * @param string    Font key
  511.      * @return array
  512.      */
  513.     public static function getFonts($strFont='')
  514.     {
  515.         if(strlen($strFont) < 1)
  516.         {
  517.             return $GLOBALS['PCT_THEMEDESIGNER']['fonts'] ?: array();
  518.         }
  519.         else
  520.         {
  521.             return $GLOBALS['PCT_THEMEDESIGNER']['fonts'][$strFont] ?: array();
  522.         }
  523.     }
  524.     
  525.     
  526. // !CSS    
  527.     
  528.     
  529.     /**
  530.      * Prepare the CSS
  531.      * @param array        The values array
  532.      * @return string    The css 
  533.      */
  534.     public function prepareCSS($arrData)
  535.     {
  536.         // @var object \FontendTemplate
  537.         $objTemplate = new \Contao\FrontendTemplate('stylesheet');
  538.         
  539.         if( \defined('LAYOUT_CSS_IS_ACTIVE') )
  540.         {
  541.             $objTemplate->layout_css_is_active true;
  542.         }
  543.         
  544.         $objTemplate->PCT_THEME PCT_THEME;
  545.         foreach($arrData as $key => $value)
  546.         {
  547.             if( empty($value) === true )
  548.             {
  549.                 continue;
  550.             }
  551.             // remove caching helpers
  552.             if( (is_object($value) || is_array($value)) && !empty($value))
  553.             {
  554.                 foreach($value as $k => $v)
  555.                 {
  556.                     // remove caching helpers
  557.                     if(strlen(strpos($v'?')) > 0)
  558.                     {
  559.                         $tmp explode('?'$v);
  560.                         if(is_object($value))
  561.                         {
  562.                             $value->{$k} = $tmp[0];
  563.                         }
  564.                         else if(is_array($value))
  565.                         {
  566.                             $value[$k] = $tmp[0];
  567.                         }
  568.                     }
  569.                 }
  570.             }
  571.             else if(strlen(strpos($value'?')) > 0)
  572.             {
  573.                 $value rtrim($value,'?');
  574.             }
  575.             
  576.             $objTemplate->{$key} = $value;
  577.         }
  578.         
  579.         // turn debug mode of so contao will not write debug information to the template
  580.         $blnDebug $GLOBALS['TL_CONFIG']['debugMode'];
  581.         $GLOBALS['TL_CONFIG']['debugMode'] = false;
  582.         
  583.         // parse template and replace Inserttags
  584.         $strReturn $objTemplate->parse();
  585.         
  586.         // absolute paths should be relative to CSS file
  587.         $preg preg_match_all('/files(.*?)\)/',$strReturn,$arrPaths);
  588.         if($preg)
  589.         {
  590.             $separator '/';
  591.             $_arrBase array_filter(explode($separatorstr_replace(basename($GLOBALS['PCT_THEMEDESIGNER_CSS_FILE']),'',$GLOBALS['PCT_THEMEDESIGNER_CSS_FILE']) ));
  592.             
  593.             foreach($arrPaths[0] as $strPath)
  594.             {
  595.                 $strPath rtrim($strPath,')');
  596.                 
  597.                 $pathinfo pathinfortrim($strPath,')') );
  598.                 $path $pathinfo['dirname'];
  599.                 $file $pathinfo['basename'];
  600.                 
  601.                 $_arrPath explode($separator,$path);
  602.                 
  603.                 $relative '';
  604.                 foreach(array_diff($_arrPath,$_arrBase) as $a)
  605.                 {
  606.                     $relative .= '../'.$a;
  607.                 }
  608.                 $relative .= '/'.$file;
  609.                 
  610.                 $strReturn str_replace($strPath$relative$strReturn);
  611.             }
  612.         }
  613.         $strReturn preg_replace("/^\s*/m"""$strReturn);
  614.         
  615.         // turn debug mode back on
  616.         $GLOBALS['TL_CONFIG']['debugMode'] = $blnDebug;
  617.         
  618.         return $strReturn;
  619.     }
  620.     
  621.     
  622.     /**
  623.      * Prepare the javascript CSS
  624.      * @param array        The values array
  625.      * @return string    The css 
  626.      */
  627.     public function prepareJavascriptCSS($arrData)
  628.     {
  629.         // @var object \FontendTemplate
  630.         $objTemplate = new \Contao\FrontendTemplate('js_stylesheet');
  631.         $objTemplate->arrCSS = array();
  632.         $objTemplate->arrFields = array();
  633.         
  634.         if( \defined('LAYOUT_CSS_IS_ACTIVE') )
  635.         {
  636.             $objTemplate->layout_css_is_active true;
  637.         }
  638.         
  639.         // @var object ThemeDesigner
  640.         $objTemplate->ThemeDesigner = new \PCT\ThemeDesigner;
  641.         
  642.         // take the current stylesheet template and observe it
  643.         // @var \File
  644.         $objFile = new \Contao\Filestr_replace(TL_ROOT,'',\Contao\TemplateLoader::getPath('stylesheet','html5')), true );
  645.         
  646.         $strContent $objFile->getContent();
  647.         #$arrContent = $objFile->getContentAsArray();
  648.         
  649.         # remove everything between /* and */
  650.         $strContent preg_replace("!/\*.*?\*/!ms"""$strContent);
  651.         
  652.         $strContent str_replace("\r"''$strContent);
  653.         
  654.         # remove everything between /* and */
  655.         
  656.         $strJS $strContent;
  657.         
  658.         // remove php comments
  659.         #$strJS = preg_match("!/<?php*?\>/!ms", "", $strJS);
  660.         
  661.         // capsule if
  662.         #$strJS = str_replace(array('):','endif;'), array(') {','}'),$strJS);
  663.         
  664.         // remove all php calls
  665.         /*
  666.         $strJS = str_replace(array('<?php','endif;','<?=','?>'), '',$strJS);
  667.         */
  668.         
  669.         $inserttag_sign '#';
  670.         $this_replacer 'objData';
  671.         
  672.         // replace the php $this
  673.         // capsule field calls in inserttag style
  674.         #$strJS = preg_replace("/this->(.*?);/", str_repeat($inserttag_sign, 3).'$1'.str_repeat($inserttag_sign, 3),$strJS);
  675.         
  676.         #$strJS = str_replace('$'.$inserttag_sign, $inserttag_sign, $strJS);
  677.         
  678.         # remove whitespaces after semicolons
  679.         #$strJS = preg_replace("/;\s+/m", "; ", $strJS);
  680.         
  681.         # remove whitespaces after {
  682.         #$strJS = preg_replace("/{\s+/m", "{ ", $strJS);
  683.         
  684.         # remove whitespace before {
  685.         #$strJS = preg_replace("/\s+{/m", " {", $strJS);
  686.         
  687.         # replace several newlines with one
  688.         #$strJS = preg_replace("/\n{2,}/m", "\n", $strJS);
  689.         
  690.         # Leading whitespace
  691.         $strJS preg_replace("/^\s*/m"""$strJS);
  692.         
  693.         // break content into single lines
  694.         $arrLines array_map('trim',array_filter(preg_split('/$\R?^/m'$strJS),'strlen'));
  695.         
  696.         // remove php document start
  697.         $arrLines[0] = str_replace('<?php','',$arrLines[0]);
  698.         
  699.         $arrValidCssChars = array('.','@','#');
  700.         
  701.         $arrJScontent = array();
  702.         
  703.         $if_open false;
  704.         $definitions = array();
  705.         foreach($arrLines as $i => $strLine)
  706.         {
  707.             $firstChar substr($strLine0,1);
  708.             
  709.             // if start
  710.             if(strlen(strpos($strLine'<?php if')) > 0)
  711.             {
  712.                 // start a new defintion collection
  713.                 $definitions = array("css += '");
  714.                 
  715.                 // mark as open if statement
  716.                 $if_open true;
  717.                 
  718.                 // strip the php call
  719.                 $strLine trimstr_replace( array('<?php',':','?>'), array('','','{'), $strLine ) );
  720.                 
  721.                 // replace the $this-> call
  722.                 
  723.                 // extract variable
  724.                 #$var = str_replace('$this->','',substr($strLine, strpos($strLine, '(') + 1, strrpos($strLine,')') - strlen($strLine) ) );
  725.                 
  726.                 $strLine str_replace( array('$this->','->'), array($this_replacer.'.','.'), $strLine);
  727.                 
  728.                 $arrJScontent[$i] = "\n".$strLine;
  729.             }
  730.             
  731.             // endif
  732.             else if(strlen(strpos($strLine'<?php endif')) > 0)
  733.             {
  734.                 // strip the php call
  735.                 $strLine trimstr_replace( array('<?php','endif;','?>'), array('','','}'), $strLine ) );
  736.                 
  737.                 // if statement closes
  738.                 $if_open false;
  739.                 
  740.                 $definition implode(""$definitions)."';";
  741.                 // insert the definition on a line before
  742.                 $arrJScontent[$i-1] = $definition;
  743.                 
  744.                 $arrJScontent[$i] = $strLine."\n";
  745.             }
  746.             else
  747.             {
  748.                 // strip php calls
  749.                 $strLine trimstr_replace( array('<?=','?>'), ''$strLine ) );
  750.                 
  751.                 // replace the $this-> call
  752.                 
  753.                 $preg preg_match_all("/this->(.*?)\;/"$strLine,$arrVars);
  754.                 if($preg)
  755.                 {
  756.                     foreach($arrVars[1] as $i => $var)
  757.                     {
  758.                         $search $arrVars[0][$i];
  759.                         
  760.                         //remove unwanted chars
  761.                         $var trimstr_replace(array('(',')'),'',$var) );
  762.                         
  763.                         // split nested vars like $a->b->c
  764.                         $arr array_filter(explode('->'$var));
  765.                         
  766.                         $tmp '';
  767.                         $replace = array();
  768.                         foreach($arr as $v)
  769.                         {
  770.                             #$tmp .= '["'.$v.'"]';
  771.                             $tmp .= '.'.$v.'';
  772.                         }
  773.                         $tmp $this_replacer.$tmp;
  774.                         $strLine str_replace($search"'+".trim($tmp)."+'"$strLine);
  775.                         unset($tmp);
  776.                     }
  777.                     
  778.                     $strLine str_replace(array('$this->','$'), ''$strLine);
  779.                     // replace whitespace before units and end of call
  780.                     $strLine str_replace(array(' px'' %'), array('px','%'), $strLine);
  781.                 }
  782.                 
  783.                 // remove double semicolons and space and backslahes like in content: "\abc";
  784.                 $strLine str_replace(array(';;',' ;','\\'),array(';',';','\\\\'), $strLine);
  785.                 
  786.                 // remove unexpected strings
  787.                 if(strlen(strpos($strLine"''")) > 0)
  788.                 {
  789.                     $strLine str_replace("''"'""'$strLine);
  790.                 }
  791.                 
  792.                 if($if_open)
  793.                 {
  794.                     $definitions[] = $strLine ' ';
  795.                 }
  796.                 // single line style
  797.                 else
  798.                 {
  799.                     $arrJScontent[$i] = "css +='".$strLine."';";
  800.                 }
  801.                 
  802.                 continue;
  803.             }
  804.         }
  805.         
  806.         #array_insert($arrJScontent, 0, array("var css = '';"));
  807.         
  808.         $objTemplate->arrJavascript $arrJScontent;
  809.         $objTemplate->js implode("\n"$arrJScontent);
  810.         $objTemplate->baseVariables 'var css = ""; var objData = {}; var PCT_THEME = "'.PCT_THEME.'"';    
  811.         
  812.         return $objTemplate->parse();
  813.     }
  814.     
  815.     
  816.     /**
  817.      * Get the current data as array
  818.      * @param boolean    For CSS use or raw data use 
  819.      * @return array
  820.      */
  821.     public function getData($blnForCSS=false)
  822.     {
  823.         if($this->isModified('arrData') && !$blnForCSS)
  824.         {
  825.             return $this->arrData;
  826.         }
  827.         
  828.         if($this->isModified('arrDataCSS') && $blnForCSS)
  829.         {
  830.             return $this->arrDataCSS;
  831.         }
  832.         
  833.         // ident for font picker style selection
  834.         $strStyleIdent $GLOBALS['PCT_THEMEDESIGNER']['fontPickerStyleIdent'] ?: '_style';
  835.         $strWeightIdent $GLOBALS['PCT_THEMEDESIGNER']['fontPickerWeightIdent'] ?: '_weight';
  836.         
  837.         // get session data
  838.         $arrSession $this->getSession();
  839.         
  840.         // switches are part of the secion session
  841.         $arrSwitch $arrSession['SWITCH'] ?? array();
  842.         
  843.         // write sections as template variables
  844.         $arrSections $this->getMainSections();
  845.         
  846.         $arrReturn = array();
  847.         $arrCounter = array();
  848.         
  849.         foreach($this->arrConfig as $section => $data)
  850.         {
  851.             if(!$GLOBALS['PCT_THEMEDESIGNER']['useFlatSelectors'])
  852.             {
  853.                 $arrReturn[$section] = array();
  854.             }
  855.             
  856.             if(isset($data['fields']) && !empty($data['fields']))
  857.             {
  858.                 foreach($data['fields'] as $name => $arrField)
  859.                 {
  860.                     $selector $section.'__'.$name;
  861.                     
  862.                     // if TD are unique by their name, use them
  863.                     if((boolean)$GLOBALS['PCT_THEMEDESIGNER']['useFlatSelectors'] === true)
  864.                     {
  865.                         $selector $name;
  866.                     }
  867.                     
  868.                     if( isset($arrField['name']) )
  869.                     {
  870.                         $selector $arrField['name'];
  871.                     }
  872.                     
  873.                     $strSwitch $selector.(!empty($arrCounter['SWITCHES'][$selector]) ? '__'.$arrCounter['SWITCHES'][$selector] : '');
  874.                     
  875.                     if( isset($arrField['switch']) && !empty($arrField['switch']) )
  876.                     {
  877.                         $strSwitch $arrField['switch'];
  878.                     }
  879.                     if( !isset($arrCounter['SWITCHES'][$selector]) )
  880.                     {
  881.                         $arrCounter['SWITCHES'][$selector] = 0;
  882.                     }
  883.                     
  884.                     $arrCounter['SWITCHES'][$selector]++;
  885.                     
  886.                     if(!isset($arrField['config']['notActiveByDefault']))
  887.                     {
  888.                         $arrField['config']['notActiveByDefault'] = false;
  889.                     }
  890.                     // bypass fields that are supposed to be turned off
  891.                     if( (isset($arrSwitch[$strSwitch]) && (boolean)$arrSwitch[$strSwitch] === false) || (!isset($arrSwitch[$strSwitch]) && (boolean)$arrField['config']['notActiveByDefault'] === true) )
  892.                     {
  893.                         continue;
  894.                     }
  895.                     
  896.                     // find the value
  897.                     $value $this->findValueByField($selector,$arrField);
  898.                     
  899.                     // upload fields with array values
  900.                     if($arrField['inputType'] == 'upload' && is_array($value))
  901.                     {
  902.                         $objFile = new \Contao\File($value['file'],true);
  903.                         
  904.                         // convert to base64
  905.                         if($objFile->exists())
  906.                         {
  907.                             $value['base64'] = base64_encode$objFile->getContent() );
  908.                         }
  909.                                                 
  910.                         $tmp = new \StdClass;
  911.                         foreach($value as $k => $v)
  912.                         {
  913.                             $tmp->{$k} = $v;
  914.                         }
  915.                         
  916.                         // set template var
  917.                         if(!$GLOBALS['PCT_THEMEDESIGNER']['useFlatSelectors'])
  918.                         {
  919.                             $arrReturn[$section][$selector] = $tmp;
  920.                         }
  921.                         else
  922.                         {
  923.                             $arrReturn[$selector] = $tmp;
  924.                         }
  925.                         
  926.                         unset($tmp);
  927.                         
  928.                         continue;
  929.                     }
  930.                     
  931.                     if(!isset($arrField['eval']['doNotValidateValue']) && $blnForCSS)
  932.                     {    
  933.                         // validate value
  934.                         $value $this->validateValueForCSS($value$selector$arrField);
  935.                         
  936.                     }
  937.                     
  938.                     // skip empty values
  939.                     if(empty($value))
  940.                     {
  941.                         continue;
  942.                     }
  943.                     
  944.                     $objJson json_decode($value);
  945.                     if(is_object($objJson))
  946.                     {
  947.                         $value $objJson;
  948.                     }
  949.                     
  950.                     // set template var for font picker styles
  951.                     if( isset($arrField['config']['isFontPicker']) )
  952.                     {
  953.                         $arrReturn[$selector.$strStyleIdent] = $this->findValueByField($selector.$strStyleIdent);
  954.                         $arrReturn[$selector.$strWeightIdent] = $this->findValueByField($selector.$strWeightIdent);
  955.                     }
  956.                     
  957.                     // set template var
  958.                     if(!$GLOBALS['PCT_THEMEDESIGNER']['useFlatSelectors'])
  959.                     {
  960.                         $arrReturn[$section][$selector] = $value;
  961.                     }
  962.                     else
  963.                     {
  964.                         $arrReturn[$selector] = $value;
  965.                     }
  966.                 }
  967.             }
  968.         }
  969.         
  970.         $arrReturn['PCT_THEME'] = PCT_THEME;
  971.         
  972.         // set data
  973.         $this->setData($arrReturn,$blnForCSS);
  974.         
  975.         return $arrReturn;
  976.     }
  977.     
  978.     
  979.     /**
  980.      * Get the current data arrays formatted for CSS usage
  981.      * @return array
  982.      */
  983.     public function getDataForCSS()
  984.     {
  985.         return $this->getData(true);
  986.     }
  987. // ! Frontend
  988.     
  989.     /**
  990.      * Set the page layout
  991.      * @param object
  992.      * @param object
  993.      * called from : getPageLayout Hook
  994.      */
  995.     public function setLayoutTemplate($objPage$objLayout)
  996.     {
  997.         $objRoot \Contao\PageModel::findByPk($objPage->rootId);
  998.         
  999.         // Render maintenance page if no backend user is logged in
  1000.         if( \version_compare(VERSION,'4.13','>=') && $objRoot->maintenanceMode )
  1001.         {
  1002.             $objTemplate = new \Contao\FrontendTemplate('fe_page');
  1003.             if( $objTemplate->hasAuthenticatedBackendUser() === false )
  1004.             {
  1005.                 return;
  1006.             }    
  1007.         }
  1008.         // Page is excluded by id or theme
  1009.         if(is_array($GLOBALS['PCT_THEMEDESIGNER']['excludes']))
  1010.         {
  1011.             if(in_array($objPage->id$GLOBALS['PCT_THEMEDESIGNER']['excludes']) || in_array($objRoot->pct_theme$GLOBALS['PCT_THEMEDESIGNER']['excludes']))
  1012.             {
  1013.                 return;
  1014.             }
  1015.         }
  1016.         
  1017.         $arrSession $this->getSession();
  1018.         
  1019.         $strIframeUrl $arrSession['IFRAME_URL'] ?? '';
  1020.         $strSessionKey $this->strSession.'_'.$this->getTheme();
  1021.         
  1022.         $tmp explode('?'$strIframeUrl);
  1023.         $strIframeUrl $tmp[0];
  1024.                 
  1025.         // redirect the whole TD page when the iframe page is a root page or has a whole new root page
  1026.         if($strIframeUrl != '' && $strIframeUrl != \Contao\Environment::get('request'))
  1027.         {
  1028.             $objPages \Contao\PageModel::findByAliases(array(str_replace(array('index.php/','.html'),'',$strIframeUrl)));
  1029.             if($objPages !== null && isset($objPages[0]) )
  1030.             {
  1031.                 $objIframePage $objPages[0];
  1032.                 if( $objIframePage->type == 'root' || $objIframePage->rootId != $objPage->rootId)
  1033.                 {
  1034.                     unset($arrSession['IFRAME_URL']);
  1035.                     unset($_SESSION[$strSessionKey]['IFRAME_URL']);
  1036.                     $this->setSession($arrSession);
  1037.                     \Contao\Controller::redirect$objIframePage->getFrontendUrl() );    
  1038.                 }
  1039.             }
  1040.             else
  1041.             {
  1042.                 unset($arrSession['IFRAME_URL']);
  1043.                 unset($_SESSION[$strSessionKey]['IFRAME_URL']);
  1044.                 $this->setSession($arrSession);
  1045.                 \Contao\Controller::reload();    
  1046.             }
  1047.         }
  1048.         
  1049.         // already on the same page and its not a root page
  1050.         if($strIframeUrl != '' && $strIframeUrl == \Contao\Environment::get('request'))
  1051.         {
  1052.             unset($arrSession['IFRAME_URL']);
  1053.             unset($_SESSION[$strSessionKey]['IFRAME_URL']);
  1054.             $this->setSession($arrSession);
  1055.             \Contao\Controller::reload();    
  1056.         }
  1057.         
  1058.         if((boolean)\Contao\Input::get('themedesigner_iframe') === true || (boolean)$GLOBALS['PCT_THEMEDESIGNER']['useIframe'] === false)
  1059.         {
  1060.             $objJsTemplate = new \Contao\FrontendTemplate('js_themedesigner_iframe_helper');
  1061.             $objJsTemplate->themedesigner_ident $arrSession['IDENT'];
  1062.             $objJsTemplate->reset \Contao\Input::get('themedesigner_reset');
  1063.             $objJsTemplate->cookie 'THEMEDESIGNER_IFRAME_'.$this->getTheme();
  1064.             $objJsTemplate->theme $this->getTheme();
  1065.             $GLOBALS['TL_JQUERY'][] = $objJsTemplate->parse();
  1066.             return;
  1067.         }
  1068.         
  1069.         $objLayout->template 'fe_page_themedesigner';
  1070.     }
  1071.     
  1072.     
  1073.     /**
  1074.      * Add the Google fonts to the page layout
  1075.      * @param object
  1076.      * @param object
  1077.      * called from : getPageLayout Hook
  1078.      */
  1079.     public function addFonts($objPage$objLayout)
  1080.     {
  1081.         //-- avoid loading fonts
  1082.         if((boolean)\Contao\Config::get('pct_themedesigner_nofonts') === true)
  1083.         {
  1084.             $objLayout->webfonts '';
  1085.             return;
  1086.         }
  1087.         //--
  1088.         
  1089.         $objTD = new \PCT\ThemeDesigner;
  1090.         
  1091.         // @var object
  1092.         $objRoot \Contao\PageModel::findByPk($objPage->rootId);
  1093.         
  1094.         // @var object
  1095.         $objActiveVersion \PCT\ThemeDesigner\Model::findActiveByTheme($objRoot->pct_theme ?: 'eclipse_default');
  1096.     
  1097.         // get current TD data
  1098.         $arrData $objTD->getData();
  1099.         
  1100.         // load last active version
  1101.         if($objActiveVersion !== null && (boolean)\Contao\Config::get('pct_themedesigner_hidden') === true)
  1102.         {
  1103.             $arrData StringUtil::deserialize($objActiveVersion->data) ?: array();
  1104.             if( !empty($arrData['VALUES']) && is_array($arrData['VALUES']))
  1105.             {
  1106.                 $arrData $arrData['VALUES'];
  1107.             }
  1108.         }
  1109.         
  1110.         // get all font pickers
  1111.         $arrFontPickers $objTD->getFontPickers();
  1112.         
  1113.         // check if any font is selected
  1114.         $arrMatch array_intersect(array_keys($arrData ?: array()), array_keys($arrFontPickers ?: array()));
  1115.         
  1116.         // no fonts selected by user but the layout comes with fontpicker information
  1117.         if(count($arrMatch) < && count($arrFontPickers) > 0)
  1118.         {
  1119.             $arrMatch array_keys($arrFontPickers);
  1120.             foreach($arrFontPickers as $font => $data)
  1121.             {
  1122.                 $font $data['default'];
  1123.                 if( !isset($data['default_style']) )
  1124.                 {
  1125.                     $data['default_style'] = '';
  1126.                 }
  1127.                 $arrData[$font] = array
  1128.                 (
  1129.                     'label' => $data['label'],
  1130.                     'styles' => array($data['default_style']),
  1131.                     'default' => $data['default'],
  1132.                 );
  1133.             }
  1134.         }
  1135.         else if(count($arrMatch) < && count($arrFontPickers) < 1)
  1136.         {
  1137.             return;
  1138.         }
  1139.         
  1140.         $arrFonts = array();
  1141.         foreach($arrMatch as $field)
  1142.         {
  1143.             if(is_array($field))
  1144.             {
  1145.                 continue;
  1146.             }
  1147.             
  1148.             if(!isset($arrData[$field]))
  1149.             {
  1150.                 continue;
  1151.             }
  1152.             
  1153.             $font $arrData[$field];
  1154.             $weights = array();
  1155.             
  1156.             // font definition
  1157.             $arrFont $objTD->getFonts$font );
  1158.             if(empty($arrFont))
  1159.             {
  1160.                 continue;
  1161.             }
  1162.             
  1163.             // default weight
  1164.             if($arrFont['default'] && strlen(strpos($arrFont['default'], ':')) > 0)
  1165.             {
  1166.                 $s explode(':'$arrFont['default']);
  1167.                 $weights[] = $s[1];
  1168.             }
  1169.             
  1170.             // style weights
  1171.             if(!empty($arrFont['styles']))
  1172.             {
  1173.                 foreach($arrFont['styles'] as $style)
  1174.                 {
  1175.                     $s explode(':'$style);
  1176.                     if($s[1])
  1177.                     {
  1178.                         $weights[] = $s[1];
  1179.                     }
  1180.                 }
  1181.             }
  1182.             
  1183.             $arrFonts[] = $font.(count($weights) > ':'.implode(','array_unique($weights)) : '');
  1184.         }
  1185.         
  1186.         $arrFonts array_unique($arrFonts);
  1187.         if(count($arrFonts) < 1)
  1188.         {
  1189.             return;
  1190.         }
  1191.         $arrFonts array_filterarray_uniquearray_merge($arrFontsexplode('|'$objLayout->webfonts) ) ) );
  1192.         
  1193.         $objLayout->webfonts implode('|'$arrFonts);
  1194.     }
  1195.     
  1196.     
  1197.     /**
  1198.      * Add user privacy sensitive javascript to load webfonts depending on user privacy selection
  1199.      * @param object
  1200.      * @param object
  1201.      * @param object
  1202.      * called from getPageLayout Hook
  1203.      */
  1204.     public function addFontsOptin($objPage$objLayout$objPageRegular)
  1205.     {
  1206.         // include fonts in themedesigner iframe
  1207.         if( (int)\Contao\Input::get('themedesigner_iframe') == && isset($objLayout->webfonts) && !empty($objLayout->webfonts) )
  1208.         {
  1209.             $GLOBALS['TL_HEAD'][] = '<link href="https://fonts.googleapis.com/css?family='.$objLayout->webfonts.'" rel="stylesheet">';
  1210.             return;
  1211.         }
  1212.         
  1213.         //-- avoid loading fonts
  1214.         if((boolean)\Contao\Config::get('pct_themedesigner_nofonts') === true)
  1215.         {
  1216.             $objLayout->webfonts '';
  1217.             return;
  1218.         }
  1219.         //--
  1220.         
  1221.         // Skip optin when TD is in development mode | not in live mode
  1222.         if( (boolean)\Contao\Config::get('pct_themedesigner_hidden') === false )
  1223.         {
  1224.             return;
  1225.         }
  1226.         
  1227.         if(empty($objLayout->webfonts))
  1228.         {
  1229.             return;
  1230.         }
  1231.         
  1232.         $strWebfonts $objLayout->webfonts;
  1233.         // clear the regular webfonts to avoid loading by Contao
  1234.         $objLayout->webfonts '';
  1235.         
  1236.         // @var object
  1237.         $objTemplate = new \Contao\FrontendTemplate('js_themedesigner_webfonts_optin');
  1238.         $objTemplate->webfonts $strWebfonts;
  1239.         $objTemplate->privacy_session_name $GLOBALS['PCT_THEMEDESIGNER']['privacy_session_name'] ?: ''
  1240.         $strBuffer $objTemplate->parse();
  1241.         // add to page head
  1242.         $GLOBALS['TL_HEAD'][] = $strBuffer;
  1243.         
  1244.         // add to option vairable
  1245.     #    $objPageRegular->Template->webfonts_optin = $strBuffer;
  1246.     }
  1247.         
  1248.     
  1249.     /**
  1250.      * Add theme designer to the page template
  1251.      * @param object
  1252.      * @param object
  1253.      * @param object
  1254.      * called from: parseTemplate Hook
  1255.      */
  1256.     public function addToTemplate($objTemplate)
  1257.     {    
  1258.         // ThemeDesigner is turned off
  1259.         if(\Contao\Config::get('pct_themedesigner_hidden') && \Contao\Config::get('pct_themedesigner_off'))
  1260.         {
  1261.             return;
  1262.         }
  1263.         
  1264.         // work on fe_page templates only
  1265.         if ( \strpos($objTemplate->getName(), 'fe_page') === false 
  1266.         {
  1267.             return;
  1268.         }
  1269.         global $objPage;
  1270.         // check if Themer loads a layout
  1271.         $objRoot \Contao\PageModel::findByPk($objPage->rootId);
  1272.         // define constants
  1273.         if(strlen($objRoot->pct_theme) > 0)
  1274.         {
  1275.             if( \defined('LAYOUT_CSS_IS_ACTIVE') === false )
  1276.             {
  1277.                 \define('LAYOUT_CSS_IS_ACTIVE',1);
  1278.             }
  1279.             $strTheme $objRoot->pct_theme;
  1280.         }
  1281.         else
  1282.         {
  1283.             $strTheme 'eclipse_default';
  1284.         }
  1285.         
  1286.         // Page is excluded by id or theme
  1287.         if(is_array($GLOBALS['PCT_THEMEDESIGNER']['excludes']))
  1288.         {
  1289.             if(in_array($objPage->id$GLOBALS['PCT_THEMEDESIGNER']['excludes']) || in_array($strTheme$GLOBALS['PCT_THEMEDESIGNER']['excludes']))
  1290.             {
  1291.                 return;
  1292.             }
  1293.         }
  1294.         
  1295.         // load config
  1296.         $this->loadConfig($objPage);
  1297.         
  1298.         // load an active version
  1299.         // @var object 
  1300.         $objActiveVersion \PCT\ThemeDesigner\Model::findActiveByTheme($strTheme);
  1301.         
  1302.         // regular fe_page template is being rendered
  1303.         if ( $objTemplate->getName() !== 'fe_page_themedesigner' 
  1304.         {
  1305.             $objTemplate->pct_layout_css '';
  1306.             
  1307.             $strVersion '';
  1308.             if( $objActiveVersion !== null )
  1309.             {
  1310.                 $strVersion sprintf($GLOBALS['PCT_THEMEDESIGNER_CSS_FILENAME'],$objActiveVersion->id,$objActiveVersion->tstamp);
  1311.             }
  1312.             // layout.css by the theme designer is final
  1313.             $strLayoutCss sprintf($GLOBALS['PCT_THEMEDESIGNER_CSS_FILE'],$strVersion);
  1314.             if(\Contao\Config::get('pct_themedesigner_hidden') && \file_exists(TL_ROOT.'/'.$strLayoutCss))
  1315.             {
  1316.                 $strFile \trim($strLayoutCss);
  1317.                 $objTemplate->pct_layout_css $strFile;
  1318.                 $objTemplate->pct_layout_css_uncached $strFile.'?'.time();    
  1319.             }
  1320.             return;
  1321.         }
  1322.         // Render maintenance page if no backend user is logged in
  1323.         if( \version_compare(VERSION,'4.13','>=') && $objRoot->maintenanceMode && $objTemplate->hasAuthenticatedBackendUser() === false )
  1324.         {
  1325.             return;
  1326.         }
  1327.         // --- THEME DESIGNER 
  1328.         // get session
  1329.         $arrSession $this->getSession();
  1330.         // mobile
  1331.         if( isset($arrSession['MOBILE']) && (boolean)$arrSession['MOBILE'])
  1332.         {
  1333.             $objTemplate->isMobileView true;
  1334.         }
  1335.         // zoom
  1336.         if( isset($arrSession['ZOOM']) && !empty($arrSession['ZOOM']) )
  1337.         {
  1338.             $objTemplate->zoom $arrSession['ZOOM'];
  1339.         }
  1340.         // device
  1341.         if( isset($arrSession['DEVICE']) && !empty($arrSession['DEVICE']) )
  1342.         {
  1343.             $objTemplate->device $arrSession['DEVICE'];
  1344.         }
  1345.         // ThemeDesigner is hidden
  1346.         if(\Contao\Config::get('pct_themedesigner_hidden') || \Contao\Environment::get('isAjaxRequest') || \Contao\Input::get('themedesigner_iframe'))
  1347.         {
  1348.             return;
  1349.         }
  1350.                             
  1351.         // --- Frontend output from here
  1352.         // if session is empty and an active version exists, fill up the session and reload the page
  1353.         if($objActiveVersion !== null && !isset($arrSession['VALUES']))
  1354.         {
  1355.             $arrSession StringUtil::deserialize($objActiveVersion->data) ?: array();
  1356.             if( !isset($arrSession['VALUES']) )
  1357.             {
  1358.                 $arrSession['VALUES'] = array();
  1359.             }
  1360.             
  1361.             // avoid applying an empty data array
  1362.             if(count($arrSession) > 0)
  1363.             {
  1364.                 $this->setSession$arrSession );
  1365.                 \Contao\Controller::reload();
  1366.             }
  1367.         }
  1368.         
  1369.         // load language file
  1370.         \Contao\System::loadLanguageFile('themedesigner');
  1371.         
  1372.         // @var object \PCT\ThemeDesigner\Navigation
  1373.         $objNavi = new \PCT\ThemeDesigner\Navigation;
  1374.         $strNavi $objNavi->render$this->strNavTemplate );
  1375.         
  1376.         // add to page template
  1377.         $objTemplate->pct_themedesigner_navigation $strNavi;
  1378.         
  1379.         // @var object \PCT\ThemeDesigner\Versions
  1380.         $objVersions = new \PCT\ThemeDesigner\Versions;
  1381.         $strVersions $objVersions->render();
  1382.         
  1383.         // add to page template
  1384.         $objTemplate->pct_themedesigner_versions $strVersions;
  1385.         
  1386.         // @var object \PCT\ThemeDesigner
  1387.         $objTD = new \PCT\ThemeDesigner;
  1388.         $strTD $objTD->render$this->strTemplate );
  1389.         
  1390.         // add to page template
  1391.         $objTemplate->pct_themedesigner $strTD;
  1392.     
  1393.         // minify button toggler
  1394.         $objTemplate->pct_themedesigner_toggler '<a id="themedesigner_toggler" title="'.$GLOBALS['TL_LANG']['pct_themedesigner']['toggler_title'].'" class="'.($arrSession['MINIFIED'] ? 'active' '').'" data-state="'.(!$arrSession['MINIFIED'] ? 0).'" class="themedesigner_minify"></a>';
  1395.         
  1396.         // reset button
  1397.         $objTemplate->pct_themedesigner_reset '<a id="themedesigner_reset" title="'.$GLOBALS['TL_LANG']['pct_themedesigner']['reset_title'].'" class="themedesigner_reset_button" href="'.$objPage->getFrontendUrl().'?themedesigner_reset=1'.'" title="ThemeDesigner reset"></a>';
  1398.         
  1399.         // mobile button
  1400.         $objTemplate->pct_themedesigner_mobile '<a id="themedesigner_mobile" title="'.$GLOBALS['TL_LANG']['pct_themedesigner']['mobile_title'].'" class="themedesigner_mobile_button"></a>';
  1401.         
  1402.         // quick info
  1403.         $objQuickinfo = new \PCT\ThemeDesigner\Quickinfo;
  1404.         $strQuickinfo $objQuickinfo->render();
  1405.         
  1406.         $objTemplate->pct_themedesigner_quickinfo $strQuickinfo;
  1407.         
  1408.         // no save info
  1409.         $objVersions \PCT\ThemeDesigner\Model::findByTheme(PCT_THEME);
  1410.         if($objVersions === null)
  1411.         {
  1412.             $objTemplate->pct_themedesigner_nosave $GLOBALS['TL_LANG']['pct_themedesigner']['nosave_info'] ?: 'No save data yet';
  1413.         }    
  1414.     }
  1415.     /**
  1416.      * Add theme designer to the page layout and include scripts via GLOBALS
  1417.      * @param object
  1418.      * @param object
  1419.      * called from: getPageLayout Hook
  1420.      */
  1421.     public function addToLayout($objPage$objLayout)
  1422.     {    
  1423.         // ThemeDesigner is turned off
  1424.         if(\Contao\Config::get('pct_themedesigner_hidden') && \Contao\Config::get('pct_themedesigner_off'))
  1425.         {
  1426.             return;
  1427.         }
  1428.         
  1429.         // load config
  1430.         $this->loadConfig($objPage);
  1431.         
  1432.         // check if Themer loads a layout
  1433.         $objRoot \Contao\PageModel::findByPk($objPage->rootId);
  1434.         // define constants
  1435.         if(strlen($objRoot->pct_theme) > 0)
  1436.         {
  1437.             \define('LAYOUT_CSS_IS_ACTIVE',1);
  1438.             $strTheme $objRoot->pct_theme;
  1439.         }
  1440.         else
  1441.         {
  1442.             $strTheme 'eclipse_default';
  1443.         }
  1444.         // Page is excluded by id or theme
  1445.         if(is_array($GLOBALS['PCT_THEMEDESIGNER']['excludes']))
  1446.         {
  1447.             if(in_array($objPage->id$GLOBALS['PCT_THEMEDESIGNER']['excludes']) || in_array($strTheme$GLOBALS['PCT_THEMEDESIGNER']['excludes']))
  1448.             {
  1449.                 return;
  1450.             }
  1451.         }
  1452.         // @var object
  1453.         $objActiveVersion \PCT\ThemeDesigner\Model::findActiveByTheme($strTheme);
  1454.         $strVersion '';
  1455.         if( $objActiveVersion !== null )
  1456.         {
  1457.             $strVersion sprintf($GLOBALS['PCT_THEMEDESIGNER_CSS_FILENAME'],$objActiveVersion->id,$objActiveVersion->tstamp);
  1458.         }
  1459.         // layout.css by the theme designer is final
  1460.         $strLayoutCss sprintf($GLOBALS['PCT_THEMEDESIGNER_CSS_FILE'],$strVersion);
  1461.         $strFile '';
  1462.         if(\Contao\Config::get('pct_themedesigner_hidden') && \file_exists(TL_ROOT.'/'.$strLayoutCss))
  1463.         {
  1464.             $strFile trim($strLayoutCss);
  1465.             return;
  1466.         }
  1467.         
  1468.         // get session
  1469.         $arrSession $this->getSession();
  1470.         
  1471.         // add body classes
  1472.         $arrClasses = array('themedesigner_active');
  1473.         if(\Contao\Config::get('pct_themedesigner_hidden'))
  1474.         {
  1475.             $arrClasses[] = 'themedesigner_hidden';
  1476.         }
  1477.         
  1478.         // minified
  1479.         if(isset($arrSession['MINIFIED']) && (boolean)$arrSession['MINIFIED'] )
  1480.         {
  1481.             $arrClasses[] = 'themedesigner_minified';
  1482.         }
  1483.         
  1484.         // mobile
  1485.         if(isset($arrSession['MOBILE']) && (boolean)$arrSession['MOBILE'] )
  1486.         {
  1487.             $arrClasses[] = 'themedesigner_mobile';
  1488.             
  1489.             if(\Contao\Input::get('themedesigner_iframe'))
  1490.             {
  1491.                 $arrClasses[] = 'mobile';
  1492.             }
  1493.         }
  1494.         // zoom
  1495.         $intZoom $arrSession['ZOOM'] ?? 100;
  1496.         if($intZoom 0)
  1497.         {
  1498.             $arrClasses[] = 'zoom_'.$intZoom;
  1499.             
  1500.             if(\Contao\Input::get('themedesigner_iframe'))
  1501.             {
  1502.                 $arrClasses[] = 'zoom_'.$intZoom;
  1503.             }
  1504.         }
  1505.         // device
  1506.         $strDevice $arrSession['DEVICE'] ?? 'desktop';
  1507.         if($strDevice)
  1508.         {
  1509.             $arrClasses[] = 'themedesigner_'.$strDevice;
  1510.             
  1511.             if(\Contao\Input::get('themedesigner_iframe'))
  1512.             {
  1513.                 $arrClasses[] = $strDevice.' themedesigner_'.$strDevice;
  1514.             }
  1515.         }
  1516.         // add themedesigner classes to body 
  1517.         if( (boolean)\Contao\Config::get('pct_themedesigner_hidden') === false )
  1518.         {
  1519.             $objLayout->cssClass .= ' '.\implode(' ',$arrClasses);
  1520.         }
  1521.         
  1522.         // include Javascript CSS only when TD is not hidden and active or no active version exists
  1523.         if((boolean)\Contao\Config::get('pct_themedesigner_hidden') === false || $objActiveVersion === null)
  1524.         {
  1525.             $arrData $this->getData();
  1526.             
  1527.             // load data from last active session
  1528.             if($objActiveVersion !== null && (empty($arrData) || empty($arrSession)))
  1529.             {
  1530.                 $arrData StringUtil::deserialize($objActiveVersion->data) ?: array();
  1531.                 $this->setData($arrData);
  1532.             }
  1533.             
  1534.             // prepare the css for javascript usage
  1535.             $strCSSforJS $this->prepareJavascriptCSS($arrData);
  1536.             
  1537.             // include the Javascript CSS
  1538.             $GLOBALS['TL_JQUERY'][] = $strCSSforJS;
  1539.         }
  1540.         
  1541.         // ThemeDesigner is hidden
  1542.         if(\Contao\Config::get('pct_themedesigner_hidden') || \Contao\Environment::get('isAjaxRequest') || \Contao\Input::get('themedesigner_iframe'))
  1543.         {
  1544.             return;
  1545.         }
  1546.                         
  1547.         // if session is empty and an active version exists, fill up the session and reload the page
  1548.         if($objActiveVersion !== null && empty($arrSession))
  1549.         {
  1550.             $arrSession StringUtil::deserialize($objActiveVersion->data) ?: array();
  1551.             
  1552.             // avoid applying an empty data array
  1553.             if(count($arrSession) > 0)
  1554.             {
  1555.                 $this->setSession$arrSession );
  1556.                 \Contao\Controller::reload();
  1557.             }
  1558.         }
  1559.         
  1560.         // load language file
  1561.         \Contao\System::loadLanguageFile('themedesigner');
  1562.         
  1563.         // load ThemeDesigner js class
  1564.         $objJsTemplate = new \Contao\FrontendTemplate$this->strJsTemplate );
  1565.         $objJsTemplate->whoAmI $this->strJsTemplate;
  1566.         $objJsTemplate->stylesheet $strFile;
  1567.         $objJsTemplate->timestamp time();
  1568.         $objJsTemplate->theme $strTheme;
  1569.         $objJsTemplate->device $strDevice;
  1570.         $objJsTemplate->zoom $intZoom;
  1571.         // load themedesigner script
  1572.         $GLOBALS['TL_JQUERY'][] = $objJsTemplate->parse();
  1573.         
  1574.         // load styling
  1575.         $GLOBALS['TL_CSS'][] = $GLOBALS['PCT_THEMEDESIGNER']['CSS'];
  1576.         
  1577.         // no save info
  1578.         $objVersions \PCT\ThemeDesigner\Model::findByTheme(PCT_THEME);
  1579.         if($objVersions === null)
  1580.         {
  1581.             // add body class
  1582.             $objLayout->cssClass .= ' themedesigner_unsaved';
  1583.         }
  1584.     }
  1585. //! File management, stylesheet creation
  1586.     
  1587.     
  1588.     /**
  1589.      * Create the stylesheet from the current data and return its path
  1590.      * @param string    The current theme name
  1591.      * return string
  1592.      */
  1593.     public function createStylesheet($strTheme='')
  1594.     {
  1595.         // prepare the css
  1596.         $strCSS $this->prepareCSS$this->getData(true) );
  1597.         
  1598.         // write the css file and set as new layout.css
  1599.         $objFile $this->writeCSS($strCSS,$strTheme);
  1600.         
  1601.         $strFile '';
  1602.         
  1603.         $time time();
  1604.         if($objFile->exists())
  1605.         {
  1606.             $strFile trim($objFile->path);
  1607.             // append timestamp to trick server caches
  1608.             if($GLOBALS['PCT_THEMEDESIGNER']['bypassServerCache'])
  1609.             {
  1610.                 $strFile .= '?'.$time;
  1611.             }
  1612.         }
  1613.         
  1614.         return $strFile;
  1615.     }
  1616.     
  1617.         
  1618.     /**
  1619.      * Public access to writeFile() method
  1620.      * @param string
  1621.      * @return object
  1622.      */
  1623.     public function writeCSS($strCSS,$strTheme='')
  1624.     {
  1625.         return $this->writeFile(sprintf($GLOBALS['PCT_THEMEDESIGNER_CSS_FILE'],$strTheme),$strCSS);
  1626.     }
  1627.     
  1628.     /**
  1629.      * Write the css file and return its path and file name
  1630.      * @param string    Path
  1631.      * @param string    Content
  1632.      * @return object
  1633.      */
  1634.     protected function writeFile($strFile,$strContent)
  1635.     {
  1636.         if(strlen($strFile) < 1)
  1637.         {
  1638.             $strFile sprintf($GLOBALS['PCT_THEMEDESIGNER_CSS_FILE'],'');
  1639.         }
  1640.         
  1641.         $objFile = new \Contao\File($strFile);
  1642.         $strContent str_replace( array("\t","\r"), ""$strContent);
  1643.         $strContent preg_replace(array('/^[\t ]+/m''/[\t ]+$/m''/\n\n+/'), array(''''"\n"), $strContent);
  1644.         
  1645.         if(\Contao\Config::get('gzipScripts'))
  1646.         {
  1647.             $objMinify = new \MatthiasMullie\Minify\CSS();
  1648.             $objMinify->add($strContent);
  1649.             $strContent $objMinify->minify();
  1650.         }
  1651.         
  1652.         if( !$objFile->exists() )
  1653.         {
  1654.             \Contao\File::putContent($strFile,$strContent);
  1655.         }
  1656.         else
  1657.         {
  1658.             $objFile->write($strContent);
  1659.         }
  1660.         
  1661.         return $objFile;
  1662.     }
  1663.     
  1664. // TODO: !render
  1665.     
  1666.     /**
  1667.      * Render the ThemeDesigner and return it as html string
  1668.      * @return string
  1669.      */
  1670.     public function render($strTemplate='themedesigner')
  1671.     {
  1672.         $objTemplate = new \Contao\FrontendTemplate($strTemplate);
  1673.         $objTemplate->type 'themedesigner';
  1674.         
  1675.         $arrSession $this->getSession();
  1676.         $strContent '';
  1677.         
  1678.         // devices
  1679.         $objDevicesAndZoom = new \Contao\FrontendTemplate('td_devices_and_zoom');
  1680.         $objDevicesAndZoom->device $arrSession['DEVICE'] ?? 'desktop';
  1681.         $objDevicesAndZoom->zoom $arrSession['ZOOM'] ?? 100;
  1682.         // render devices and zoom
  1683.         $strContent .= $objDevicesAndZoom->parse();
  1684.         // get the main sections
  1685.         $arrSections $this->getSections();
  1686.         if(count($arrSections) < 1)
  1687.         {
  1688.             $objTemplate->empty true;
  1689.             return $objTemplate->parse();
  1690.         }
  1691.         
  1692.         $objTemplate->sections $arrSections;    
  1693.         
  1694.         // prepare section
  1695.         $arrItems = array();
  1696.         
  1697.         foreach($arrSections as $section)
  1698.         {
  1699.             $objSection = new \PCT\ThemeDesigner\Section($section);
  1700.             if($objSection === null)
  1701.             {
  1702.                 continue;
  1703.             }
  1704.             
  1705.             $strSection $objSection->render();
  1706.             if(empty($strSection))
  1707.             {
  1708.                 continue;
  1709.             }
  1710.             
  1711.             $strContent .= $strSection;
  1712.             
  1713.             $class = array($section);
  1714.             
  1715.             $arrItems[] = array
  1716.             (
  1717.                 'section'     => $section,
  1718.                 'html'        => $strSection,
  1719.                 'class'        => implode(' '$class),
  1720.             );
  1721.         }
  1722.         
  1723.         if(!empty($arrItems))
  1724.         {
  1725.             $last count($arrItems) - 1;
  1726.             
  1727.             $arrItems[0]['class'] = trim($arrItems[0]['class']) . ' first';
  1728.             $arrItems[$last]['class'] = trim($arrItems[$last]['class']) . ' last';
  1729.         }
  1730.         
  1731.         $objTemplate->items $arrItems;
  1732.         $objTemplate->content $strContent;
  1733.         
  1734.         return $objTemplate->parse();
  1735.     }
  1736.     
  1737.     
  1738.     /**
  1739.      * Return the main section names as array
  1740.      * @return array
  1741.      */
  1742.     public static function getMainSections()
  1743.     {
  1744.         if(!is_array($GLOBALS['PCT_THEMEDESIGNER_CONFIG']) || empty($GLOBALS['PCT_THEMEDESIGNER_CONFIG']))
  1745.         {
  1746.             return array();
  1747.         }
  1748.         
  1749.         $arrChilds = array();
  1750.         
  1751.         // find child sections
  1752.         foreach($GLOBALS['PCT_THEMEDESIGNER_CONFIG'] as $config)
  1753.         {
  1754.             if( !empty($config['csection']) )
  1755.             {
  1756.                 $arrChilds array_merge($arrChildsarray_filter($config['csection']) );
  1757.             }
  1758.         }
  1759.         
  1760.         return array_diffarray_keys($GLOBALS['PCT_THEMEDESIGNER_CONFIG']),$arrChilds );
  1761.     }
  1762.     
  1763.     
  1764.     /**
  1765.      * Return all section names as array
  1766.      * @return array
  1767.      */
  1768.     public function getSections()
  1769.     {
  1770.         if(!is_array($GLOBALS['PCT_THEMEDESIGNER_CONFIG']) || empty($GLOBALS['PCT_THEMEDESIGNER_CONFIG']))
  1771.         {
  1772.             return array();
  1773.         }
  1774.         
  1775.         return array_keys($GLOBALS['PCT_THEMEDESIGNER_CONFIG']);
  1776.     }
  1777. // ! Session management
  1778.         
  1779.     
  1780.     /**
  1781.      * Return the current session as array
  1782.      * @return array
  1783.      */
  1784.     public function getSession()
  1785.     {
  1786.         $objSession \Contao\System::getContainer()->get('session');
  1787.         $strTheme $this->getTheme();
  1788.         $strSession $this->strSession.'_'.$strTheme;
  1789.         
  1790.         $arrReturn $objSession->get($strSession);
  1791.         
  1792.         if(!is_array($arrReturn))
  1793.         {
  1794.             $arrReturn = array();
  1795.         }
  1796.         if( !isset($_SESSION[$strSession]) )
  1797.         {
  1798.             $_SESSION[$strSession] = array();
  1799.         }
  1800.         
  1801.         // merge with default php session. This is nessessary for upload files
  1802.         if(is_array($_SESSION[$strSession]) && count($_SESSION[$strSession]) > 0)
  1803.         {
  1804.             $arrIntersect array_intersect_key($_SESSION[$strSession],$arrReturn);
  1805.             if(is_array($arrIntersect) && count($arrIntersect) > 0)
  1806.             {
  1807.                 foreach($arrIntersect as $k => $arr)
  1808.                 {
  1809.                     if(!is_array($arrReturn[$k]))
  1810.                     {
  1811.                         $arrReturn[$k] = array();
  1812.                     }
  1813.                     
  1814.                     if(is_array($arr))
  1815.                     {
  1816.                         $arrReturn[$k] = array_merge($arrReturn[$k],$arr);
  1817.                     }
  1818.                     else
  1819.                     {
  1820.                         $arrReturn[$k] = $arr;
  1821.                     }
  1822.                 }
  1823.             }
  1824.             
  1825.             $arrDiff array_diff_key($_SESSION[$strSession], $arrReturn);
  1826.             if(is_array($arrDiff) && count($arrDiff) > 0)
  1827.             {
  1828.                 foreach($arrDiff as $k => $arr)
  1829.                 {
  1830.                     $arrReturn[$k] = $arr;
  1831.                 }
  1832.             }
  1833.             
  1834.             // merge complete, remove temp. php helper session
  1835.             unset($_SESSION[$strSession]);
  1836.             
  1837.             // write session
  1838.             $objSession->set($strSession,$arrReturn);
  1839.             
  1840.             return $arrReturn;
  1841.         }
  1842.         if( !isset($arrReturn['MINIFIED']) )
  1843.         {
  1844.             $arrReturn['MINIFIED'] = false;
  1845.         }
  1846.         if( !isset($arrReturn['SWITCH']) )
  1847.         {
  1848.             $arrReturn['SWITCH'] = array();
  1849.         }
  1850.         if( !isset($arrReturn['IDENT']) )
  1851.         {
  1852.             $arrReturn['IDENT'] = 0;
  1853.         }
  1854.         
  1855.         if( !isset($arrReturn['IFRAME_URL']) )
  1856.         {
  1857.             $arrReturn['IFRAME_URL'] = '';
  1858.         }
  1859.         
  1860.         return $arrReturn
  1861.     }
  1862.     /**
  1863.      * Set a session array
  1864.      * @param array||null
  1865.      */
  1866.     public function setSession($arrSession=null)
  1867.     {
  1868.         $objSession \Contao\System::getContainer()->get('session');
  1869.         
  1870.         // remove the whole session
  1871.         if($arrSession === null)
  1872.         {
  1873.             $objSession->remove($this->strSession.'_'.$this->getTheme());
  1874.             unset($_SESSION[$this->strSession.'_'.$this->getTheme()]);
  1875.             return;
  1876.         }
  1877.         
  1878.         $objSession->set($this->strSession.'_'.$this->getTheme(),$arrSession);
  1879.     }
  1880.         
  1881.     
  1882.     /**
  1883.      * Reset and reload
  1884.      * @param boolean
  1885.      */
  1886.     public function reset()
  1887.     {
  1888.         global $objPage;
  1889.         
  1890.         // clear session
  1891.         $this->setSession();
  1892.         
  1893.         // reload page without parameter
  1894.         \Contao\Controller::redirect$objPage->getFrontendUrl() );
  1895.     }
  1896.     
  1897.     /**
  1898.      * Add an uploaded file
  1899.      * @param array        Data array of the upload
  1900.      */
  1901.     public function addUpload($strField,$arrFile)
  1902.     {
  1903.         $maxSize \Contao\System::getReadableSize$GLOBALS['PCT_THEMEDESIGNER']['maxFileSize'] );
  1904.         
  1905.         $arrFile['name'] = \Contao\StringUtil::sanitizeFileName($arrFile['name']);
  1906.         
  1907.         if (!is_uploaded_file($arrFile['tmp_name']))
  1908.         {
  1909.             if ($arrFile['error'] == || $arrFile['error'] == 2)
  1910.             {
  1911.                 \Contao\System::log('File "'.$arrFile['name'].'" exceeds the maximum file size of '.$maxSize__METHOD__TL_ERROR);
  1912.             }
  1913.         }
  1914.         
  1915.         $arrField $this->getFieldDefinition($strField);
  1916.         
  1917.         $strExtension pathinfo($arrFile['name'], PATHINFO_EXTENSION);
  1918.         
  1919.         // standardize file names
  1920.         $arrFile['name'] = str_replace(' ''_',$arrFile['name']);
  1921.         
  1922.         $arrUploadTypes StringUtil::trimsplit(','$GLOBALS['PCT_THEMEDESIGNER']['allowedUploadTypes']);
  1923.         // not allowed file type
  1924.         if(!in_array($strExtension$arrUploadTypes))
  1925.         {
  1926.             \Contao\System::log('File "'.$arrFile['name'].'" is a not allowed file type'__METHOD__TL_ERROR);
  1927.         }
  1928.         
  1929.         $arrImageSize = @getimagesize($arrFile['tmp_name']);
  1930.         
  1931.         // svg is not an image type in php
  1932.         if($strExtension == 'svg')
  1933.         {
  1934.             $arrImageSize['mime'] = 'image/svg+xml';
  1935.         }
  1936.         
  1937.         $objFiles \Contao\System::importStatic('Files');
  1938.         $strDest $GLOBALS['PCT_THEMEDESIGNER']['uploadFolder'] ?: 'system/tmp/themedesigner';
  1939.         
  1940.         // is image
  1941.         if($arrImageSize)
  1942.         {
  1943.             if(!is_dir(TL_ROOT.'/'.$strDest))
  1944.             {
  1945.                 $objFiles->mkdir($strDest);
  1946.             }
  1947.             
  1948.             $strFile $strDest.'/'.$arrFile['name'];
  1949.             
  1950.             $blnSuccess $objFiles->move_uploaded_file($arrFile['tmp_name'], $strFile);
  1951.             
  1952.             if(!$blnSuccess)
  1953.             {
  1954.                 \Contao\System::log('File "'.$strFile.'" could not be created or moved to destination: '.$strDest__METHOD__TL_ERROR);
  1955.                 return;
  1956.             }
  1957.             
  1958.             $image = array
  1959.             (
  1960.                 'mime'         => $arrImageSize['mime'],
  1961.                 'extension'    => $strExtension,
  1962.                 'file'        => $strFile,
  1963.                 'name'        => $arrFile['name'],
  1964.                 #'size'        => $arrImageSize,
  1965.             );
  1966.             
  1967.             $arrSession $this->getSession();
  1968.             $arrSession['VALUES'][$strField] = $image;
  1969.             $this->setSession($arrSession);
  1970.             
  1971.             $_SESSION[$this->strSession.'_'.$this->getTheme()]['VALUES'][$strField] = $image;
  1972.         }
  1973.     }
  1974. }