vendor/contao/core-bundle/src/Resources/contao/classes/BackendUser.php line 34

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\RedirectResponseException;
  11. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  12. use Symfony\Component\Routing\RouterInterface;
  13. use Symfony\Component\Security\Core\User\UserInterface;
  14. /**
  15.  * Provide methods to manage back end users.
  16.  *
  17.  * @property boolean $isAdmin
  18.  * @property array   $groups
  19.  * @property array   $elements
  20.  * @property array   $fields
  21.  * @property array   $pagemounts
  22.  * @property array   $filemounts
  23.  * @property array   $filemountIds
  24.  * @property string  $fop
  25.  * @property array   $alexf
  26.  * @property array   $imageSizes
  27.  *
  28.  * @author Leo Feyer <https://github.com/leofeyer>
  29.  */
  30. class BackendUser extends User
  31. {
  32.     /**
  33.      * Edit page flag
  34.      */
  35.     const CAN_EDIT_PAGE 1;
  36.     /**
  37.      * Edit page hierarchy flag
  38.      */
  39.     const CAN_EDIT_PAGE_HIERARCHY 2;
  40.     /**
  41.      * Delete page flag
  42.      */
  43.     const CAN_DELETE_PAGE 3;
  44.     /**
  45.      * Edit articles flag
  46.      */
  47.     const CAN_EDIT_ARTICLES 4;
  48.     /**
  49.      * Edit article hierarchy flag
  50.      */
  51.     const CAN_EDIT_ARTICLE_HIERARCHY 5;
  52.     /**
  53.      * Delete articles flag
  54.      */
  55.     const CAN_DELETE_ARTICLES 6;
  56.     /**
  57.      * Symfony Security session key
  58.      * @deprecated Deprecated since Contao 4.8, to be removed in Contao 5.0
  59.      */
  60.     const SECURITY_SESSION_KEY '_security_contao_backend';
  61.     /**
  62.      * Current object instance (do not remove)
  63.      * @var BackendUser
  64.      */
  65.     protected static $objInstance;
  66.     /**
  67.      * Name of the corresponding table
  68.      * @var string
  69.      */
  70.     protected $strTable 'tl_user';
  71.     /**
  72.      * Name of the current cookie
  73.      * @var string
  74.      */
  75.     protected $strCookie 'BE_USER_AUTH';
  76.     /**
  77.      * Allowed excluded fields
  78.      * @var array
  79.      */
  80.     protected $alexf = array();
  81.     /**
  82.      * File mount IDs
  83.      * @var array
  84.      */
  85.     protected $arrFilemountIds;
  86.     /**
  87.      * Symfony security roles
  88.      * @var array
  89.      */
  90.     protected $roles = array('ROLE_USER');
  91.     /**
  92.      * Initialize the object
  93.      */
  94.     protected function __construct()
  95.     {
  96.         parent::__construct();
  97.         $this->strIp Environment::get('ip');
  98.         $this->strHash Input::cookie($this->strCookie);
  99.     }
  100.     /**
  101.      * Instantiate a new user object
  102.      *
  103.      * @return static|User The object instance
  104.      */
  105.     public static function getInstance()
  106.     {
  107.         if (static::$objInstance !== null)
  108.         {
  109.             return static::$objInstance;
  110.         }
  111.         $objToken System::getContainer()->get('security.token_storage')->getToken();
  112.         // Load the user from the security storage
  113.         if ($objToken !== null && is_a($objToken->getUser(), static::class))
  114.         {
  115.             return $objToken->getUser();
  116.         }
  117.         // Check for an authenticated user in the session
  118.         $strUser System::getContainer()->get('contao.security.token_checker')->getBackendUsername();
  119.         if ($strUser !== null)
  120.         {
  121.             static::$objInstance = static::loadUserByUsername($strUser);
  122.             return static::$objInstance;
  123.         }
  124.         return parent::getInstance();
  125.     }
  126.     /**
  127.      * Extend parent getter class and modify some parameters
  128.      *
  129.      * @param string $strKey
  130.      *
  131.      * @return mixed
  132.      */
  133.     public function __get($strKey)
  134.     {
  135.         switch ($strKey)
  136.         {
  137.             case 'isAdmin':
  138.                 return $this->arrData['admin'] ? true false;
  139.             case 'groups':
  140.                 return \is_array($this->arrData['groups']) ? $this->arrData['groups'] : ($this->arrData['groups'] ? array($this->arrData['groups']) : array());
  141.             case 'pagemounts':
  142.                 return \is_array($this->arrData['pagemounts']) ? $this->arrData['pagemounts'] : ($this->arrData['pagemounts'] ? array($this->arrData['pagemounts']) : false);
  143.             case 'filemounts':
  144.                 return \is_array($this->arrData['filemounts']) ? $this->arrData['filemounts'] : ($this->arrData['filemounts'] ? array($this->arrData['filemounts']) : false);
  145.             case 'filemountIds':
  146.                 return $this->arrFilemountIds;
  147.             case 'fop':
  148.                 return \is_array($this->arrData['fop']) ? $this->arrData['fop'] : ($this->arrData['fop'] ? array($this->arrData['fop']) : false);
  149.             case 'alexf':
  150.                 return $this->alexf;
  151.         }
  152.         return parent::__get($strKey);
  153.     }
  154.     /**
  155.      * Redirect to the login screen if authentication fails
  156.      *
  157.      * @return boolean True if the user could be authenticated
  158.      *
  159.      * @deprecated Deprecated since Contao 4.5, to be removed in Contao 5.0.
  160.      *             Use Symfony security instead.
  161.      */
  162.     public function authenticate()
  163.     {
  164.         trigger_deprecation('contao/core-bundle''4.5''Using "Contao\BackendUser::authenticate()" has been deprecated and will no longer work in Contao 5.0. Use Symfony security instead.');
  165.         // Do not redirect if authentication is successful
  166.         if (System::getContainer()->get('contao.security.token_checker')->hasBackendUser())
  167.         {
  168.             return true;
  169.         }
  170.         if (!$request System::getContainer()->get('request_stack')->getCurrentRequest())
  171.         {
  172.             return false;
  173.         }
  174.         $route $request->attributes->get('_route');
  175.         if ($route == 'contao_backend_login')
  176.         {
  177.             return false;
  178.         }
  179.         $url System::getContainer()->get('router')->generate('contao_backend_login', array('redirect' => $request->getUri()), UrlGeneratorInterface::ABSOLUTE_URL);
  180.         throw new RedirectResponseException(System::getContainer()->get('uri_signer')->sign($url));
  181.     }
  182.     /**
  183.      * Try to login the current user
  184.      *
  185.      * @return boolean True if the user could be logged in
  186.      *
  187.      * @deprecated Deprecated since Contao 4.5, to be removed in Contao 5.0.
  188.      *             Use Symfony security instead.
  189.      */
  190.     public function login()
  191.     {
  192.         trigger_deprecation('contao/core-bundle''4.5''Using "Contao\BackendUser::login()" has been deprecated and will no longer work in Contao 5.0. Use Symfony security instead.');
  193.         return System::getContainer()->get('contao.security.token_checker')->hasBackendUser();
  194.     }
  195.     /**
  196.      * Check whether the current user has a certain access right
  197.      *
  198.      * @param array|string $field
  199.      * @param string       $array
  200.      *
  201.      * @return boolean
  202.      */
  203.     public function hasAccess($field$array)
  204.     {
  205.         if ($this->isAdmin)
  206.         {
  207.             return true;
  208.         }
  209.         if (!\is_array($field))
  210.         {
  211.             $field = array($field);
  212.         }
  213.         if (\is_array($this->$array) && array_intersect($field$this->$array))
  214.         {
  215.             return true;
  216.         }
  217.         if ($array == 'filemounts')
  218.         {
  219.             // Check the subfolders (filemounts)
  220.             foreach ($this->filemounts as $folder)
  221.             {
  222.                 if (preg_match('/^' preg_quote($folder'/') . '(\/|$)/i'$field[0]))
  223.                 {
  224.                     return true;
  225.                 }
  226.             }
  227.         }
  228.         return false;
  229.     }
  230.     /**
  231.      * Return true if the current user is allowed to do the current operation on the current page
  232.      *
  233.      * @param integer $int
  234.      * @param array   $row
  235.      *
  236.      * @return boolean
  237.      */
  238.     public function isAllowed($int$row)
  239.     {
  240.         if ($this->isAdmin)
  241.         {
  242.             return true;
  243.         }
  244.         // Inherit CHMOD settings
  245.         if (!$row['includeChmod'])
  246.         {
  247.             $pid $row['pid'];
  248.             $row['chmod'] = false;
  249.             $row['cuser'] = false;
  250.             $row['cgroup'] = false;
  251.             $objParentPage PageModel::findById($pid);
  252.             while ($objParentPage !== null && $row['chmod'] === false && $pid 0)
  253.             {
  254.                 $pid $objParentPage->pid;
  255.                 $row['chmod'] = $objParentPage->includeChmod $objParentPage->chmod false;
  256.                 $row['cuser'] = $objParentPage->includeChmod $objParentPage->cuser false;
  257.                 $row['cgroup'] = $objParentPage->includeChmod $objParentPage->cgroup false;
  258.                 $objParentPage PageModel::findById($pid);
  259.             }
  260.             // Set default values
  261.             if ($row['chmod'] === false)
  262.             {
  263.                 $row['chmod'] = Config::get('defaultChmod');
  264.             }
  265.             if ($row['cuser'] === false)
  266.             {
  267.                 $row['cuser'] = (int) Config::get('defaultUser');
  268.             }
  269.             if ($row['cgroup'] === false)
  270.             {
  271.                 $row['cgroup'] = (int) Config::get('defaultGroup');
  272.             }
  273.         }
  274.         // Set permissions
  275.         $chmod StringUtil::deserialize($row['chmod']);
  276.         $chmod = \is_array($chmod) ? $chmod : array($chmod);
  277.         $permission = array('w' $int);
  278.         if (\in_array($row['cgroup'], $this->groups))
  279.         {
  280.             $permission[] = 'g' $int;
  281.         }
  282.         if ($row['cuser'] == $this->id)
  283.         {
  284.             $permission[] = 'u' $int;
  285.         }
  286.         return \count(array_intersect($permission$chmod)) > 0;
  287.     }
  288.     /**
  289.      * Return true if there is at least one allowed excluded field
  290.      *
  291.      * @param string $table
  292.      *
  293.      * @return boolean
  294.      */
  295.     public function canEditFieldsOf($table)
  296.     {
  297.         if ($this->isAdmin)
  298.         {
  299.             return true;
  300.         }
  301.         return \count(preg_grep('/^' preg_quote($table'/') . '::/'$this->alexf)) > 0;
  302.     }
  303.     /**
  304.      * Restore the original numeric file mounts (see #5083)
  305.      */
  306.     public function save()
  307.     {
  308.         $filemounts $this->filemounts;
  309.         if (!empty($this->arrFilemountIds))
  310.         {
  311.             $this->arrData['filemounts'] = $this->arrFilemountIds;
  312.         }
  313.         parent::save();
  314.         $this->filemounts $filemounts;
  315.     }
  316.     /**
  317.      * Set all user properties from a database record
  318.      */
  319.     protected function setUserFromDb()
  320.     {
  321.         $this->intId $this->id;
  322.         // Unserialize values
  323.         foreach ($this->arrData as $k=>$v)
  324.         {
  325.             if (!is_numeric($v))
  326.             {
  327.                 $this->$k StringUtil::deserialize($v);
  328.             }
  329.         }
  330.         $GLOBALS['TL_USERNAME'] = $this->username;
  331.         Config::set('showHelp'$this->showHelp);
  332.         Config::set('useRTE'$this->useRTE);
  333.         Config::set('useCE'$this->useCE);
  334.         Config::set('thumbnails'$this->thumbnails);
  335.         Config::set('backendTheme'$this->backendTheme);
  336.         Config::set('fullscreen'$this->fullscreen);
  337.         // Inherit permissions
  338.         $always = array('alexf');
  339.         $depends = array('modules''themes''elements''fields''pagemounts''alpty''filemounts''fop''forms''formp''imageSizes''amg');
  340.         // HOOK: Take custom permissions
  341.         if (!empty($GLOBALS['TL_PERMISSIONS']) && \is_array($GLOBALS['TL_PERMISSIONS']))
  342.         {
  343.             $depends array_merge($depends$GLOBALS['TL_PERMISSIONS']);
  344.         }
  345.         // Overwrite user permissions if only group permissions shall be inherited
  346.         if ($this->inherit == 'group')
  347.         {
  348.             foreach ($depends as $field)
  349.             {
  350.                 $this->$field = array();
  351.             }
  352.         }
  353.         // Merge permissions
  354.         $inherit = \in_array($this->inherit, array('group''extend')) ? array_merge($always$depends) : $always;
  355.         $time Date::floorToMinute();
  356.         foreach ((array) $this->groups as $id)
  357.         {
  358.             $objGroup $this->Database->prepare("SELECT * FROM tl_user_group WHERE id=? AND disable!='1' AND (start='' OR start<='$time') AND (stop='' OR stop>'$time')")
  359.                                        ->limit(1)
  360.                                        ->execute($id);
  361.             if ($objGroup->numRows 0)
  362.             {
  363.                 foreach ($inherit as $field)
  364.                 {
  365.                     $value StringUtil::deserialize($objGroup->$fieldtrue);
  366.                     // The new page/file picker can return integers instead of arrays, so use empty() instead of is_array() and StringUtil::deserialize(true) here
  367.                     if (!empty($value))
  368.                     {
  369.                         $this->$field array_merge((\is_array($this->$field) ? $this->$field : ($this->$field ? array($this->$field) : array())), $value);
  370.                         $this->$field array_unique($this->$field);
  371.                     }
  372.                 }
  373.             }
  374.         }
  375.         // Make sure pagemounts and filemounts are set!
  376.         if (!\is_array($this->pagemounts))
  377.         {
  378.             $this->pagemounts = array();
  379.         }
  380.         else
  381.         {
  382.             $this->pagemounts array_filter($this->pagemounts);
  383.         }
  384.         if (!\is_array($this->filemounts))
  385.         {
  386.             $this->filemounts = array();
  387.         }
  388.         else
  389.         {
  390.             $this->filemounts array_filter($this->filemounts);
  391.         }
  392.         // Store the numeric file mounts
  393.         $this->arrFilemountIds $this->filemounts;
  394.         // Convert the file mounts into paths
  395.         if (!$this->isAdmin && !empty($this->filemounts))
  396.         {
  397.             $objFiles FilesModel::findMultipleByUuids($this->filemounts);
  398.             if ($objFiles !== null)
  399.             {
  400.                 $this->filemounts $objFiles->fetchEach('path');
  401.             }
  402.         }
  403.         // Hide the "admin" field if the user is not an admin (see #184)
  404.         if (!$this->isAdmin && ($index array_search('tl_user::admin'$this->alexf)) !== false)
  405.         {
  406.             unset($this->alexf[$index]);
  407.         }
  408.     }
  409.     /**
  410.      * Generate the navigation menu and return it as array
  411.      *
  412.      * @param boolean $blnShowAll
  413.      *
  414.      * @return array
  415.      */
  416.     public function navigation($blnShowAll=false)
  417.     {
  418.         /** @var RouterInterface $router */
  419.         $router System::getContainer()->get('router');
  420.         $arrModules = array();
  421.         $arrStatus System::getContainer()->get('session')->getBag('contao_backend')->get('backend_modules');
  422.         $strRefererId System::getContainer()->get('request_stack')->getCurrentRequest()->attributes->get('_contao_referer_id');
  423.         foreach ($GLOBALS['BE_MOD'] as $strGroupName=>$arrGroupModules)
  424.         {
  425.             if (!empty($arrGroupModules) && ($strGroupName == 'system' || $this->hasAccess(array_keys($arrGroupModules), 'modules')))
  426.             {
  427.                 $arrModules[$strGroupName]['class'] = 'group-' $strGroupName ' node-expanded';
  428.                 $arrModules[$strGroupName]['title'] = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['collapseNode']);
  429.                 $arrModules[$strGroupName]['label'] = ($label = \is_array($GLOBALS['TL_LANG']['MOD'][$strGroupName] ?? null) ? ($GLOBALS['TL_LANG']['MOD'][$strGroupName][0] ?? null) : ($GLOBALS['TL_LANG']['MOD'][$strGroupName] ?? null)) ? $label $strGroupName;
  430.                 $arrModules[$strGroupName]['href'] = $router->generate('contao_backend', array('do'=>Input::get('do'), 'mtg'=>$strGroupName'ref'=>$strRefererId));
  431.                 $arrModules[$strGroupName]['ajaxUrl'] = $router->generate('contao_backend');
  432.                 $arrModules[$strGroupName]['icon'] = 'modPlus.gif'// backwards compatibility with e.g. EasyThemes
  433.                 foreach ($arrGroupModules as $strModuleName=>$arrModuleConfig)
  434.                 {
  435.                     // Check access
  436.                     $blnAccess = (isset($arrModuleConfig['disablePermissionChecks']) && $arrModuleConfig['disablePermissionChecks'] === true) || $this->hasAccess($strModuleName'modules');
  437.                     $blnHide = isset($arrModuleConfig['hideInNavigation']) && $arrModuleConfig['hideInNavigation'] === true;
  438.                     if ($blnAccess && !$blnHide)
  439.                     {
  440.                         $arrModules[$strGroupName]['modules'][$strModuleName] = $arrModuleConfig;
  441.                         $arrModules[$strGroupName]['modules'][$strModuleName]['title'] = StringUtil::specialchars($GLOBALS['TL_LANG']['MOD'][$strModuleName][1] ?? '');
  442.                         $arrModules[$strGroupName]['modules'][$strModuleName]['label'] = ($label = \is_array($GLOBALS['TL_LANG']['MOD'][$strModuleName] ?? null) ? ($GLOBALS['TL_LANG']['MOD'][$strModuleName][0] ?? null) : ($GLOBALS['TL_LANG']['MOD'][$strModuleName] ?? null)) ? $label $strModuleName;
  443.                         $arrModules[$strGroupName]['modules'][$strModuleName]['class'] = 'navigation ' $strModuleName;
  444.                         $arrModules[$strGroupName]['modules'][$strModuleName]['href'] = $router->generate('contao_backend', array('do'=>$strModuleName'ref'=>$strRefererId));
  445.                         $arrModules[$strGroupName]['modules'][$strModuleName]['isActive'] = false;
  446.                     }
  447.                 }
  448.             }
  449.         }
  450.         // HOOK: add custom logic
  451.         if (isset($GLOBALS['TL_HOOKS']['getUserNavigation']) && \is_array($GLOBALS['TL_HOOKS']['getUserNavigation']))
  452.         {
  453.             foreach ($GLOBALS['TL_HOOKS']['getUserNavigation'] as $callback)
  454.             {
  455.                 $this->import($callback[0]);
  456.                 $arrModules $this->{$callback[0]}->{$callback[1]}($arrModulestrue);
  457.             }
  458.         }
  459.         foreach ($arrModules as $strGroupName => $arrGroupModules)
  460.         {
  461.             $arrModules[$strGroupName]['isClosed'] = false;
  462.             // Do not show the modules if the group is closed
  463.             if (!$blnShowAll && isset($arrStatus[$strGroupName]) && $arrStatus[$strGroupName] < 1)
  464.             {
  465.                 $arrModules[$strGroupName]['class'] = str_replace('node-expanded'''$arrModules[$strGroupName]['class']) . ' node-collapsed';
  466.                 $arrModules[$strGroupName]['title'] = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['expandNode']);
  467.                 $arrModules[$strGroupName]['isClosed'] = true;
  468.             }
  469.             if (isset($arrGroupModules['modules']) && \is_array($arrGroupModules['modules']))
  470.             {
  471.                 foreach ($arrGroupModules['modules'] as $strModuleName => $arrModuleConfig)
  472.                 {
  473.                     // Mark the active module and its group
  474.                     if (Input::get('do') == $strModuleName)
  475.                     {
  476.                         $arrModules[$strGroupName]['class'] .= ' trail';
  477.                         $arrModules[$strGroupName]['modules'][$strModuleName]['isActive'] = true;
  478.                     }
  479.                 }
  480.             }
  481.         }
  482.         return $arrModules;
  483.     }
  484.     /**
  485.      * {@inheritdoc}
  486.      */
  487.     public function getRoles()
  488.     {
  489.         if ($this->isAdmin)
  490.         {
  491.             return array('ROLE_USER''ROLE_ADMIN''ROLE_ALLOWED_TO_SWITCH''ROLE_ALLOWED_TO_SWITCH_MEMBER');
  492.         }
  493.         if (!empty($this->amg) && \is_array($this->amg))
  494.         {
  495.             return array('ROLE_USER''ROLE_ALLOWED_TO_SWITCH_MEMBER');
  496.         }
  497.         return $this->roles;
  498.     }
  499.     /**
  500.      * @deprecated Deprecated since Contao 4.9, to be removed in Contao 5.0.
  501.      */
  502.     public function serialize()
  503.     {
  504.         $data $this->__serialize();
  505.         $data['parent'] = serialize($data['parent']);
  506.         return serialize($data);
  507.     }
  508.     public function __serialize(): array
  509.     {
  510.         return array('admin' => $this->admin'amg' => $this->amg'parent' => parent::__serialize());
  511.     }
  512.     /**
  513.      * @deprecated Deprecated since Contao 4.9 to be removed in Contao 5.0.
  514.      */
  515.     public function unserialize($data)
  516.     {
  517.         $unserialized unserialize($data, array('allowed_classes'=>false));
  518.         if (!isset($unserialized['parent']))
  519.         {
  520.             return;
  521.         }
  522.         $unserialized['parent'] = unserialize($unserialized['parent'], array('allowed_classes'=>false));
  523.         $this->__unserialize($unserialized);
  524.     }
  525.     public function __unserialize(array $data): void
  526.     {
  527.         if (array_keys($data) != array('admin''amg''parent'))
  528.         {
  529.             return;
  530.         }
  531.         list($this->admin$this->amg$parent) = array_values($data);
  532.         parent::__unserialize($parent);
  533.     }
  534.     /**
  535.      * {@inheritdoc}
  536.      */
  537.     public function isEqualTo(UserInterface $user)
  538.     {
  539.         if (!$user instanceof self)
  540.         {
  541.             return false;
  542.         }
  543.         if ((bool) $this->admin !== (bool) $user->admin)
  544.         {
  545.             return false;
  546.         }
  547.         return parent::isEqualTo($user);
  548.     }
  549. }
  550. class_alias(BackendUser::class, 'BackendUser');