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

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