vendor/contao/core-bundle/src/Resources/contao/dca/tl_article.php line 204

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. use Contao\ArticleModel;
  10. use Contao\Backend;
  11. use Contao\BackendUser;
  12. use Contao\Config;
  13. use Contao\Controller;
  14. use Contao\CoreBundle\Exception\AccessDeniedException;
  15. use Contao\CoreBundle\Security\ContaoCorePermissions;
  16. use Contao\DataContainer;
  17. use Contao\DC_Table;
  18. use Contao\Image;
  19. use Contao\Input;
  20. use Contao\LayoutModel;
  21. use Contao\PageModel;
  22. use Contao\StringUtil;
  23. use Contao\System;
  24. use Contao\Versions;
  25. $this->loadDataContainer('tl_page');
  26. $GLOBALS['TL_DCA']['tl_article'] = array
  27. (
  28.     // Config
  29.     'config' => array
  30.     (
  31.         'dataContainer'               => DC_Table::class,
  32.         'ptable'                      => 'tl_page',
  33.         'ctable'                      => array('tl_content'),
  34.         'switchToEdit'                => true,
  35.         'enableVersioning'            => true,
  36.         'markAsCopy'                  => 'title',
  37.         'onload_callback' => array
  38.         (
  39.             array('tl_article''checkPermission'),
  40.             array('tl_article''addCustomLayoutSectionReferences'),
  41.             array('tl_page''addBreadcrumb')
  42.         ),
  43.         'sql' => array
  44.         (
  45.             'keys' => array
  46.             (
  47.                 'id' => 'primary',
  48.                 'alias' => 'index',
  49.                 'pid,published,inColumn,start,stop' => 'index'
  50.             )
  51.         )
  52.     ),
  53.     // List
  54.     'list' => array
  55.     (
  56.         'sorting' => array
  57.         (
  58.             'mode'                    => DataContainer::MODE_TREE_EXTENDED,
  59.             'panelLayout'             => 'filter;search'
  60.         ),
  61.         'label' => array
  62.         (
  63.             'fields'                  => array('title''inColumn'),
  64.             'format'                  => '%s <span style="color:#999;padding-left:3px">[%s]</span>',
  65.             'label_callback'          => array('tl_article''addIcon')
  66.         ),
  67.         'global_operations' => array
  68.         (
  69.             'toggleNodes' => array
  70.             (
  71.                 'href'                => '&amp;ptg=all',
  72.                 'class'               => 'header_toggle',
  73.                 'showOnSelect'        => true
  74.             ),
  75.             'all' => array
  76.             (
  77.                 'href'                => 'act=select',
  78.                 'class'               => 'header_edit_all',
  79.                 'attributes'          => 'onclick="Backend.getScrollOffset()" accesskey="e"'
  80.             )
  81.         ),
  82.         'operations' => array
  83.         (
  84.             'edit' => array
  85.             (
  86.                 'href'                => 'table=tl_content',
  87.                 'icon'                => 'edit.svg',
  88.                 'button_callback'     => array('tl_article''editArticle')
  89.             ),
  90.             'editheader' => array
  91.             (
  92.                 'href'                => 'act=edit',
  93.                 'icon'                => 'header.svg',
  94.                 'button_callback'     => array('tl_article''editHeader')
  95.             ),
  96.             'copy' => array
  97.             (
  98.                 'href'                => 'act=paste&amp;mode=copy',
  99.                 'icon'                => 'copy.svg',
  100.                 'attributes'          => 'onclick="Backend.getScrollOffset()"',
  101.                 'button_callback'     => array('tl_article''copyArticle')
  102.             ),
  103.             'cut' => array
  104.             (
  105.                 'href'                => 'act=paste&amp;mode=cut',
  106.                 'icon'                => 'cut.svg',
  107.                 'attributes'          => 'onclick="Backend.getScrollOffset()"',
  108.                 'button_callback'     => array('tl_article''cutArticle')
  109.             ),
  110.             'delete' => array
  111.             (
  112.                 'href'                => 'act=delete',
  113.                 'icon'                => 'delete.svg',
  114.                 'attributes'          => 'onclick="if(!confirm(\'' . ($GLOBALS['TL_LANG']['MSC']['deleteConfirm'] ?? null) . '\'))return false;Backend.getScrollOffset()"',
  115.                 'button_callback'     => array('tl_article''deleteArticle')
  116.             ),
  117.             'toggle' => array
  118.             (
  119.                 'href'                => 'act=toggle&amp;field=published',
  120.                 'icon'                => 'visible.svg',
  121.                 'button_callback'     => array('tl_article''toggleIcon'),
  122.                 'showInHeader'        => true
  123.             ),
  124.             'show' => array
  125.             (
  126.                 'href'                => 'act=show',
  127.                 'icon'                => 'show.svg'
  128.             )
  129.         )
  130.     ),
  131.     // Select
  132.     'select' => array
  133.     (
  134.         'buttons_callback' => array
  135.         (
  136.             array('tl_article''addAliasButton')
  137.         )
  138.     ),
  139.     // Palettes
  140.     'palettes' => array
  141.     (
  142.         '__selector__'                => array('protected'),
  143.         'default'                     => '{title_legend},title,alias,author;{layout_legend},inColumn,keywords;{teaser_legend:hide},teaserCssID,showTeaser,teaser;{syndication_legend},printable;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID;{publish_legend},published,start,stop'
  144.     ),
  145.     // Subpalettes
  146.     'subpalettes' => array
  147.     (
  148.         'protected'                   => 'groups'
  149.     ),
  150.     // Fields
  151.     'fields' => array
  152.     (
  153.         'id' => array
  154.         (
  155.             'label'                   => array('ID'),
  156.             'search'                  => true,
  157.             'sql'                     => "int(10) unsigned NOT NULL auto_increment"
  158.         ),
  159.         'pid' => array
  160.         (
  161.             'foreignKey'              => 'tl_page.title',
  162.             'sql'                     => "int(10) unsigned NOT NULL default 0",
  163.             'relation'                => array('type'=>'belongsTo''load'=>'lazy')
  164.         ),
  165.         'sorting' => array
  166.         (
  167.             'sql'                     => "int(10) unsigned NOT NULL default 0"
  168.         ),
  169.         'tstamp' => array
  170.         (
  171.             'sql'                     => "int(10) unsigned NOT NULL default 0"
  172.         ),
  173.         'title' => array
  174.         (
  175.             'exclude'                 => true,
  176.             'inputType'               => 'text',
  177.             'search'                  => true,
  178.             'eval'                    => array('mandatory'=>true'decodeEntities'=>true'maxlength'=>255'tl_class'=>'w50'),
  179.             'sql'                     => "varchar(255) NOT NULL default ''"
  180.         ),
  181.         'alias' => array
  182.         (
  183.             'exclude'                 => true,
  184.             'inputType'               => 'text',
  185.             'search'                  => true,
  186.             'eval'                    => array('rgxp'=>'alias''doNotCopy'=>true'maxlength'=>255'tl_class'=>'w50 clr'),
  187.             'save_callback' => array
  188.             (
  189.                 array('tl_article''generateAlias')
  190.             ),
  191.             'sql'                     => "varchar(255) BINARY NOT NULL default ''"
  192.         ),
  193.         'author' => array
  194.         (
  195.             'default'                 => BackendUser::getInstance()->id,
  196.             'exclude'                 => true,
  197.             'search'                  => true,
  198.             'filter'                  => true,
  199.             'inputType'               => 'select',
  200.             'foreignKey'              => 'tl_user.name',
  201.             'eval'                    => array('doNotCopy'=>true'mandatory'=>true'chosen'=>true'includeBlankOption'=>true'tl_class'=>'w50'),
  202.             'sql'                     => "int(10) unsigned NOT NULL default 0",
  203.             'relation'                => array('type'=>'hasOne''load'=>'lazy')
  204.         ),
  205.         'inColumn' => array
  206.         (
  207.             'exclude'                 => true,
  208.             'filter'                  => true,
  209.             'inputType'               => 'select',
  210.             'options_callback'        => array('tl_article''getActiveLayoutSections'),
  211.             'eval'                    => array('mandatory'=>true'tl_class'=>'w50'),
  212.             'reference'               => &$GLOBALS['TL_LANG']['COLS'],
  213.             'sql'                     => "varchar(32) NOT NULL default 'main'"
  214.         ),
  215.         'keywords' => array
  216.         (
  217.             'exclude'                 => true,
  218.             'inputType'               => 'textarea',
  219.             'search'                  => true,
  220.             'eval'                    => array('style'=>'height:60px''decodeEntities'=>true'tl_class'=>'clr'),
  221.             'sql'                     => "text NULL"
  222.         ),
  223.         'showTeaser' => array
  224.         (
  225.             'exclude'                 => true,
  226.             'inputType'               => 'checkbox',
  227.             'eval'                    => array('tl_class'=>'w50 m12'),
  228.             'sql'                     => "char(1) NOT NULL default ''"
  229.         ),
  230.         'teaserCssID' => array
  231.         (
  232.             'exclude'                 => true,
  233.             'inputType'               => 'text',
  234.             'eval'                    => array('multiple'=>true'size'=>2'tl_class'=>'w50'),
  235.             'sql'                     => "varchar(255) NOT NULL default ''"
  236.         ),
  237.         'teaser' => array
  238.         (
  239.             'exclude'                 => true,
  240.             'inputType'               => 'textarea',
  241.             'search'                  => true,
  242.             'eval'                    => array('rte'=>'tinyMCE''tl_class'=>'clr'),
  243.             'sql'                     => "text NULL"
  244.         ),
  245.         'printable' => array
  246.         (
  247.             'exclude'                 => true,
  248.             'inputType'               => 'checkbox',
  249.             'options'                 => array('print''facebook''twitter'),
  250.             'eval'                    => array('multiple'=>true),
  251.             'reference'               => &$GLOBALS['TL_LANG']['tl_article'],
  252.             'sql'                     => "varchar(255) NOT NULL default ''"
  253.         ),
  254.         'customTpl' => array
  255.         (
  256.             'exclude'                 => true,
  257.             'inputType'               => 'select',
  258.             'options_callback' => static function ()
  259.             {
  260.                 return Controller::getTemplateGroup('mod_article_', array(), 'mod_article');
  261.             },
  262.             'eval'                    => array('chosen'=>true'tl_class'=>'w50'),
  263.             'sql'                     => "varchar(64) NOT NULL default ''"
  264.         ),
  265.         'protected' => array
  266.         (
  267.             'exclude'                 => true,
  268.             'filter'                  => true,
  269.             'inputType'               => 'checkbox',
  270.             'eval'                    => array('submitOnChange'=>true),
  271.             'sql'                     => "char(1) NOT NULL default ''"
  272.         ),
  273.         'groups' => array
  274.         (
  275.             'exclude'                 => true,
  276.             'filter'                  => true,
  277.             'inputType'               => 'checkbox',
  278.             'foreignKey'              => 'tl_member_group.name',
  279.             'eval'                    => array('mandatory'=>true'multiple'=>true),
  280.             'sql'                     => "blob NULL",
  281.             'relation'                => array('type'=>'hasMany''load'=>'lazy')
  282.         ),
  283.         'guests' => array
  284.         (
  285.             'exclude'                 => true,
  286.             'filter'                  => true,
  287.             'inputType'               => 'checkbox',
  288.             'eval'                    => array('tl_class'=>'w50'),
  289.             'sql'                     => "char(1) NOT NULL default ''"
  290.         ),
  291.         'cssID' => array
  292.         (
  293.             'exclude'                 => true,
  294.             'inputType'               => 'text',
  295.             'eval'                    => array('multiple'=>true'size'=>2'tl_class'=>'w50 clr'),
  296.             'sql'                     => "varchar(255) NOT NULL default ''"
  297.         ),
  298.         'published' => array
  299.         (
  300.             'exclude'                 => true,
  301.             'toggle'                  => true,
  302.             'filter'                  => true,
  303.             'inputType'               => 'checkbox',
  304.             'eval'                    => array('doNotCopy'=>true),
  305.             'sql'                     => "char(1) NOT NULL default ''"
  306.         ),
  307.         'start' => array
  308.         (
  309.             'exclude'                 => true,
  310.             'inputType'               => 'text',
  311.             'eval'                    => array('rgxp'=>'datim''datepicker'=>true'tl_class'=>'w50 wizard'),
  312.             'sql'                     => "varchar(10) NOT NULL default ''"
  313.         ),
  314.         'stop' => array
  315.         (
  316.             'exclude'                 => true,
  317.             'inputType'               => 'text',
  318.             'eval'                    => array('rgxp'=>'datim''datepicker'=>true'tl_class'=>'w50 wizard'),
  319.             'sql'                     => "varchar(10) NOT NULL default ''"
  320.         )
  321.     )
  322. );
  323. /**
  324.  * Provide miscellaneous methods that are used by the data configuration array.
  325.  */
  326. class tl_article extends Backend
  327. {
  328.     /**
  329.      * Import the back end user object
  330.      */
  331.     public function __construct()
  332.     {
  333.         parent::__construct();
  334.         $this->import(BackendUser::class, 'User');
  335.     }
  336.     /**
  337.      * Check permissions to edit table tl_page
  338.      *
  339.      * @throws AccessDeniedException
  340.      */
  341.     public function checkPermission()
  342.     {
  343.         if ($this->User->isAdmin)
  344.         {
  345.             return;
  346.         }
  347.         $objSession System::getContainer()->get('session');
  348.         $session $objSession->all();
  349.         // Set the default page user and group
  350.         $GLOBALS['TL_DCA']['tl_page']['fields']['cuser']['default'] = (int) Config::get('defaultUser') ?: $this->User->id;
  351.         $GLOBALS['TL_DCA']['tl_page']['fields']['cgroup']['default'] = (int) Config::get('defaultGroup') ?: (int) $this->User->groups[0];
  352.         // Restrict the page tree
  353.         if (empty($this->User->pagemounts) || !is_array($this->User->pagemounts))
  354.         {
  355.             $root = array(0);
  356.         }
  357.         else
  358.         {
  359.             $root $this->User->pagemounts;
  360.         }
  361.         $GLOBALS['TL_DCA']['tl_page']['list']['sorting']['root'] = $root;
  362.         $security System::getContainer()->get('security.helper');
  363.         // Set allowed page IDs (edit multiple)
  364.         if (is_array($session['CURRENT']['IDS'] ?? null))
  365.         {
  366.             $edit_all = array();
  367.             $delete_all = array();
  368.             foreach ($session['CURRENT']['IDS'] as $id)
  369.             {
  370.                 $objArticle $this->Database->prepare("SELECT p.pid, p.includeChmod, p.chmod, p.cuser, p.cgroup FROM tl_article a, tl_page p WHERE a.id=? AND a.pid=p.id")
  371.                                              ->limit(1)
  372.                                              ->execute($id);
  373.                 if ($objArticle->numRows 1)
  374.                 {
  375.                     continue;
  376.                 }
  377.                 $row $objArticle->row();
  378.                 if ($security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLES$row))
  379.                 {
  380.                     $edit_all[] = $id;
  381.                 }
  382.                 if ($security->isGranted(ContaoCorePermissions::USER_CAN_DELETE_ARTICLES$row))
  383.                 {
  384.                     $delete_all[] = $id;
  385.                 }
  386.             }
  387.             $session['CURRENT']['IDS'] = (Input::get('act') == 'deleteAll') ? $delete_all $edit_all;
  388.         }
  389.         // Set allowed clipboard IDs
  390.         if (isset($session['CLIPBOARD']['tl_article']) && is_array($session['CLIPBOARD']['tl_article']['id']))
  391.         {
  392.             $clipboard = array();
  393.             foreach ($session['CLIPBOARD']['tl_article']['id'] as $id)
  394.             {
  395.                 $objArticle $this->Database->prepare("SELECT p.pid, p.includeChmod, p.chmod, p.cuser, p.cgroup FROM tl_article a, tl_page p WHERE a.id=? AND a.pid=p.id")
  396.                                              ->limit(1)
  397.                                              ->execute($id);
  398.                 if ($objArticle->numRows 1)
  399.                 {
  400.                     continue;
  401.                 }
  402.                 if ($security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLE_HIERARCHY$objArticle->row()))
  403.                 {
  404.                     $clipboard[] = $id;
  405.                 }
  406.             }
  407.             $session['CLIPBOARD']['tl_article']['id'] = $clipboard;
  408.         }
  409.         $permission null;
  410.         // Overwrite the session
  411.         $objSession->replace($session);
  412.         // Check current action
  413.         if (Input::get('act') && Input::get('act') != 'paste')
  414.         {
  415.             // Set ID of the article's page
  416.             $objPage $this->Database->prepare("SELECT pid FROM tl_article WHERE id=?")
  417.                                       ->limit(1)
  418.                                       ->execute(Input::get('id'));
  419.             $ids $objPage->numRows ? array($objPage->pid) : array();
  420.             // Set permission
  421.             switch (Input::get('act'))
  422.             {
  423.                 case 'edit':
  424.                 case 'toggle':
  425.                     $permission ContaoCorePermissions::USER_CAN_EDIT_ARTICLES;
  426.                     break;
  427.                 case 'move':
  428.                     $permission ContaoCorePermissions::USER_CAN_EDIT_ARTICLE_HIERARCHY;
  429.                     $ids[] = Input::get('sid');
  430.                     break;
  431.                 // Do not insert articles into a website root page
  432.                 case 'create':
  433.                 case 'copy':
  434.                 case 'copyAll':
  435.                 case 'cut':
  436.                 case 'cutAll':
  437.                     $permission ContaoCorePermissions::USER_CAN_EDIT_ARTICLE_HIERARCHY;
  438.                     // Insert into a page
  439.                     if (Input::get('mode') == 2)
  440.                     {
  441.                         $objParent $this->Database->prepare("SELECT id, type FROM tl_page WHERE id=?")
  442.                                                     ->limit(1)
  443.                                                     ->execute(Input::get('pid'));
  444.                         $ids[] = Input::get('pid');
  445.                     }
  446.                     // Insert after an article
  447.                     else
  448.                     {
  449.                         $objParent $this->Database->prepare("SELECT id, type FROM tl_page WHERE id=(SELECT pid FROM tl_article WHERE id=?)")
  450.                                                     ->limit(1)
  451.                                                     ->execute(Input::get('pid'));
  452.                         $ids[] = $objParent->id;
  453.                     }
  454.                     if ($objParent->numRows && $objParent->type == 'root')
  455.                     {
  456.                         throw new AccessDeniedException('Attempt to insert an article into website root page ID ' Input::get('pid') . '.');
  457.                     }
  458.                     break;
  459.                 case 'delete':
  460.                     $permission ContaoCorePermissions::USER_CAN_DELETE_ARTICLES;
  461.                     break;
  462.             }
  463.             // Check user permissions
  464.             $pagemounts = array();
  465.             // Get all allowed pages for the current user
  466.             foreach ($this->User->pagemounts as $root)
  467.             {
  468.                 $pagemounts[] = array($root);
  469.                 $pagemounts[] = $this->Database->getChildRecords($root'tl_page');
  470.             }
  471.             if (!empty($pagemounts))
  472.             {
  473.                 $pagemounts array_merge(...$pagemounts);
  474.             }
  475.             $pagemounts array_unique($pagemounts);
  476.             // Check each page
  477.             foreach ($ids as $id)
  478.             {
  479.                 if (!in_array($id$pagemounts))
  480.                 {
  481.                     throw new AccessDeniedException('Page ID ' $id ' is not mounted.');
  482.                 }
  483.                 if (Input::get('act') == 'show')
  484.                 {
  485.                     continue;
  486.                 }
  487.                 // Check whether the current user has permission for the current page
  488.                 if ($permission === null || !$security->isGranted($permission$id))
  489.                 {
  490.                     throw new AccessDeniedException('Not enough permissions to ' Input::get('act') . ' ' . (Input::get('id') ? 'article ID ' Input::get('id') : ' articles') . ' on page ID ' $id ' or to paste it/them into page ID ' $id '.');
  491.                 }
  492.             }
  493.         }
  494.     }
  495.     /**
  496.      * Add an image to each page in the tree
  497.      *
  498.      * @param array  $row
  499.      * @param string $label
  500.      *
  501.      * @return string
  502.      */
  503.     public function addIcon($row$label)
  504.     {
  505.         $image 'articles';
  506.         $unpublished = ($row['start'] && $row['start'] > time()) || ($row['stop'] && $row['stop'] <= time());
  507.         if ($unpublished || !$row['published'])
  508.         {
  509.             $image .= '_';
  510.         }
  511.         $attributes sprintf(
  512.             'data-icon="%s" data-icon-disabled="%s"',
  513.             Image::getPath($unpublished $image rtrim($image'_')),
  514.             Image::getPath(rtrim($image'_') . '_')
  515.         );
  516.         $href System::getContainer()->get('router')->generate('contao_backend_preview', array('page'=>$row['pid'], 'article'=>($row['alias'] ?: $row['id'])));
  517.         return '<a href="' StringUtil::specialcharsUrl($href) . '" title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['view']) . '" target="_blank">' Image::getHtml($image '.svg'''$attributes) . '</a> ' $label;
  518.     }
  519.     /**
  520.      * Auto-generate an article alias if it has not been set yet
  521.      *
  522.      * @param mixed         $varValue
  523.      * @param DataContainer $dc
  524.      *
  525.      * @return string
  526.      *
  527.      * @throws Exception
  528.      */
  529.     public function generateAlias($varValueDataContainer $dc)
  530.     {
  531.         $aliasExists = function (string $alias) use ($dc): bool
  532.         {
  533.             if (in_array($alias, array('top''wrapper''header''container''main''left''right''footer'), true))
  534.             {
  535.                 return true;
  536.             }
  537.             return $this->Database->prepare("SELECT id FROM tl_article WHERE alias=? AND id!=?")->execute($alias$dc->id)->numRows 0;
  538.         };
  539.         // Generate an alias if there is none
  540.         if (!$varValue)
  541.         {
  542.             $varValue System::getContainer()->get('contao.slug')->generate($dc->activeRecord->title$dc->activeRecord->pid$aliasExists);
  543.         }
  544.         elseif (preg_match('/^[1-9]\d*$/'$varValue))
  545.         {
  546.             throw new Exception(sprintf($GLOBALS['TL_LANG']['ERR']['aliasNumeric'], $varValue));
  547.         }
  548.         elseif ($aliasExists($varValue))
  549.         {
  550.             throw new Exception(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $varValue));
  551.         }
  552.         return $varValue;
  553.     }
  554.     /**
  555.      * Return all active layout sections as array
  556.      *
  557.      * @param DataContainer $dc
  558.      *
  559.      * @return array
  560.      */
  561.     public function getActiveLayoutSections(DataContainer $dc)
  562.     {
  563.         // Show only active sections
  564.         if ($dc->activeRecord->pid ?? null)
  565.         {
  566.             $arrSections = array();
  567.             $objPage PageModel::findWithDetails($dc->activeRecord->pid);
  568.             // Get the layout sections
  569.             if ($objPage->layout)
  570.             {
  571.                 $objLayout LayoutModel::findByPk($objPage->layout);
  572.                 if ($objLayout === null)
  573.                 {
  574.                     return array();
  575.                 }
  576.                 $arrModules StringUtil::deserialize($objLayout->modules);
  577.                 if (empty($arrModules) || !is_array($arrModules))
  578.                 {
  579.                     return array();
  580.                 }
  581.                 // Find all sections with an article module (see #6094)
  582.                 foreach ($arrModules as $arrModule)
  583.                 {
  584.                     if ($arrModule['mod'] == && $arrModule['enable'])
  585.                     {
  586.                         $arrSections[] = $arrModule['col'];
  587.                     }
  588.                 }
  589.             }
  590.         }
  591.         // Show all sections (e.g. "override all" mode)
  592.         else
  593.         {
  594.             $arrSections = array('header''left''right''main''footer');
  595.             $objLayout $this->Database->query("SELECT sections FROM tl_layout WHERE sections!=''");
  596.             while ($objLayout->next())
  597.             {
  598.                 $arrCustom StringUtil::deserialize($objLayout->sections);
  599.                 // Add the custom layout sections
  600.                 if (!empty($arrCustom) && is_array($arrCustom))
  601.                 {
  602.                     foreach ($arrCustom as $v)
  603.                     {
  604.                         if (!empty($v['id']))
  605.                         {
  606.                             $arrSections[] = $v['id'];
  607.                         }
  608.                     }
  609.                 }
  610.             }
  611.         }
  612.         return Backend::convertLayoutSectionIdsToAssociativeArray($arrSections);
  613.     }
  614.     /**
  615.      * Return the edit article button
  616.      *
  617.      * @param array  $row
  618.      * @param string $href
  619.      * @param string $label
  620.      * @param string $title
  621.      * @param string $icon
  622.      * @param string $attributes
  623.      *
  624.      * @return string
  625.      */
  626.     public function editArticle($row$href$label$title$icon$attributes)
  627.     {
  628.         $objPage PageModel::findById($row['pid']);
  629.         return System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLES$objPage->row()) ? '<a href="' $this->addToUrl($href '&amp;id=' $row['id']) . '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($icon$label) . '</a> ' Image::getHtml(preg_replace('/\.svg$/i''_.svg'$icon)) . ' ';
  630.     }
  631.     /**
  632.      * Return the edit header button
  633.      *
  634.      * @param array  $row
  635.      * @param string $href
  636.      * @param string $label
  637.      * @param string $title
  638.      * @param string $icon
  639.      * @param string $attributes
  640.      *
  641.      * @return string
  642.      */
  643.     public function editHeader($row$href$label$title$icon$attributes)
  644.     {
  645.         $security System::getContainer()->get('security.helper');
  646.         if (!$security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELDS_OF_TABLE'tl_article'))
  647.         {
  648.             return Image::getHtml(preg_replace('/\.svg$/i''_.svg'$icon)) . ' ';
  649.         }
  650.         $objPage PageModel::findById($row['pid']);
  651.         return $security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLES$objPage->row()) ? '<a href="' $this->addToUrl($href '&amp;id=' $row['id']) . '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($icon$label) . '</a> ' Image::getHtml(preg_replace('/\.svg$/i''_.svg'$icon)) . ' ';
  652.     }
  653.     /**
  654.      * Return the copy article button
  655.      *
  656.      * @param array  $row
  657.      * @param string $href
  658.      * @param string $label
  659.      * @param string $title
  660.      * @param string $icon
  661.      * @param string $attributes
  662.      * @param string $table
  663.      *
  664.      * @return string
  665.      */
  666.     public function copyArticle($row$href$label$title$icon$attributes$table)
  667.     {
  668.         if ($GLOBALS['TL_DCA'][$table]['config']['closed'] ?? null)
  669.         {
  670.             return '';
  671.         }
  672.         $objPage PageModel::findById($row['pid']);
  673.         return System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLE_HIERARCHY$objPage->row()) ? '<a href="' $this->addToUrl($href '&amp;id=' $row['id']) . '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($icon$label) . '</a> ' Image::getHtml(preg_replace('/\.svg$/i''_.svg'$icon)) . ' ';
  674.     }
  675.     /**
  676.      * Return the cut article button
  677.      *
  678.      * @param array  $row
  679.      * @param string $href
  680.      * @param string $label
  681.      * @param string $title
  682.      * @param string $icon
  683.      * @param string $attributes
  684.      *
  685.      * @return string
  686.      */
  687.     public function cutArticle($row$href$label$title$icon$attributes)
  688.     {
  689.         $objPage PageModel::findById($row['pid']);
  690.         return System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLE_HIERARCHY$objPage->row()) ? '<a href="' $this->addToUrl($href '&amp;id=' $row['id']) . '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($icon$label) . '</a> ' Image::getHtml(preg_replace('/\.svg$/i''_.svg'$icon)) . ' ';
  691.     }
  692.     /**
  693.      * Return the paste article button
  694.      *
  695.      * @param DataContainer $dc
  696.      * @param array         $row
  697.      * @param string        $table
  698.      * @param boolean       $cr
  699.      * @param array         $arrClipboard
  700.      *
  701.      * @return string
  702.      *
  703.      * @deprecated
  704.      */
  705.     public function pasteArticle(DataContainer $dc$row$table$cr$arrClipboard=null)
  706.     {
  707.         trigger_deprecation('contao/core-bundle''4.10''Using "tl_article::pasteArticle()" has been deprecated and will no longer work in Contao 5.0.');
  708.         return System::getContainer()
  709.             ->get('contao.listener.data_container.content_composition')
  710.             ->renderArticlePasteButton($dc$row$table$cr$arrClipboard)
  711.         ;
  712.     }
  713.     /**
  714.      * Return the delete article button
  715.      *
  716.      * @param array  $row
  717.      * @param string $href
  718.      * @param string $label
  719.      * @param string $title
  720.      * @param string $icon
  721.      * @param string $attributes
  722.      *
  723.      * @return string
  724.      */
  725.     public function deleteArticle($row$href$label$title$icon$attributes)
  726.     {
  727.         $objPage PageModel::findById($row['pid']);
  728.         return System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_DELETE_ARTICLES$objPage->row()) ? '<a href="' $this->addToUrl($href '&amp;id=' $row['id']) . '" title="' StringUtil::specialchars($title) . '"' $attributes '>' Image::getHtml($icon$label) . '</a> ' Image::getHtml(preg_replace('/\.svg$/i''_.svg'$icon)) . ' ';
  729.     }
  730.     /**
  731.      * Automatically generate the folder URL aliases
  732.      *
  733.      * @param array $arrButtons
  734.      *
  735.      * @return array
  736.      */
  737.     public function addAliasButton($arrButtons)
  738.     {
  739.         if (!System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELD_OF_TABLE'tl_article::alias'))
  740.         {
  741.             return $arrButtons;
  742.         }
  743.         // Generate the aliases
  744.         if (isset($_POST['alias']) && Input::post('FORM_SUBMIT') == 'tl_select')
  745.         {
  746.             $objSession System::getContainer()->get('session');
  747.             $session $objSession->all();
  748.             $ids $session['CURRENT']['IDS'] ?? array();
  749.             foreach ($ids as $id)
  750.             {
  751.                 $objArticle ArticleModel::findByPk($id);
  752.                 if ($objArticle === null)
  753.                 {
  754.                     continue;
  755.                 }
  756.                 $strAlias System::getContainer()->get('contao.slug')->generate($objArticle->title$objArticle->pid);
  757.                 // The alias has not changed
  758.                 if ($strAlias == $objArticle->alias)
  759.                 {
  760.                     continue;
  761.                 }
  762.                 // Initialize the version manager
  763.                 $objVersions = new Versions('tl_article'$id);
  764.                 $objVersions->initialize();
  765.                 // Store the new alias
  766.                 $this->Database->prepare("UPDATE tl_article SET alias=? WHERE id=?")
  767.                                ->execute($strAlias$id);
  768.                 // Create a new version
  769.                 $objVersions->create();
  770.             }
  771.             $this->redirect($this->getReferer());
  772.         }
  773.         // Add the button
  774.         $arrButtons['alias'] = '<button type="submit" name="alias" id="alias" class="tl_submit" accesskey="a">' $GLOBALS['TL_LANG']['MSC']['aliasSelected'] . '</button> ';
  775.         return $arrButtons;
  776.     }
  777.     /**
  778.      * Return the "toggle visibility" button
  779.      *
  780.      * @param array  $row
  781.      * @param string $href
  782.      * @param string $label
  783.      * @param string $title
  784.      * @param string $icon
  785.      * @param string $attributes
  786.      *
  787.      * @return string
  788.      */
  789.     public function toggleIcon($row$href$label$title$icon$attributes)
  790.     {
  791.         $security System::getContainer()->get('security.helper');
  792.         // Check permissions AFTER checking the tid, so hacking attempts are logged
  793.         if (!$security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELD_OF_TABLE'tl_article::published'))
  794.         {
  795.             return '';
  796.         }
  797.         $href .= '&amp;id=' $row['id'];
  798.         if (!$row['published'])
  799.         {
  800.             $icon 'invisible.svg';
  801.         }
  802.         $objPage PageModel::findById($row['pid']);
  803.         if (!$security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_ARTICLES$objPage->row()))
  804.         {
  805.             if ($row['published'])
  806.             {
  807.                 $icon preg_replace('/\.svg$/i''_.svg'$icon); // see #8126
  808.             }
  809.             return Image::getHtml($icon) . ' ';
  810.         }
  811.         return '<a href="' $this->addToUrl($href) . '" title="' StringUtil::specialchars($title) . '" onclick="Backend.getScrollOffset();return AjaxRequest.toggleField(this,true)">' Image::getHtml($icon$label'data-icon="' Image::getPath('visible.svg') . '" data-icon-disabled="' Image::getPath('invisible.svg') . '" data-state="' . ($row['published'] ? 0) . '"') . '</a> ';
  812.     }
  813. }