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

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