vendor/contao/core-bundle/src/Resources/contao/classes/DataContainer.php line 1629

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Contao\CoreBundle\Exception\AccessDeniedException;
  11. use Contao\CoreBundle\Exception\ResponseException;
  12. use Contao\CoreBundle\Picker\DcaPickerProviderInterface;
  13. use Contao\CoreBundle\Picker\PickerInterface;
  14. use Contao\CoreBundle\Security\ContaoCorePermissions;
  15. use Contao\Image\ResizeConfiguration;
  16. use Imagine\Gd\Imagine;
  17. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
  18. /**
  19.  * Provide methods to handle data container arrays.
  20.  *
  21.  * @property string|integer $id
  22.  * @property string         $table
  23.  * @property mixed          $value
  24.  * @property string         $field
  25.  * @property string         $inputName
  26.  * @property string         $palette
  27.  * @property object|null    $activeRecord
  28.  * @property array          $rootIds
  29.  */
  30. abstract class DataContainer extends Backend
  31. {
  32.     /**
  33.      * Records are not sorted
  34.      */
  35.     public const MODE_UNSORTED 0;
  36.     /**
  37.      * Records are sorted by a fixed field
  38.      */
  39.     public const MODE_SORTED 1;
  40.     /**
  41.      * Records are sorted by a switchable field
  42.      */
  43.     public const MODE_SORTABLE 2;
  44.     /**
  45.      * Records are sorted by the parent table
  46.      */
  47.     public const MODE_SORTED_PARENT 3;
  48.     /**
  49.      * Displays the child records of a parent record (see content elements)
  50.      */
  51.     public const MODE_PARENT 4;
  52.     /**
  53.      * Records are displayed as tree (see site structure)
  54.      */
  55.     public const MODE_TREE 5;
  56.     /**
  57.      * Displays the child records within a tree structure (see articles module)
  58.      */
  59.     public const MODE_TREE_EXTENDED 6;
  60.     /**
  61.      * Sort by initial letter ascending
  62.      */
  63.     public const SORT_INITIAL_LETTER_ASC 1;
  64.     /**
  65.      * Sort by initial letter descending
  66.      */
  67.     public const SORT_INITIAL_LETTER_DESC 2;
  68.     /**
  69.      * Sort by initial two letters ascending
  70.      */
  71.     public const SORT_INITIAL_LETTERS_ASC 3;
  72.     /**
  73.      * Sort by initial two letters descending
  74.      */
  75.     public const SORT_INITIAL_LETTERS_DESC 4;
  76.     /**
  77.      * Sort by day ascending
  78.      */
  79.     public const SORT_DAY_ASC 5;
  80.     /**
  81.      * Sort by day descending
  82.      */
  83.     public const SORT_DAY_DESC 6;
  84.     /**
  85.      * Sort by month ascending
  86.      */
  87.     public const SORT_MONTH_ASC 7;
  88.     /**
  89.      * Sort by month descending
  90.      */
  91.     public const SORT_MONTH_DESC 8;
  92.     /**
  93.      * Sort by year ascending
  94.      */
  95.     public const SORT_YEAR_ASC 9;
  96.     /**
  97.      * Sort by year descending
  98.      */
  99.     public const SORT_YEAR_DESC 10;
  100.     /**
  101.      * Sort ascending
  102.      */
  103.     public const SORT_ASC 11;
  104.     /**
  105.      * Sort descending
  106.      */
  107.     public const SORT_DESC 12;
  108.     /**
  109.      * Current ID
  110.      * @var integer|string
  111.      */
  112.     protected $intId;
  113.     /**
  114.      * Name of the current table
  115.      * @var string
  116.      */
  117.     protected $strTable;
  118.     /**
  119.      * Name of the current field
  120.      * @var string
  121.      */
  122.     protected $strField;
  123.     /**
  124.      * Name attribute of the current input field
  125.      * @var string
  126.      */
  127.     protected $strInputName;
  128.     /**
  129.      * Value of the current field
  130.      * @var mixed
  131.      */
  132.     protected $varValue;
  133.     /**
  134.      * Name of the current palette
  135.      * @var string
  136.      */
  137.     protected $strPalette;
  138.     /**
  139.      * IDs of all root records (permissions)
  140.      * @var array
  141.      */
  142.     protected $root = array();
  143.     /**
  144.      * IDs of children of root records (permissions)
  145.      * @var array
  146.      */
  147.     protected $rootChildren = array();
  148.     /**
  149.      * IDs of visible parents of the root records
  150.      * @var array
  151.      */
  152.     protected $visibleRootTrails = array();
  153.     /**
  154.      * If pasting at root level is allowed (permissions)
  155.      * @var bool
  156.      */
  157.     protected $rootPaste false;
  158.     /**
  159.      * WHERE clause of the database query
  160.      * @var array
  161.      */
  162.     protected $procedure = array();
  163.     /**
  164.      * Values for the WHERE clause of the database query
  165.      * @var array
  166.      */
  167.     protected $values = array();
  168.     /**
  169.      * Form attribute "onsubmit"
  170.      * @var array
  171.      */
  172.     protected $onsubmit = array();
  173.     /**
  174.      * Reload the page after the form has been submitted
  175.      * @var boolean
  176.      */
  177.     protected $noReload false;
  178.     /**
  179.      * Active record
  180.      * @var Model|FilesModel
  181.      */
  182.     protected $objActiveRecord;
  183.     /**
  184.      * True if one of the form fields is uploadable
  185.      * @var boolean
  186.      */
  187.     protected $blnUploadable false;
  188.     /**
  189.      * DCA Picker instance
  190.      * @var PickerInterface
  191.      */
  192.     protected $objPicker;
  193.     /**
  194.      * Callback to convert DCA value to picker value
  195.      * @var callable
  196.      */
  197.     protected $objPickerCallback;
  198.     /**
  199.      * The picker value
  200.      * @var array
  201.      */
  202.     protected $arrPickerValue = array();
  203.     /**
  204.      * The picker field type
  205.      * @var string
  206.      */
  207.     protected $strPickerFieldType;
  208.     /**
  209.      * True if a new version has to be created
  210.      * @var boolean
  211.      */
  212.     protected $blnCreateNewVersion false;
  213.     /**
  214.      * Set an object property
  215.      *
  216.      * @param string $strKey
  217.      * @param mixed  $varValue
  218.      */
  219.     public function __set($strKey$varValue)
  220.     {
  221.         switch ($strKey)
  222.         {
  223.             case 'activeRecord':
  224.                 $this->objActiveRecord $varValue;
  225.                 break;
  226.             case 'createNewVersion':
  227.                 $this->blnCreateNewVersion = (bool) $varValue;
  228.                 break;
  229.             case 'id':
  230.                 $this->intId $varValue;
  231.                 break;
  232.             default:
  233.                 $this->$strKey $varValue// backwards compatibility
  234.                 break;
  235.         }
  236.     }
  237.     /**
  238.      * Return an object property
  239.      *
  240.      * @param string $strKey
  241.      *
  242.      * @return mixed
  243.      */
  244.     public function __get($strKey)
  245.     {
  246.         switch ($strKey)
  247.         {
  248.             case 'id':
  249.                 return $this->intId;
  250.             case 'table':
  251.                 return $this->strTable;
  252.             case 'value':
  253.                 return $this->varValue;
  254.             case 'field':
  255.                 return $this->strField;
  256.             case 'inputName':
  257.                 return $this->strInputName;
  258.             case 'palette':
  259.                 return $this->strPalette;
  260.             case 'activeRecord':
  261.                 return $this->objActiveRecord;
  262.             case 'createNewVersion':
  263.                 return $this->blnCreateNewVersion;
  264.         }
  265.         return parent::__get($strKey);
  266.     }
  267.     /**
  268.      * Render a row of a box and return it as HTML string
  269.      *
  270.      * @param string|array|null $strPalette
  271.      *
  272.      * @return string
  273.      *
  274.      * @throws AccessDeniedException
  275.      * @throws \Exception
  276.      */
  277.     protected function row($strPalette=null)
  278.     {
  279.         $arrData $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField] ?? array();
  280.         // Check if the field is excluded
  281.         if ($arrData['exclude'] ?? null)
  282.         {
  283.             throw new AccessDeniedException('Field "' $this->strTable '.' $this->strField '" is excluded from being edited.');
  284.         }
  285.         $xlabel '';
  286.         // Toggle line wrap (textarea)
  287.         if (($arrData['inputType'] ?? null) == 'textarea' && !isset($arrData['eval']['rte']))
  288.         {
  289.             $xlabel .= ' ' Image::getHtml('wrap.svg'$GLOBALS['TL_LANG']['MSC']['wordWrap'], 'title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['wordWrap']) . '" class="toggleWrap" onclick="Backend.toggleWrap(\'ctrl_' $this->strInputName '\')"');
  290.         }
  291.         // Add the help wizard
  292.         if ($arrData['eval']['helpwizard'] ?? null)
  293.         {
  294.             $xlabel .= ' <a href="' StringUtil::specialcharsUrl(System::getContainer()->get('router')->generate('contao_backend_help', array('table' => $this->strTable'field' => $this->strField))) . '" title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['helpWizard']) . '" onclick="Backend.openModalIframe({\'title\':\'' StringUtil::specialchars(str_replace("'""\\'"$arrData['label'][0] ?? '')) . '\',\'url\':this.href});return false">' Image::getHtml('about.svg'$GLOBALS['TL_LANG']['MSC']['helpWizard']) . '</a>';
  295.         }
  296.         // Add a custom xlabel
  297.         if (\is_array($arrData['xlabel'] ?? null))
  298.         {
  299.             foreach ($arrData['xlabel'] as $callback)
  300.             {
  301.                 if (\is_array($callback))
  302.                 {
  303.                     $this->import($callback[0]);
  304.                     $xlabel .= $this->{$callback[0]}->{$callback[1]}($this);
  305.                 }
  306.                 elseif (\is_callable($callback))
  307.                 {
  308.                     $xlabel .= $callback($this);
  309.                 }
  310.             }
  311.         }
  312.         // Input field callback
  313.         if (\is_array($arrData['input_field_callback'] ?? null))
  314.         {
  315.             $this->import($arrData['input_field_callback'][0]);
  316.             return $this->{$arrData['input_field_callback'][0]}->{$arrData['input_field_callback'][1]}($this$xlabel);
  317.         }
  318.         if (\is_callable($arrData['input_field_callback'] ?? null))
  319.         {
  320.             return $arrData['input_field_callback']($this$xlabel);
  321.         }
  322.         $strClass $GLOBALS['BE_FFL'][($arrData['inputType'] ?? null)] ?? null;
  323.         // Return if the widget class does not exist
  324.         if (!class_exists($strClass))
  325.         {
  326.             return '';
  327.         }
  328.         $arrData['eval']['required'] = false;
  329.         if ($arrData['eval']['mandatory'] ?? null)
  330.         {
  331.             if (\is_array($this->varValue))
  332.             {
  333.                 if (empty($this->varValue))
  334.                 {
  335.                     $arrData['eval']['required'] = true;
  336.                 }
  337.             }
  338.             // Use strlen() here (see #3277)
  339.             elseif (!\strlen($this->varValue))
  340.             {
  341.                 $arrData['eval']['required'] = true;
  342.             }
  343.         }
  344.         // Convert insert tags in src attributes (see #5965)
  345.         if (isset($arrData['eval']['rte']) && strncmp($arrData['eval']['rte'], 'tiny'4) === 0)
  346.         {
  347.             $this->varValue StringUtil::insertTagToSrc($this->varValue);
  348.         }
  349.         // Use raw request if set globally but allow opting out setting useRawRequestData to false explicitly
  350.         $useRawGlobally = isset($GLOBALS['TL_DCA'][$this->strTable]['config']['useRawRequestData']) && $GLOBALS['TL_DCA'][$this->strTable]['config']['useRawRequestData'] === true;
  351.         $notRawForField = isset($arrData['eval']['useRawRequestData']) && $arrData['eval']['useRawRequestData'] === false;
  352.         if ($useRawGlobally && !$notRawForField)
  353.         {
  354.             $arrData['eval']['useRawRequestData'] = true;
  355.         }
  356.         /** @var Widget $objWidget */
  357.         $objWidget = new $strClass($strClass::getAttributesFromDca($arrData$this->strInputName$this->varValue$this->strField$this->strTable$this));
  358.         $objWidget->xlabel $xlabel;
  359.         $objWidget->currentRecord $this->intId;
  360.         // Validate the field
  361.         if (Input::post('FORM_SUBMIT') == $this->strTable)
  362.         {
  363.             $suffix $this->getFormFieldSuffix();
  364.             $key = (Input::get('act') == 'editAll') ? 'FORM_FIELDS_' $suffix 'FORM_FIELDS';
  365.             // Calculate the current palette
  366.             $postPaletteFields implode(','Input::post($key));
  367.             $postPaletteFields array_unique(StringUtil::trimsplit('[,;]'$postPaletteFields));
  368.             // Compile the palette if there is none
  369.             if ($strPalette === null)
  370.             {
  371.                 $newPaletteFields StringUtil::trimsplit('[,;]'$this->getPalette());
  372.             }
  373.             else
  374.             {
  375.                 // Use the given palette ($strPalette is an array in editAll mode)
  376.                 $newPaletteFields = \is_array($strPalette) ? $strPalette StringUtil::trimsplit('[,;]'$strPalette);
  377.                 // Recompile the palette if the current field is a selector field and the value has changed
  378.                 if (isset($GLOBALS['TL_DCA'][$this->strTable]['palettes']['__selector__']) && $this->varValue != Input::post($this->strInputName) && \in_array($this->strField$GLOBALS['TL_DCA'][$this->strTable]['palettes']['__selector__']))
  379.                 {
  380.                     $newPaletteFields StringUtil::trimsplit('[,;]'$this->getPalette());
  381.                 }
  382.             }
  383.             // Adjust the names in editAll mode
  384.             if (Input::get('act') == 'editAll')
  385.             {
  386.                 foreach ($newPaletteFields as $k=>$v)
  387.                 {
  388.                     $newPaletteFields[$k] = $v '_' $suffix;
  389.                 }
  390.                 if ($this->User->isAdmin)
  391.                 {
  392.                     $newPaletteFields['pid'] = 'pid_' $suffix;
  393.                     $newPaletteFields['sorting'] = 'sorting_' $suffix;
  394.                 }
  395.             }
  396.             $paletteFields array_intersect($postPaletteFields$newPaletteFields);
  397.             // Deprecated since Contao 4.2, to be removed in Contao 5.0
  398.             if (!isset($_POST[$this->strInputName]) && \in_array($this->strInputName$paletteFields))
  399.             {
  400.                 trigger_deprecation('contao/core-bundle''4.2''Using $_POST[\'FORM_FIELDS\'] has been deprecated and will no longer work in Contao 5.0. Make sure to always submit at least an empty string in your widget.');
  401.             }
  402.             // Validate and save the field
  403.             if ($objWidget->submitInput() && (\in_array($this->strInputName$paletteFields) || Input::get('act') == 'overrideAll'))
  404.             {
  405.                 $objWidget->validate();
  406.                 if ($objWidget->hasErrors())
  407.                 {
  408.                     // Skip mandatory fields on auto-submit (see #4077)
  409.                     if (!$objWidget->mandatory || $objWidget->value || Input::post('SUBMIT_TYPE') != 'auto')
  410.                     {
  411.                         $this->noReload true;
  412.                     }
  413.                 }
  414.                 // The return value of submitInput() might have changed, therefore check it again here (see #2383)
  415.                 elseif ($objWidget->submitInput())
  416.                 {
  417.                     $varValue $objWidget->value;
  418.                     // Sort array by key (fix for JavaScript wizards)
  419.                     if (\is_array($varValue))
  420.                     {
  421.                         ksort($varValue);
  422.                         $varValue serialize($varValue);
  423.                     }
  424.                     // Convert file paths in src attributes (see #5965)
  425.                     if ($varValue && isset($arrData['eval']['rte']) && strncmp($arrData['eval']['rte'], 'tiny'4) === 0)
  426.                     {
  427.                         $varValue StringUtil::srcToInsertTag($varValue);
  428.                     }
  429.                     // Save the current value
  430.                     try
  431.                     {
  432.                         $this->save($varValue);
  433.                     }
  434.                     catch (ResponseException $e)
  435.                     {
  436.                         throw $e;
  437.                     }
  438.                     catch (\Exception $e)
  439.                     {
  440.                         $this->noReload true;
  441.                         $objWidget->addError($e->getMessage());
  442.                     }
  443.                 }
  444.             }
  445.         }
  446.         $wizard '';
  447.         $strHelpClass '';
  448.         // Date picker
  449.         if ($arrData['eval']['datepicker'] ?? null)
  450.         {
  451.             $rgxp $arrData['eval']['rgxp'] ?? 'date';
  452.             $format Date::formatToJs(Config::get($rgxp 'Format'));
  453.             switch ($rgxp)
  454.             {
  455.                 case 'datim':
  456.                     $time ",\n        timePicker: true";
  457.                     break;
  458.                 case 'time':
  459.                     $time ",\n        pickOnly: \"time\"";
  460.                     break;
  461.                 default:
  462.                     $time '';
  463.                     break;
  464.             }
  465.             $strOnSelect '';
  466.             // Trigger the auto-submit function (see #8603)
  467.             if ($arrData['eval']['submitOnChange'] ?? null)
  468.             {
  469.                 $strOnSelect ",\n        onSelect: function() { Backend.autoSubmit(\"" $this->strTable "\"); }";
  470.             }
  471.             $wizard .= ' ' Image::getHtml('assets/datepicker/images/icon.svg''''title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['datepicker']) . '" id="toggle_' $objWidget->id '" style="cursor:pointer"') . '
  472.   <script>
  473.     window.addEvent("domready", function() {
  474.       new Picker.Date($("ctrl_' $objWidget->id '"), {
  475.         draggable: false,
  476.         toggle: $("toggle_' $objWidget->id '"),
  477.         format: "' $format '",
  478.         positionOffset: {x:-211,y:-209}' $time ',
  479.         pickerClass: "datepicker_bootstrap",
  480.         useFadeInOut: !Browser.ie' $strOnSelect ',
  481.         startDay: ' $GLOBALS['TL_LANG']['MSC']['weekOffset'] . ',
  482.         titleFormat: "' $GLOBALS['TL_LANG']['MSC']['titleFormat'] . '"
  483.       });
  484.     });
  485.   </script>';
  486.         }
  487.         // Color picker
  488.         if ($arrData['eval']['colorpicker'] ?? null)
  489.         {
  490.             // Support single fields as well (see #5240)
  491.             $strKey = ($arrData['eval']['multiple'] ?? null) ? $this->strField '_0' $this->strField;
  492.             $wizard .= ' ' Image::getHtml('pickcolor.svg'$GLOBALS['TL_LANG']['MSC']['colorpicker'], 'title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['colorpicker']) . '" id="moo_' $this->strField '" style="cursor:pointer"') . '
  493.   <script>
  494.     window.addEvent("domready", function() {
  495.       var cl = $("ctrl_' $strKey '").value.hexToRgb(true) || [255, 0, 0];
  496.       new MooRainbow("moo_' $this->strField '", {
  497.         id: "ctrl_' $strKey '",
  498.         startColor: cl,
  499.         imgPath: "assets/colorpicker/images/",
  500.         onComplete: function(color) {
  501.           $("ctrl_' $strKey '").value = color.hex.replace("#", "");
  502.         }
  503.       });
  504.     });
  505.   </script>';
  506.         }
  507.         $arrClasses StringUtil::trimsplit(' '$arrData['eval']['tl_class'] ?? '');
  508.         // DCA picker
  509.         if (isset($arrData['eval']['dcaPicker']) && (\is_array($arrData['eval']['dcaPicker']) || $arrData['eval']['dcaPicker'] === true))
  510.         {
  511.             $arrClasses[] = 'dcapicker';
  512.             $wizard .= Backend::getDcaPickerWizard($arrData['eval']['dcaPicker'], $this->strTable$this->strField$this->strInputName);
  513.         }
  514.         if (($arrData['inputType'] ?? null) == 'password')
  515.         {
  516.             $wizard .= Backend::getTogglePasswordWizard($this->strInputName);
  517.         }
  518.         // Add a custom wizard
  519.         if (\is_array($arrData['wizard'] ?? null))
  520.         {
  521.             foreach ($arrData['wizard'] as $callback)
  522.             {
  523.                 if (\is_array($callback))
  524.                 {
  525.                     $this->import($callback[0]);
  526.                     $wizard .= $this->{$callback[0]}->{$callback[1]}($this);
  527.                 }
  528.                 elseif (\is_callable($callback))
  529.                 {
  530.                     $wizard .= $callback($this);
  531.                 }
  532.             }
  533.         }
  534.         $hasWizardClass = \in_array('wizard'$arrClasses);
  535.         if ($wizard && !($arrData['eval']['disabled'] ?? false) && !($arrData['eval']['readonly'] ?? false))
  536.         {
  537.             $objWidget->wizard $wizard;
  538.             if (!$hasWizardClass)
  539.             {
  540.                 $arrClasses[] = 'wizard';
  541.             }
  542.         }
  543.         elseif ($hasWizardClass)
  544.         {
  545.             unset($arrClasses[array_search('wizard'$arrClasses)]);
  546.         }
  547.         // Set correct form enctype
  548.         if ($objWidget instanceof UploadableWidgetInterface)
  549.         {
  550.             $this->blnUploadable true;
  551.         }
  552.         $arrClasses[] = 'widget';
  553.         // Mark floated single checkboxes
  554.         if (($arrData['inputType'] ?? null) == 'checkbox' && !($arrData['eval']['multiple'] ?? null) && \in_array('w50'$arrClasses))
  555.         {
  556.             $arrClasses[] = 'cbx';
  557.         }
  558.         elseif (($arrData['inputType'] ?? null) == 'text' && ($arrData['eval']['multiple'] ?? null) && \in_array('wizard'$arrClasses))
  559.         {
  560.             $arrClasses[] = 'inline';
  561.         }
  562.         if (!empty($arrClasses))
  563.         {
  564.             $arrData['eval']['tl_class'] = implode(' 'array_unique($arrClasses));
  565.         }
  566.         $updateMode '';
  567.         // Replace the textarea with an RTE instance
  568.         if (!empty($arrData['eval']['rte']))
  569.         {
  570.             list($file$type) = explode('|'$arrData['eval']['rte'], 2) + array(nullnull);
  571.             $fileBrowserTypes = array();
  572.             $pickerBuilder System::getContainer()->get('contao.picker.builder');
  573.             foreach (array('file' => 'image''link' => 'file') as $context => $fileBrowserType)
  574.             {
  575.                 if ($pickerBuilder->supportsContext($context))
  576.                 {
  577.                     $fileBrowserTypes[] = $fileBrowserType;
  578.                 }
  579.             }
  580.             $objTemplate = new BackendTemplate('be_' $file);
  581.             $objTemplate->selector 'ctrl_' $this->strInputName;
  582.             $objTemplate->type $type;
  583.             $objTemplate->fileBrowserTypes $fileBrowserTypes;
  584.             $objTemplate->source $this->strTable '.' $this->intId;
  585.             // Deprecated since Contao 4.0, to be removed in Contao 5.0
  586.             $objTemplate->language Backend::getTinyMceLanguage();
  587.             $updateMode $objTemplate->parse();
  588.             unset($file$type$pickerBuilder$fileBrowserTypes$fileBrowserType);
  589.         }
  590.         // Handle multi-select fields in "override all" mode
  591.         elseif ((($arrData['inputType'] ?? null) == 'checkbox' || ($arrData['inputType'] ?? null) == 'checkboxWizard') && ($arrData['eval']['multiple'] ?? null) && Input::get('act') == 'overrideAll')
  592.         {
  593.             $updateMode '
  594. </div>
  595. <div class="widget">
  596.   <fieldset class="tl_radio_container">
  597.   <legend>' $GLOBALS['TL_LANG']['MSC']['updateMode'] . '</legend>
  598.     <input type="radio" name="' $this->strInputName '_update" id="opt_' $this->strInputName '_update_1" class="tl_radio" value="add" onfocus="Backend.getScrollOffset()"> <label for="opt_' $this->strInputName '_update_1">' $GLOBALS['TL_LANG']['MSC']['updateAdd'] . '</label><br>
  599.     <input type="radio" name="' $this->strInputName '_update" id="opt_' $this->strInputName '_update_2" class="tl_radio" value="remove" onfocus="Backend.getScrollOffset()"> <label for="opt_' $this->strInputName '_update_2">' $GLOBALS['TL_LANG']['MSC']['updateRemove'] . '</label><br>
  600.     <input type="radio" name="' $this->strInputName '_update" id="opt_' $this->strInputName '_update_0" class="tl_radio" value="replace" checked="checked" onfocus="Backend.getScrollOffset()"> <label for="opt_' $this->strInputName '_update_0">' $GLOBALS['TL_LANG']['MSC']['updateReplace'] . '</label>
  601.   </fieldset>';
  602.         }
  603.         $strPreview '';
  604.         // Show a preview image (see #4948)
  605.         if ($this->strTable == 'tl_files' && $this->strField == 'name' && $this->objActiveRecord !== null && $this->objActiveRecord->type == 'file')
  606.         {
  607.             $objFile = new File($this->objActiveRecord->path);
  608.             if ($objFile->isImage)
  609.             {
  610.                 $blnCanResize true;
  611.                 if ($objFile->isSvgImage)
  612.                 {
  613.                     // SVG images with undefined sizes cannot be resized
  614.                     if (!$objFile->viewWidth || !$objFile->viewHeight)
  615.                     {
  616.                         $blnCanResizefalse;
  617.                     }
  618.                 }
  619.                 elseif (System::getContainer()->get('contao.image.imagine') instanceof Imagine)
  620.                 {
  621.                     // Check the maximum width and height if the GDlib is used to resize images
  622.                     if ($objFile->height Config::get('gdMaxImgHeight') || $objFile->width Config::get('gdMaxImgWidth'))
  623.                     {
  624.                         $blnCanResize false;
  625.                     }
  626.                 }
  627.                 if ($blnCanResize)
  628.                 {
  629.                     $container System::getContainer();
  630.                     $projectDir $container->getParameter('kernel.project_dir');
  631.                     $image rawurldecode($container->get('contao.image.factory')->create($projectDir '/' $objFile->path, array(699524ResizeConfiguration::MODE_BOX))->getUrl($projectDir));
  632.                 }
  633.                 else
  634.                 {
  635.                     $image Image::getPath('placeholder.svg');
  636.                 }
  637.                 $objImage = new File($image);
  638.                 $ctrl 'ctrl_preview_' substr(md5($image), 08);
  639.                 $strPreview '
  640. <div id="' $ctrl '" class="tl_edit_preview">
  641.   <img src="' $objImage->dataUri '" width="' $objImage->width '" height="' $objImage->height '" alt="">
  642. </div>';
  643.                 // Add the script to mark the important part
  644.                 if (basename($image) !== 'placeholder.svg')
  645.                 {
  646.                     $strPreview .= '<script>Backend.editPreviewWizard($(\'' $ctrl '\'));</script>';
  647.                     if (Config::get('showHelp'))
  648.                     {
  649.                         $strPreview .= '<p class="tl_help tl_tip">' $GLOBALS['TL_LANG'][$this->strTable]['edit_preview_help'] . '</p>';
  650.                     }
  651.                     $strPreview '<div class="widget">' $strPreview '</div>';
  652.                 }
  653.             }
  654.         }
  655.         return $strPreview '
  656. <div' . (!empty($arrData['eval']['tl_class']) ? ' class="' trim($arrData['eval']['tl_class']) . '"' '') . '>' $objWidget->parse() . $updateMode . (!$objWidget->hasErrors() ? $this->help($strHelpClass) : '') . '
  657. </div>';
  658.     }
  659.     /**
  660.      * Return the field explanation as HTML string
  661.      *
  662.      * @param string $strClass
  663.      *
  664.      * @return string
  665.      */
  666.     public function help($strClass='')
  667.     {
  668.         $return $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['label'][1] ?? null;
  669.         if (!$return || ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['inputType'] ?? null) == 'password' || !Config::get('showHelp'))
  670.         {
  671.             return '';
  672.         }
  673.         return '
  674.   <p class="tl_help tl_tip' $strClass '">' $return '</p>';
  675.     }
  676.     /**
  677.      * Generate possible palette names from an array by taking the first value and either adding or not adding the following values
  678.      *
  679.      * @param array $names
  680.      *
  681.      * @return array
  682.      */
  683.     protected function combiner($names)
  684.     {
  685.         $return = array('');
  686.         $names array_values($names);
  687.         for ($i=0$c=\count($names); $i<$c$i++)
  688.         {
  689.             $buffer = array();
  690.             foreach ($return as $k=>$v)
  691.             {
  692.                 $buffer[] = ($k%== 0) ? $v $v $names[$i];
  693.                 $buffer[] = ($k%== 0) ? $v $names[$i] : $v;
  694.             }
  695.             $return $buffer;
  696.         }
  697.         return array_filter($return);
  698.     }
  699.     /**
  700.      * Return a query string that switches into edit mode
  701.      *
  702.      * @param integer $id
  703.      *
  704.      * @return string
  705.      */
  706.     protected function switchToEdit($id)
  707.     {
  708.         $arrKeys = array();
  709.         $arrUnset = array('act''key''id''table''mode''pid');
  710.         foreach (array_keys($_GET) as $strKey)
  711.         {
  712.             if (!\in_array($strKey$arrUnset))
  713.             {
  714.                 $arrKeys[$strKey] = $strKey '=' Input::get($strKey);
  715.             }
  716.         }
  717.         $strUrl TL_SCRIPT '?' implode('&'$arrKeys);
  718.         return $strUrl . (!empty($arrKeys) ? '&' '') . (Input::get('table') ? 'table=' Input::get('table') . '&amp;' '') . 'act=edit&amp;id=' rawurlencode($id);
  719.     }
  720.     /**
  721.      * Compile buttons from the table configuration array and return them as HTML
  722.      *
  723.      * @param array   $arrRow
  724.      * @param string  $strTable
  725.      * @param array   $arrRootIds
  726.      * @param boolean $blnCircularReference
  727.      * @param array   $arrChildRecordIds
  728.      * @param string  $strPrevious
  729.      * @param string  $strNext
  730.      *
  731.      * @return string
  732.      */
  733.     protected function generateButtons($arrRow$strTable$arrRootIds=array(), $blnCircularReference=false$arrChildRecordIds=null$strPrevious=null$strNext=null)
  734.     {
  735.         if (!\is_array($GLOBALS['TL_DCA'][$strTable]['list']['operations'] ?? null))
  736.         {
  737.             return '';
  738.         }
  739.         $return '';
  740.         foreach ($GLOBALS['TL_DCA'][$strTable]['list']['operations'] as $k=>$v)
  741.         {
  742.             $v = \is_array($v) ? $v : array($v);
  743.             $id StringUtil::specialchars(rawurldecode($arrRow['id']));
  744.             $label $title $k;
  745.             if (isset($v['label']))
  746.             {
  747.                 if (\is_array($v['label']))
  748.                 {
  749.                     $label $v['label'][0] ?? null;
  750.                     $title sprintf($v['label'][1] ?? ''$id);
  751.                 }
  752.                 else
  753.                 {
  754.                     $label $title sprintf($v['label'], $id);
  755.                 }
  756.             }
  757.             $attributes = !empty($v['attributes']) ? ' ' ltrim(sprintf($v['attributes'], $id$id)) : '';
  758.             // Add the key as CSS class
  759.             if (strpos($attributes'class="') !== false)
  760.             {
  761.                 $attributes str_replace('class="''class="' $k ' '$attributes);
  762.             }
  763.             else
  764.             {
  765.                 $attributes ' class="' $k '"' $attributes;
  766.             }
  767.             // Call a custom function instead of using the default button
  768.             if (\is_array($v['button_callback'] ?? null))
  769.             {
  770.                 $this->import($v['button_callback'][0]);
  771.                 $return .= $this->{$v['button_callback'][0]}->{$v['button_callback'][1]}($arrRow$v['href'] ?? null$label$title$v['icon'] ?? null$attributes$strTable$arrRootIds$arrChildRecordIds$blnCircularReference$strPrevious$strNext$this);
  772.                 continue;
  773.             }
  774.             if (\is_callable($v['button_callback'] ?? null))
  775.             {
  776.                 $return .= $v['button_callback']($arrRow$v['href'] ?? null$label$title$v['icon'] ?? null$attributes$strTable$arrRootIds$arrChildRecordIds$blnCircularReference$strPrevious$strNext$this);
  777.                 continue;
  778.             }
  779.             // Generate all buttons except "move up" and "move down" buttons
  780.             if ($k != 'move' && $v != 'move')
  781.             {
  782.                 if ($k == 'show')
  783.                 {
  784.                     if (!empty($v['route']))
  785.                     {
  786.                         $href System::getContainer()->get('router')->generate($v['route'], array('id' => $arrRow['id'], 'popup' => '1'));
  787.                     }
  788.                     else
  789.                     {
  790.                         $href $this->addToUrl($v['href'] . '&amp;id=' $arrRow['id'] . '&amp;popup=1');
  791.                     }
  792.                     $return .= '<a href="' $href '" title="' StringUtil::specialchars($title) . '" onclick="Backend.openModalIframe({\'title\':\'' StringUtil::specialchars(str_replace("'""\\'"$label)) . '\',\'url\':this.href});return false"' $attributes '>' Image::getHtml($v['icon'], $label) . '</a> ';
  793.                 }
  794.                 else
  795.                 {
  796.                     if (!empty($v['route']))
  797.                     {
  798.                         $href System::getContainer()->get('router')->generate($v['route'], array('id' => $arrRow['id']));
  799.                     }
  800.                     else
  801.                     {
  802.                         $href $this->addToUrl($v['href'] . '&amp;id=' $arrRow['id'] . (Input::get('nb') ? '&amp;nc=1' ''));
  803.                     }
  804.                     parse_str(StringUtil::decodeEntities($v['href'] ?? ''), $params);
  805.                     if (($params['act'] ?? null) == 'toggle' && isset($params['field']))
  806.                     {
  807.                         // Hide the toggle icon if the user does not have access to the field
  808.                         if (($GLOBALS['TL_DCA'][$strTable]['fields'][$params['field']]['toggle'] ?? false) !== true || !System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELD_OF_TABLE$strTable '::' $params['field']))
  809.                         {
  810.                             continue;
  811.                         }
  812.                         $icon $v['icon'];
  813.                         $_icon pathinfo($v['icon'], PATHINFO_FILENAME) . '_.' pathinfo($v['icon'], PATHINFO_EXTENSION);
  814.                         if (false !== strpos($v['icon'], '/'))
  815.                         {
  816.                             $_icon = \dirname($v['icon']) . '/' $_icon;
  817.                         }
  818.                         if ($icon == 'visible.svg')
  819.                         {
  820.                             $_icon 'invisible.svg';
  821.                         }
  822.                         $state $arrRow[$params['field']] ? 0;
  823.                         if ($v['reverse'] ?? false)
  824.                         {
  825.                             $state $arrRow[$params['field']] ? 1;
  826.                         }
  827.                         $return .= '<a href="' $href '" title="' StringUtil::specialchars($title) . '" onclick="Backend.getScrollOffset();return AjaxRequest.toggleField(this,' . ($icon == 'visible.svg' 'true' 'false') . ')">' Image::getHtml($state $icon $_icon$label'data-icon="' Image::getPath($icon) . '" data-icon-disabled="' Image::getPath($_icon) . '" data-state="' $state '"') . '</a> ';
  828.                     }
  829.                     else
  830.                     {
  831.                         $return .= '<a href="' $href '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($v['icon'], $label) . '</a> ';
  832.                     }
  833.                 }
  834.                 continue;
  835.             }
  836.             trigger_deprecation('contao/core-bundle''4.13''The DCA "move" operation is deprecated and will be removed in Contao 5.');
  837.             $arrDirections = array('up''down');
  838.             $arrRootIds = \is_array($arrRootIds) ? $arrRootIds : array($arrRootIds);
  839.             foreach ($arrDirections as $dir)
  840.             {
  841.                 $label = !empty($GLOBALS['TL_LANG'][$strTable][$dir][0]) ? $GLOBALS['TL_LANG'][$strTable][$dir][0] : $dir;
  842.                 $title = !empty($GLOBALS['TL_LANG'][$strTable][$dir][1]) ? $GLOBALS['TL_LANG'][$strTable][$dir][1] : $dir;
  843.                 $label Image::getHtml($dir '.svg'$label);
  844.                 $href = !empty($v['href']) ? $v['href'] : '&amp;act=move';
  845.                 if ($dir == 'up')
  846.                 {
  847.                     $return .= ((is_numeric($strPrevious) && (empty($GLOBALS['TL_DCA'][$strTable]['list']['sorting']['root']) || !\in_array($arrRow['id'], $arrRootIds))) ? '<a href="' $this->addToUrl($href '&amp;id=' $arrRow['id']) . '&amp;sid=' . (int) $strPrevious '" title="' StringUtil::specialchars($title) . '"' $attributes '>' $label '</a> ' Image::getHtml('up_.svg')) . ' ';
  848.                 }
  849.                 else
  850.                 {
  851.                     $return .= ((is_numeric($strNext) && (empty($GLOBALS['TL_DCA'][$strTable]['list']['sorting']['root']) || !\in_array($arrRow['id'], $arrRootIds))) ? '<a href="' $this->addToUrl($href '&amp;id=' $arrRow['id']) . '&amp;sid=' . (int) $strNext '" title="' StringUtil::specialchars($title) . '"' $attributes '>' $label '</a> ' Image::getHtml('down_.svg')) . ' ';
  852.                 }
  853.             }
  854.         }
  855.         return trim($return);
  856.     }
  857.     /**
  858.      * Compile global buttons from the table configuration array and return them as HTML
  859.      *
  860.      * @return string
  861.      */
  862.     protected function generateGlobalButtons()
  863.     {
  864.         if (!\is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['global_operations'] ?? null))
  865.         {
  866.             return '';
  867.         }
  868.         $return '';
  869.         foreach ($GLOBALS['TL_DCA'][$this->strTable]['list']['global_operations'] as $k=>$v)
  870.         {
  871.             if (!($v['showOnSelect'] ?? null) && Input::get('act') == 'select')
  872.             {
  873.                 continue;
  874.             }
  875.             $v = \is_array($v) ? $v : array($v);
  876.             $title $label $k;
  877.             if (isset($v['label']))
  878.             {
  879.                 $label = \is_array($v['label']) ? $v['label'][0] : $v['label'];
  880.                 $title = \is_array($v['label']) ? ($v['label'][1] ?? null) : $v['label'];
  881.             }
  882.             $attributes = !empty($v['attributes']) ? ' ' ltrim($v['attributes']) : '';
  883.             // Custom icon (see #5541)
  884.             if ($v['icon'] ?? null)
  885.             {
  886.                 $v['class'] = trim(($v['class'] ?? '') . ' header_icon');
  887.                 // Add the theme path if only the file name is given
  888.                 if (strpos($v['icon'], '/') === false)
  889.                 {
  890.                     $v['icon'] = Image::getPath($v['icon']);
  891.                 }
  892.                 $attributes sprintf(' style="background-image:url(\'%s\')"'Controller::addAssetsUrlTo($v['icon'])) . $attributes;
  893.             }
  894.             if (!$label)
  895.             {
  896.                 $label $k;
  897.             }
  898.             if (!$title)
  899.             {
  900.                 $title $label;
  901.             }
  902.             // Call a custom function instead of using the default button
  903.             if (\is_array($v['button_callback'] ?? null))
  904.             {
  905.                 $this->import($v['button_callback'][0]);
  906.                 $return .= $this->{$v['button_callback'][0]}->{$v['button_callback'][1]}($v['href'], $label$title$v['class'], $attributes$this->strTable$this->root);
  907.                 continue;
  908.             }
  909.             if (\is_callable($v['button_callback'] ?? null))
  910.             {
  911.                 $return .= $v['button_callback']($v['href'] ?? null$label$title$v['class'] ?? null$attributes$this->strTable$this->root);
  912.                 continue;
  913.             }
  914.             if (!empty($v['route']))
  915.             {
  916.                 $href System::getContainer()->get('router')->generate($v['route']);
  917.             }
  918.             else
  919.             {
  920.                 $href $this->addToUrl($v['href']);
  921.             }
  922.             $return .= '<a href="' $href '" class="' $v['class'] . '" title="' StringUtil::specialchars($title) . '"' $attributes '>' $label '</a> ';
  923.         }
  924.         return $return;
  925.     }
  926.     /**
  927.      * Compile header buttons from the table configuration array and return them as HTML
  928.      *
  929.      * @param array  $arrRow
  930.      * @param string $strPtable
  931.      *
  932.      * @return string
  933.      */
  934.     protected function generateHeaderButtons($arrRow$strPtable)
  935.     {
  936.         if (!\is_array($GLOBALS['TL_DCA'][$strPtable]['list']['operations'] ?? null))
  937.         {
  938.             return '';
  939.         }
  940.         $return '';
  941.         foreach ($GLOBALS['TL_DCA'][$strPtable]['list']['operations'] as $k=> $v)
  942.         {
  943.             if (empty($v['showInHeader']) || (Input::get('act') == 'select' && !($v['showOnSelect'] ?? null)))
  944.             {
  945.                 continue;
  946.             }
  947.             $v = \is_array($v) ? $v : array($v);
  948.             $id StringUtil::specialchars(rawurldecode($arrRow['id']));
  949.             $label $title $k;
  950.             if (isset($v['label']))
  951.             {
  952.                 if (\is_array($v['label']))
  953.                 {
  954.                     $label $v['label'][0];
  955.                     $title sprintf($v['label'][1], $id);
  956.                 }
  957.                 else
  958.                 {
  959.                     $label $title sprintf($v['label'], $id);
  960.                 }
  961.             }
  962.             $attributes = !empty($v['attributes']) ? ' ' ltrim(sprintf($v['attributes'], $id$id)) : '';
  963.             // Add the key as CSS class
  964.             if (strpos($attributes'class="') !== false)
  965.             {
  966.                 $attributes str_replace('class="''class="' $k ' '$attributes);
  967.             }
  968.             else
  969.             {
  970.                 $attributes ' class="' $k '"' $attributes;
  971.             }
  972.             // Add the parent table to the href
  973.             if (isset($v['href']))
  974.             {
  975.                 $v['href'] .= '&amp;table=' $strPtable;
  976.             }
  977.             else
  978.             {
  979.                 $v['href'] = 'table=' $strPtable;
  980.             }
  981.             // Call a custom function instead of using the default button
  982.             if (\is_array($v['button_callback'] ?? null))
  983.             {
  984.                 $this->import($v['button_callback'][0]);
  985.                 $return .= $this->{$v['button_callback'][0]}->{$v['button_callback'][1]}($arrRow$v['href'], $label$title$v['icon'], $attributes$strPtable, array(), nullfalsenullnull$this);
  986.                 continue;
  987.             }
  988.             if (\is_callable($v['button_callback'] ?? null))
  989.             {
  990.                 $return .= $v['button_callback']($arrRow$v['href'], $label$title$v['icon'], $attributes$strPtable, array(), nullfalsenullnull$this);
  991.                 continue;
  992.             }
  993.             if ($k == 'show')
  994.             {
  995.                 if (!empty($v['route']))
  996.                 {
  997.                     $href System::getContainer()->get('router')->generate($v['route'], array('id' => $arrRow['id'], 'popup' => '1'));
  998.                 }
  999.                 else
  1000.                 {
  1001.                     $href $this->addToUrl($v['href'] . '&amp;id=' $arrRow['id'] . '&amp;popup=1');
  1002.                 }
  1003.                 $return .= '<a href="' $href '" title="' StringUtil::specialchars($title) . '" onclick="Backend.openModalIframe({\'title\':\'' StringUtil::specialchars(str_replace("'""\\'"sprintf(\is_array($GLOBALS['TL_LANG'][$strPtable]['show'] ?? null) ? $GLOBALS['TL_LANG'][$strPtable]['show'][1] : ($GLOBALS['TL_LANG'][$strPtable]['show'] ?? ''), $arrRow['id']))) . '\',\'url\':this.href});return false"' $attributes '>' Image::getHtml($v['icon'], $label) . '</a> ';
  1004.             }
  1005.             else
  1006.             {
  1007.                 if (!empty($v['route']))
  1008.                 {
  1009.                     $href System::getContainer()->get('router')->generate($v['route'], array('id' => $arrRow['id']));
  1010.                 }
  1011.                 else
  1012.                 {
  1013.                     $href $this->addToUrl($v['href'] . '&amp;id=' $arrRow['id'] . (Input::get('nb') ? '&amp;nc=1' ''));
  1014.                 }
  1015.                 parse_str(StringUtil::decodeEntities($v['href']), $params);
  1016.                 if (($params['act'] ?? null) == 'toggle' && isset($params['field']))
  1017.                 {
  1018.                     // Hide the toggle icon if the user does not have access to the field
  1019.                     if (($GLOBALS['TL_DCA'][$strPtable]['fields'][$params['field']]['toggle'] ?? false) !== true || !System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELD_OF_TABLE$strPtable '::' $params['field']))
  1020.                     {
  1021.                         continue;
  1022.                     }
  1023.                     $icon $v['icon'];
  1024.                     $_icon pathinfo($v['icon'], PATHINFO_FILENAME) . '_.' pathinfo($v['icon'], PATHINFO_EXTENSION);
  1025.                     if (false !== strpos($v['icon'], '/'))
  1026.                     {
  1027.                         $_icon = \dirname($v['icon']) . '/' $_icon;
  1028.                     }
  1029.                     if ($icon == 'visible.svg')
  1030.                     {
  1031.                         $_icon 'invisible.svg';
  1032.                     }
  1033.                     $state $arrRow[$params['field']] ? 0;
  1034.                     if ($v['reverse'] ?? false)
  1035.                     {
  1036.                         $state $arrRow[$params['field']] ? 1;
  1037.                     }
  1038.                     $return .= '<a href="' $href '" title="' StringUtil::specialchars($title) . '" onclick="Backend.getScrollOffset();return AjaxRequest.toggleField(this)">' Image::getHtml($state $icon $_icon$label'data-icon="' Image::getPath($icon) . '" data-icon-disabled="' Image::getPath($_icon) . '" data-state="' $state '"') . '</a> ';
  1039.                 }
  1040.                 else
  1041.                 {
  1042.                     $return .= '<a href="' $href '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($v['icon'], $label) . '</a> ';
  1043.                 }
  1044.             }
  1045.         }
  1046.         return $return;
  1047.     }
  1048.     /**
  1049.      * Initialize the picker
  1050.      *
  1051.      * @param PickerInterface $picker
  1052.      *
  1053.      * @return array|null
  1054.      */
  1055.     public function initPicker(PickerInterface $picker)
  1056.     {
  1057.         $provider $picker->getCurrentProvider();
  1058.         if (!$provider instanceof DcaPickerProviderInterface || $provider->getDcaTable($picker->getConfig()) != $this->strTable)
  1059.         {
  1060.             return null;
  1061.         }
  1062.         $attributes $provider->getDcaAttributes($picker->getConfig());
  1063.         $this->objPicker $picker;
  1064.         $this->strPickerFieldType $attributes['fieldType'];
  1065.         $this->objPickerCallback = static function ($value) use ($picker$provider)
  1066.         {
  1067.             return $provider->convertDcaValue($picker->getConfig(), $value);
  1068.         };
  1069.         if (isset($attributes['value']))
  1070.         {
  1071.             $this->arrPickerValue = (array) $attributes['value'];
  1072.         }
  1073.         return $attributes;
  1074.     }
  1075.     /**
  1076.      * Return the picker input field markup
  1077.      *
  1078.      * @param string $value
  1079.      * @param string $attributes
  1080.      *
  1081.      * @return string
  1082.      */
  1083.     protected function getPickerInputField($value$attributes='')
  1084.     {
  1085.         $id is_numeric($value) ? $value md5($value);
  1086.         switch ($this->strPickerFieldType)
  1087.         {
  1088.             case 'checkbox':
  1089.                 return ' <input type="checkbox" name="picker[]" id="picker_' $id '" class="tl_tree_checkbox" value="' StringUtil::specialchars(($this->objPickerCallback)($value)) . '" onfocus="Backend.getScrollOffset()"' Widget::optionChecked($value$this->arrPickerValue) . $attributes '>';
  1090.             case 'radio':
  1091.                 return ' <input type="radio" name="picker" id="picker_' $id '" class="tl_tree_radio" value="' StringUtil::specialchars(($this->objPickerCallback)($value)) . '" onfocus="Backend.getScrollOffset()"' Widget::optionChecked($value$this->arrPickerValue) . $attributes '>';
  1092.         }
  1093.         return '';
  1094.     }
  1095.     /**
  1096.      * Return the data-picker-value attribute with the currently selected picker values (see #1816)
  1097.      *
  1098.      * @return string
  1099.      */
  1100.     protected function getPickerValueAttribute()
  1101.     {
  1102.         // Only load the previously selected values for the checkbox field type (see #2346)
  1103.         if ($this->strPickerFieldType != 'checkbox')
  1104.         {
  1105.             return '';
  1106.         }
  1107.         $values array_map($this->objPickerCallback$this->arrPickerValue);
  1108.         $values array_map('strval'$values);
  1109.         $values json_encode($values);
  1110.         $values htmlspecialchars($values);
  1111.         return ' data-picker-value="' $values '"';
  1112.     }
  1113.     /**
  1114.      * Build the sort panel and return it as string
  1115.      *
  1116.      * @return string
  1117.      */
  1118.     protected function panel()
  1119.     {
  1120.         if (!($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout'] ?? null))
  1121.         {
  1122.             return '';
  1123.         }
  1124.         // Reset all filters
  1125.         if (isset($_POST['filter_reset']) && Input::post('FORM_SUBMIT') == 'tl_filters')
  1126.         {
  1127.             /** @var AttributeBagInterface $objSessionBag */
  1128.             $objSessionBag System::getContainer()->get('session')->getBag('contao_backend');
  1129.             $data $objSessionBag->all();
  1130.             unset(
  1131.                 $data['filter'][$this->strTable],
  1132.                 $data['filter'][$this->strTable '_' CURRENT_ID],
  1133.                 $data['sorting'][$this->strTable],
  1134.                 $data['search'][$this->strTable]
  1135.             );
  1136.             $objSessionBag->replace($data);
  1137.             $this->reload();
  1138.         }
  1139.         $intFilterPanel 0;
  1140.         $arrPanels = array();
  1141.         $arrPanes StringUtil::trimsplit(';'$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout'] ?? '');
  1142.         foreach ($arrPanes as $strPanel)
  1143.         {
  1144.             $panels '';
  1145.             $arrSubPanels StringUtil::trimsplit(','$strPanel);
  1146.             foreach ($arrSubPanels as $strSubPanel)
  1147.             {
  1148.                 $panel '';
  1149.                 switch ($strSubPanel)
  1150.                 {
  1151.                     case 'limit':
  1152.                         // The limit menu depends on other panels that may set a filter query, e.g. search and filter.
  1153.                         // In order to correctly calculate the total row count, the limit menu must be compiled last.
  1154.                         // We insert a placeholder here and compile the limit menu after all other panels.
  1155.                         $panel '###limit_menu###';
  1156.                         break;
  1157.                     case 'search':
  1158.                         $panel $this->searchMenu();
  1159.                         break;
  1160.                     case 'sort':
  1161.                         $panel $this->sortMenu();
  1162.                         break;
  1163.                     case 'filter':
  1164.                         // Multiple filter subpanels can be defined to split the fields across panels
  1165.                         $panel $this->filterMenu(++$intFilterPanel);
  1166.                         break;
  1167.                     default:
  1168.                         // Call the panel_callback
  1169.                         $arrCallback $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panel_callback'][$strSubPanel] ?? null;
  1170.                         if (\is_array($arrCallback))
  1171.                         {
  1172.                             $this->import($arrCallback[0]);
  1173.                             $panel $this->{$arrCallback[0]}->{$arrCallback[1]}($this);
  1174.                         }
  1175.                         elseif (\is_callable($arrCallback))
  1176.                         {
  1177.                             $panel $arrCallback($this);
  1178.                         }
  1179.                 }
  1180.                 // Add the panel if it is not empty
  1181.                 if ($panel)
  1182.                 {
  1183.                     $panels $panel $panels;
  1184.                 }
  1185.             }
  1186.             // Add the group if it is not empty
  1187.             if ($panels)
  1188.             {
  1189.                 $arrPanels[] = $panels;
  1190.             }
  1191.         }
  1192.         if (empty($arrPanels))
  1193.         {
  1194.             return '';
  1195.         }
  1196.         // Compile limit menu if placeholder is present
  1197.         foreach ($arrPanels as $key => $strPanel)
  1198.         {
  1199.             if (strpos($strPanel'###limit_menu###') === false)
  1200.             {
  1201.                 continue;
  1202.             }
  1203.             $arrPanels[$key] = str_replace('###limit_menu###'$this->limitMenu(), $strPanel);
  1204.         }
  1205.         if (Input::post('FORM_SUBMIT') == 'tl_filters')
  1206.         {
  1207.             $this->reload();
  1208.         }
  1209.         $return '';
  1210.         $intTotal = \count($arrPanels);
  1211.         $intLast $intTotal 1;
  1212.         for ($i=0$i<$intTotal$i++)
  1213.         {
  1214.             $submit '';
  1215.             if ($i == $intLast)
  1216.             {
  1217.                 $submit '
  1218. <div class="tl_submit_panel tl_subpanel">
  1219.   <button name="filter" id="filter" class="tl_img_submit filter_apply" title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['applyTitle']) . '">' $GLOBALS['TL_LANG']['MSC']['apply'] . '</button>
  1220.   <button name="filter_reset" id="filter_reset" value="1" class="tl_img_submit filter_reset" title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['resetTitle']) . '">' $GLOBALS['TL_LANG']['MSC']['reset'] . '</button>
  1221. </div>';
  1222.             }
  1223.             $return .= '
  1224. <div class="tl_panel cf">
  1225.   ' $submit $arrPanels[$i] . '
  1226. </div>';
  1227.         }
  1228.         $return '
  1229. <form class="tl_form" method="post" aria-label="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['searchAndFilter']) . '">
  1230. <div class="tl_formbody">
  1231.   <input type="hidden" name="FORM_SUBMIT" value="tl_filters">
  1232.   <input type="hidden" name="REQUEST_TOKEN" value="' REQUEST_TOKEN '">
  1233.   ' $return '
  1234. </div>
  1235. </form>';
  1236.         return $return;
  1237.     }
  1238.     /**
  1239.      * Invalidate the cache tags associated with a given DC
  1240.      *
  1241.      * Call this whenever an entry is modified (added, updated, deleted).
  1242.      */
  1243.     public function invalidateCacheTags()
  1244.     {
  1245.         if (!System::getContainer()->has('fos_http_cache.cache_manager'))
  1246.         {
  1247.             return;
  1248.         }
  1249.         $tags = array('contao.db.' $this->table '.' $this->id);
  1250.         $this->addPtableTags($this->table$this->id$tags);
  1251.         $this->addCtableTags($this->table$this->id$tags);
  1252.         // Trigger the oninvalidate_cache_tags_callback
  1253.         if (\is_array($GLOBALS['TL_DCA'][$this->table]['config']['oninvalidate_cache_tags_callback'] ?? null))
  1254.         {
  1255.             foreach ($GLOBALS['TL_DCA'][$this->table]['config']['oninvalidate_cache_tags_callback'] as $callback)
  1256.             {
  1257.                 if (\is_array($callback))
  1258.                 {
  1259.                     $this->import($callback[0]);
  1260.                     $tags $this->{$callback[0]}->{$callback[1]}($this$tags);
  1261.                 }
  1262.                 elseif (\is_callable($callback))
  1263.                 {
  1264.                     $tags $callback($this$tags);
  1265.                 }
  1266.             }
  1267.         }
  1268.         // Make sure tags are unique and empty ones are removed
  1269.         $tags array_filter(array_unique($tags));
  1270.         System::getContainer()->get('fos_http_cache.cache_manager')->invalidateTags($tags);
  1271.     }
  1272.     public function addPtableTags($strTable$intId, &$tags)
  1273.     {
  1274.         if (empty($GLOBALS['TL_DCA'][$strTable]['config']['ptable']))
  1275.         {
  1276.             $tags[] = 'contao.db.' $strTable;
  1277.             return;
  1278.         }
  1279.         $ptable $GLOBALS['TL_DCA'][$strTable]['config']['ptable'];
  1280.         Controller::loadDataContainer($ptable);
  1281.         $objPid $this->Database->prepare('SELECT pid FROM ' Database::quoteIdentifier($strTable) . ' WHERE id=?')
  1282.                                  ->execute($intId);
  1283.         if (!$objPid->numRows)
  1284.         {
  1285.             return;
  1286.         }
  1287.         $tags[] = 'contao.db.' $ptable '.' $objPid->pid;
  1288.         $this->addPtableTags($ptable$objPid->pid$tags);
  1289.     }
  1290.     public function addCtableTags($strTable$intId, &$tags)
  1291.     {
  1292.         if (empty($GLOBALS['TL_DCA'][$strTable]['config']['ctable']))
  1293.         {
  1294.             return;
  1295.         }
  1296.         foreach ($GLOBALS['TL_DCA'][$strTable]['config']['ctable'] as $ctable)
  1297.         {
  1298.             Controller::loadDataContainer($ctable);
  1299.             if ($GLOBALS['TL_DCA'][$ctable]['config']['dynamicPtable'] ?? null)
  1300.             {
  1301.                 $objIds $this->Database->prepare('SELECT id FROM ' Database::quoteIdentifier($ctable) . ' WHERE pid=? AND ptable=?')
  1302.                                          ->execute($intId$strTable);
  1303.             }
  1304.             else
  1305.             {
  1306.                 $objIds $this->Database->prepare('SELECT id FROM ' Database::quoteIdentifier($ctable) . ' WHERE pid=?')
  1307.                                          ->execute($intId);
  1308.             }
  1309.             if (!$objIds->numRows)
  1310.             {
  1311.                 continue;
  1312.             }
  1313.             while ($objIds->next())
  1314.             {
  1315.                 $tags[] = 'contao.db.' $ctable '.' $objIds->id;
  1316.                 $this->addCtableTags($ctable$objIds->id$tags);
  1317.             }
  1318.         }
  1319.     }
  1320.     /**
  1321.      * Return the form field suffix
  1322.      *
  1323.      * @return integer|string
  1324.      */
  1325.     protected function getFormFieldSuffix()
  1326.     {
  1327.         return $this->intId;
  1328.     }
  1329.     /**
  1330.      * Return the name of the current palette
  1331.      *
  1332.      * @return string
  1333.      */
  1334.     abstract public function getPalette();
  1335.     /**
  1336.      * Save the current value
  1337.      *
  1338.      * @param mixed $varValue
  1339.      *
  1340.      * @throws \Exception
  1341.      */
  1342.     abstract protected function save($varValue);
  1343.     /**
  1344.      * Return the class name of the DataContainer driver for the given table.
  1345.      *
  1346.      * @param string $table
  1347.      *
  1348.      * @return string
  1349.      *
  1350.      * @todo Change the return type to ?string in Contao 5.0
  1351.      */
  1352.     public static function getDriverForTable(string $table): string
  1353.     {
  1354.         if (!isset($GLOBALS['TL_DCA'][$table]['config']['dataContainer']))
  1355.         {
  1356.             return '';
  1357.         }
  1358.         $dataContainer $GLOBALS['TL_DCA'][$table]['config']['dataContainer'];
  1359.         if ('' !== $dataContainer && false === strpos($dataContainer'\\'))
  1360.         {
  1361.             trigger_deprecation('contao/core-bundle''4.9''The usage of a non fully qualified class name as DataContainer name has been deprecated and will no longer work in Contao 5.0. Use the fully qualified class name instead, e.g. Contao\DC_Table::class.');
  1362.             $dataContainer 'DC_' $dataContainer;
  1363.             if (class_exists($dataContainer))
  1364.             {
  1365.                 $ref = new \ReflectionClass($dataContainer);
  1366.                 return $ref->getName();
  1367.             }
  1368.         }
  1369.         return $dataContainer;
  1370.     }
  1371.     /**
  1372.      * Generates the label for a given data record according to the DCA configuration.
  1373.      * Returns an array of strings if 'showColumns' is enabled in the DCA configuration.
  1374.      *
  1375.      * @param array  $row   The data record
  1376.      * @param string $table The name of the data container
  1377.      *
  1378.      * @return string|array<string>
  1379.      */
  1380.     public function generateRecordLabel(array $rowstring $table nullbool $protected falsebool $isVisibleRootTrailPage false)
  1381.     {
  1382.         $table $table ?? $this->strTable;
  1383.         $labelConfig = &$GLOBALS['TL_DCA'][$table]['list']['label'];
  1384.         $args = array();
  1385.         foreach ($labelConfig['fields'] as $k=>$v)
  1386.         {
  1387.             // Decrypt the value
  1388.             if ($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['encrypt'] ?? null)
  1389.             {
  1390.                 $row[$v] = Encryption::decrypt(StringUtil::deserialize($row[$v]));
  1391.             }
  1392.             if (strpos($v':') !== false)
  1393.             {
  1394.                 list($strKey$strTable) = explode(':'$v2);
  1395.                 list($strTable$strField) = explode('.'$strTable2);
  1396.                 $objRef Database::getInstance()
  1397.                     ->prepare("SELECT " Database::quoteIdentifier($strField) . " FROM " $strTable " WHERE id=?")
  1398.                     ->limit(1)
  1399.                     ->execute($row[$strKey]);
  1400.                 $args[$k] = $objRef->numRows $objRef->$strField '';
  1401.             }
  1402.             elseif (\in_array($GLOBALS['TL_DCA'][$table]['fields'][$v]['flag'] ?? null, array(self::SORT_DAY_ASCself::SORT_DAY_DESCself::SORT_MONTH_ASCself::SORT_MONTH_DESCself::SORT_YEAR_ASCself::SORT_YEAR_DESC)))
  1403.             {
  1404.                 if (($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['rgxp'] ?? null) == 'date')
  1405.                 {
  1406.                     $args[$k] = $row[$v] ? Date::parse(Config::get('dateFormat'), $row[$v]) : '-';
  1407.                 }
  1408.                 elseif (($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['rgxp'] ?? null) == 'time')
  1409.                 {
  1410.                     $args[$k] = $row[$v] ? Date::parse(Config::get('timeFormat'), $row[$v]) : '-';
  1411.                 }
  1412.                 else
  1413.                 {
  1414.                     $args[$k] = $row[$v] ? Date::parse(Config::get('datimFormat'), $row[$v]) : '-';
  1415.                 }
  1416.             }
  1417.             elseif (($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['isBoolean'] ?? null) || (($GLOBALS['TL_DCA'][$table]['fields'][$v]['inputType'] ?? null) == 'checkbox' && !($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['multiple'] ?? null)))
  1418.             {
  1419.                 $args[$k] = $row[$v] ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no'];
  1420.             }
  1421.             elseif (isset($row[$v]))
  1422.             {
  1423.                 $row_v StringUtil::deserialize($row[$v]);
  1424.                 if (\is_array($row_v))
  1425.                 {
  1426.                     $args_k = array();
  1427.                     foreach ($row_v as $option)
  1428.                     {
  1429.                         $args_k[] = $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$option] ?? $option;
  1430.                     }
  1431.                     $args[$k] = implode(', '$args_k);
  1432.                 }
  1433.                 elseif (isset($GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]]))
  1434.                 {
  1435.                     $args[$k] = \is_array($GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]]) ? $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]][0] : $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]];
  1436.                 }
  1437.                 elseif ((($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['isAssociative'] ?? null) || ArrayUtil::isAssoc($GLOBALS['TL_DCA'][$table]['fields'][$v]['options'] ?? null)) && isset($GLOBALS['TL_DCA'][$table]['fields'][$v]['options'][$row[$v]]))
  1438.                 {
  1439.                     $args[$k] = $GLOBALS['TL_DCA'][$table]['fields'][$v]['options'][$row[$v]] ?? null;
  1440.                 }
  1441.                 else
  1442.                 {
  1443.                     $args[$k] = $row[$v];
  1444.                 }
  1445.             }
  1446.             else
  1447.             {
  1448.                 $args[$k] = null;
  1449.             }
  1450.         }
  1451.         // Render the label
  1452.         $label vsprintf($labelConfig['format'] ?? '%s'$args);
  1453.         // Shorten the label it if it is too long
  1454.         if (($labelConfig['maxCharacters'] ?? null) > && $labelConfig['maxCharacters'] < \strlen(strip_tags($label)))
  1455.         {
  1456.             $label trim(StringUtil::substrHtml($label$labelConfig['maxCharacters'])) . ' …';
  1457.         }
  1458.         // Remove empty brackets (), [], {}, <> and empty tags from the label
  1459.         $label preg_replace('/\( *\) ?|\[ *] ?|{ *} ?|< *> ?/'''$label);
  1460.         $label preg_replace('/<[^>]+>\s*<\/[^>]+>/'''$label);
  1461.         $mode $GLOBALS['TL_DCA'][$table]['list']['sorting']['mode'] ?? self::MODE_SORTED;
  1462.         // Execute label_callback
  1463.         if (\is_array($labelConfig['label_callback'] ?? null) || \is_callable($labelConfig['label_callback'] ?? null))
  1464.         {
  1465.             if (\in_array($mode, array(self::MODE_TREEself::MODE_TREE_EXTENDED)))
  1466.             {
  1467.                 if (\is_array($labelConfig['label_callback'] ?? null))
  1468.                 {
  1469.                     $label System::importStatic($labelConfig['label_callback'][0])->{$labelConfig['label_callback'][1]}($row$label$this''false$protected$isVisibleRootTrailPage);
  1470.                 }
  1471.                 else
  1472.                 {
  1473.                     $label $labelConfig['label_callback']($row$label$this''false$protected$isVisibleRootTrailPage);
  1474.                 }
  1475.             }
  1476.             elseif ($mode === self::MODE_PARENT)
  1477.             {
  1478.                 if (\is_array($labelConfig['label_callback'] ?? null))
  1479.                 {
  1480.                     $label System::importStatic($labelConfig['label_callback'][0])->{$labelConfig['label_callback'][1]}($row$label$this);
  1481.                 }
  1482.                 else
  1483.                 {
  1484.                     $label $labelConfig['label_callback']($row$label$this);
  1485.                 }
  1486.             }
  1487.             else
  1488.             {
  1489.                 if (\is_array($labelConfig['label_callback'] ?? null))
  1490.                 {
  1491.                     $label System::importStatic($labelConfig['label_callback'][0])->{$labelConfig['label_callback'][1]}($row$label$this$args);
  1492.                 }
  1493.                 else
  1494.                 {
  1495.                     $label $labelConfig['label_callback']($row$label$this$args);
  1496.                 }
  1497.             }
  1498.         }
  1499.         elseif (\in_array($mode, array(self::MODE_TREEself::MODE_TREE_EXTENDED)))
  1500.         {
  1501.             $label Image::getHtml('iconPLAIN.svg') . ' ' $label;
  1502.         }
  1503.         if (($labelConfig['showColumns'] ?? null) && !\in_array($mode, array(self::MODE_PARENTself::MODE_TREEself::MODE_TREE_EXTENDED)))
  1504.         {
  1505.             return \is_array($label) ? $label $args;
  1506.         }
  1507.         return $label;
  1508.     }
  1509. }
  1510. class_alias(DataContainer::class, 'DataContainer');