From a34fc5dd9e0999bcafd253ff673cb64e921128ef Mon Sep 17 00:00:00 2001
From: Morton Jonuschat <m.jonuschat@mojocode.de>
Date: Fri, 4 Mar 2016 17:11:53 +0100
Subject: [PATCH] [TASK] Convert BackendAuthentication to Doctrine API

Convert all SQL statements in backend authentication classes
to the new Doctrine DBAL based API.

Releases: master
Resolves: #75546
Change-Id: I2450a9cb8947673285763b475bcf25a2dc284ee8
Reviewed-on: https://review.typo3.org/47576
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Susanne Moog <typo3@susannemoog.de>
Tested-by: Susanne Moog <typo3@susannemoog.de>
---
 .../AbstractUserAuthentication.php            | 281 +++++++++++++-----
 .../BackendUserAuthentication.php             | 228 ++++++++++----
 .../AbstractUserAuthenticationTest.php        |  29 +-
 .../BackendUserAuthenticationTest.php         |  30 +-
 4 files changed, 428 insertions(+), 140 deletions(-)

diff --git a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
index c5a10170f4ea..abec38e999a3 100644
--- a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
@@ -15,11 +15,15 @@ namespace TYPO3\CMS\Core\Authentication;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Crypto\Random;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Exception;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
-use TYPO3\CMS\Core\Crypto\Random;
 
 /**
  * Authentication of users in TYPO3
@@ -824,11 +828,10 @@ abstract class AbstractUserAuthentication
         $oldSessionId = $this->id;
         $this->id = $this->createSessionId();
         // Update session record with new ID
-        $this->db->exec_UPDATEquery(
+        GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
             $this->session_table,
-            'ses_id = ' . $this->db->fullQuoteStr($oldSessionId, $this->session_table)
-                . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table),
-            array('ses_id' => $this->id)
+            ['ses_id' => $this->id],
+            ['ses_id' => $oldSessionId, 'ses_name' => $this->name]
         );
         $this->newSessionID = true;
     }
@@ -851,16 +854,17 @@ abstract class AbstractUserAuthentication
             GeneralUtility::devLog('Create session ses_id = ' . $this->id, AbstractUserAuthentication::class);
         }
         // Delete session entry first
-        $this->db->exec_DELETEquery(
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table);
+        $connection->delete(
             $this->session_table,
-            'ses_id = ' . $this->db->fullQuoteStr($this->id, $this->session_table)
-                . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table)
+            ['ses_id' => $this->id, 'ses_name' => $this->name]
         );
+
         // Re-create session entry
         $insertFields = $this->getNewSessionRecord($tempuser);
-        $inserted = (bool)$this->db->exec_INSERTquery($this->session_table, $insertFields);
+        $inserted = (bool)$connection->insert($this->session_table, $insertFields);
         if (!$inserted) {
-            $message = 'Session data could not be written to DB. Error: ' . $this->db->sql_error();
+            $message = 'Session data could not be written to DB. Error: ' . $connection->errorInfo();
             GeneralUtility::sysLog($message, 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
             if ($this->writeDevLog) {
                 GeneralUtility::devLog($message, AbstractUserAuthentication::class, 2);
@@ -868,14 +872,15 @@ abstract class AbstractUserAuthentication
         }
         // Updating lastLogin_column carrying information about last login.
         if ($this->lastLogin_column && $inserted) {
-            $this->db->exec_UPDATEquery(
+            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
+            $connection->update(
                 $this->user_table,
-                $this->userid_column . '=' . $this->db->fullQuoteStr($tempuser[$this->userid_column], $this->user_table),
-                array($this->lastLogin_column => $GLOBALS['EXEC_TIME'])
+                [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
+                [$this->userid_column => $tempuser[$this->userid_column]]
             );
         }
 
-        return $inserted ? $insertFields : array();
+        return $inserted ? $insertFields : [];
     }
 
     /**
@@ -912,13 +917,8 @@ abstract class AbstractUserAuthentication
         }
 
         // Fetch the user session from the DB
-        $statement = $this->fetchUserSessionFromDB();
+        $user = $this->fetchUserSessionFromDB();
 
-        if ($statement) {
-            $statement->execute();
-            $user = $statement->fetch();
-            $statement->free();
-        }
         if ($user) {
             // A user was found
             $user['ses_tstamp'] = (int)$user['ses_tstamp'];
@@ -934,8 +934,11 @@ abstract class AbstractUserAuthentication
             if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $user['ses_tstamp'] + $timeout) {
                 $sessionUpdateGracePeriod = 61;
                 if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($user['ses_tstamp'] + $sessionUpdateGracePeriod)) {
-                    $this->db->exec_UPDATEquery($this->session_table, 'ses_id=' . $this->db->fullQuoteStr($this->id, $this->session_table)
-                        . ' AND ses_name=' . $this->db->fullQuoteStr($this->name, $this->session_table), array('ses_tstamp' => $GLOBALS['EXEC_TIME']));
+                    GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
+                        $this->session_table,
+                        ['ses_tstamp' => $GLOBALS['EXEC_TIME']],
+                        ['ses_id' => $this->id, 'ses_name' => $this->name]
+                    );
                     // Make sure that the timestamp is also updated in the array
                     $user['ses_tstamp'] = $GLOBALS['EXEC_TIME'];
                 }
@@ -971,8 +974,12 @@ abstract class AbstractUserAuthentication
                 }
             }
         }
-        $this->db->exec_DELETEquery($this->session_table, 'ses_id = ' . $this->db->fullQuoteStr($this->id, $this->session_table) . '
-						AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table));
+
+        GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->delete(
+            $this->session_table,
+            ['ses_id' => $this->id, 'ses_name' => $this->name]
+        );
+
         $this->user = null;
         // Hook for post-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
         if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'])) {
@@ -1008,11 +1015,14 @@ abstract class AbstractUserAuthentication
      */
     public function isExistingSessionRecord($id)
     {
-        $statement = $this->db->prepare_SELECTquery('COUNT(*)', $this->session_table, 'ses_id = :ses_id');
-        $statement->execute(array(':ses_id' => $id));
-        $row = $statement->fetch(\TYPO3\CMS\Core\Database\PreparedStatement::FETCH_NUM);
-        $statement->free();
-        return (bool)$row[0];
+        $conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table);
+        $count = $conn->count(
+            '*',
+            $this->session_table,
+            ['ses_id' => $id]
+        );
+
+        return (bool)$count;
     }
 
     /**
@@ -1038,25 +1048,104 @@ abstract class AbstractUserAuthentication
      * then don't evaluate with the hashLockClause, as the client/browser is included in this hash
      * and thus, the flash request would be rejected
      *
-     * @return \TYPO3\CMS\Core\Database\PreparedStatement
+     * @return array|false
      * @access private
      */
     protected function fetchUserSessionFromDB()
     {
-        $statement = null;
-        $ipLockClause = $this->ipLockClause();
-        $statement = $this->db->prepare_SELECTquery('*', $this->session_table . ',' . $this->user_table, $this->session_table . '.ses_id = :ses_id
-					AND ' . $this->session_table . '.ses_name = :ses_name
-					AND ' . $this->session_table . '.ses_userid = ' . $this->user_table . '.' . $this->userid_column . '
-					' . $ipLockClause['where'] . '
-					' . $this->hashLockClause() . '
-					' . $this->user_where_clause());
-        $statement->bindValues(array(
-                ':ses_id' => $this->id,
-                ':ses_name' => $this->name
-            ));
-        $statement->bindValues($ipLockClause['parameters']);
-        return $statement;
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($this->session_table);
+        $queryBuilder->select('*')
+            ->from($this->session_table)
+            ->from($this->user_table)
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $this->session_table . '.ses_id',
+                    $queryBuilder->createNamedParameter($this->id)
+                )
+            )
+            ->andWhere(
+                $queryBuilder->expr()->eq(
+                    $this->session_table . '.ses_name',
+                    $queryBuilder->createNamedParameter($this->name)
+                )
+            )
+            // Condition on which to join the session and user table
+            ->andWhere(
+                $queryBuilder->expr()->eq(
+                    $this->session_table . '.ses_userid',
+                    $queryBuilder->quoteIdentifier($this->user_table . '.' . $this->userid_column)
+                )
+            )
+            ->andWhere(
+                $queryBuilder->expr()->eq(
+                    $this->session_table . '.ses_hashlock',
+                    $queryBuilder->createNamedParameter($this->hashLockClause_getHashInt())
+                )
+            )
+            ->andWhere($this->userConstraints($queryBuilder->expr()));
+
+        if ($this->lockIP) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->in(
+                    $this->session_table . '.ses_iplock',
+                    $queryBuilder->createNamedParameter(
+                        [$this->ipLockClause_remoteIPNumber($this->lockIP), '[DISABLED]'],
+                        Connection::PARAM_STR_ARRAY // Automatically expand the array into multiple named parameters
+                    )
+                )
+            );
+        }
+
+        // Force the fetch mode to ensure we get back an array independently of the default fetch mode.
+        return $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC);
+    }
+
+    /**
+     * @param ExpressionBuilder $expressionBuilder
+     * @param string $tableAlias
+     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
+     * @internal
+     */
+    protected function userConstraints(
+        ExpressionBuilder $expressionBuilder,
+        string $tableAlias = ''
+    ): \Doctrine\DBAL\Query\Expression\CompositeExpression {
+        if ($tableAlias === '') {
+            $tableAlias = $this->user_table;
+        }
+
+        $constraints = $expressionBuilder->andX();
+        if ($this->enablecolumns['rootLevel']) {
+            $constraints->add(
+                $expressionBuilder->eq($tableAlias . '.pid', 0)
+            );
+        }
+        if ($this->enablecolumns['disabled']) {
+            $constraints->add(
+                $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['disabled'], 0)
+            );
+        }
+        if ($this->enablecolumns['deleted']) {
+            $constraints->add(
+                $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['deleted'], 0)
+            );
+        }
+        if ($this->enablecolumns['starttime']) {
+            $constraints->add(
+                $expressionBuilder->lte($tableAlias . '.' . $this->enablecolumns['starttime'], $GLOBALS['EXEC_TIME'])
+            );
+        }
+        if ($this->enablecolumns['endtime']) {
+            $constraints->add(
+                $expressionBuilder->orX(
+                    $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['endtime'], 0),
+                    $expressionBuilder->gt($tableAlias . '.' . $this->enablecolumns['endtime'], $GLOBALS['EXEC_TIME'])
+                )
+            );
+        }
+
+        return $constraints;
     }
 
     /**
@@ -1065,9 +1154,12 @@ abstract class AbstractUserAuthentication
      *
      * @return string
      * @access private
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
      */
     protected function user_where_clause()
     {
+        GeneralUtility::logDeprecatedFunction();
+
         $whereClause = '';
         if ($this->enablecolumns['rootLevel']) {
             $whereClause .= 'AND ' . $this->user_table . '.pid=0 ';
@@ -1093,9 +1185,11 @@ abstract class AbstractUserAuthentication
      *
      * @return array
      * @access private
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
      */
     protected function ipLockClause()
     {
+        GeneralUtility::logDeprecatedFunction();
         $statementClause = array(
             'where' => '',
             'parameters' => array()
@@ -1183,7 +1277,7 @@ abstract class AbstractUserAuthentication
      * You can fetch the data again through $this->uc in this class!
      * If $variable is not an array, $this->uc is saved!
      *
-     * @param array|string $variable An array you want to store for the user as session data. If $variable is not supplied (is blank string), the internal variable, ->uc, is stored by default
+     * @param array|string $variable An array you want to store for the user as session data. If $variable is not supplied (is null), the internal variable, ->uc, is stored by default
      * @return void
      */
     public function writeUC($variable = '')
@@ -1193,9 +1287,16 @@ abstract class AbstractUserAuthentication
                 $variable = $this->uc;
             }
             if ($this->writeDevLog) {
-                GeneralUtility::devLog('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column], AbstractUserAuthentication::class);
+                GeneralUtility::devLog(
+                    'writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column],
+                    AbstractUserAuthentication::class
+                );
             }
-            $this->db->exec_UPDATEquery($this->user_table, $this->userid_column . '=' . (int)$this->user[$this->userid_column], array('uc' => serialize($variable)));
+            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
+                $this->user_table,
+                ['uc' => serialize($variable)],
+                [$this->userid_column => (int)$this->user[$this->userid_column]]
+            );
         }
     }
 
@@ -1279,7 +1380,11 @@ abstract class AbstractUserAuthentication
         if ($this->writeDevLog) {
             GeneralUtility::devLog('setAndSaveSessionData: ses_id = ' . $this->user['ses_id'], AbstractUserAuthentication::class);
         }
-        $this->db->exec_UPDATEquery($this->session_table, 'ses_id=' . $this->db->fullQuoteStr($this->user['ses_id'], $this->session_table), array('ses_data' => $this->user['ses_data']));
+        GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
+            $this->session_table,
+            ['ses_data' => $this->user['ses_data']],
+            ['ses_id' => $this->user['ses_id']]
+        );
     }
 
     /*************************
@@ -1364,6 +1469,8 @@ abstract class AbstractUserAuthentication
      */
     public function getAuthInfoArray()
     {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $expressionBuilder = $queryBuilder->expr();
         $authInfo = array();
         $authInfo['loginType'] = $this->loginType;
         $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
@@ -1377,11 +1484,13 @@ abstract class AbstractUserAuthentication
         $authInfo['db_user']['username_column'] = $this->username_column;
         $authInfo['db_user']['userident_column'] = $this->userident_column;
         $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
-        $authInfo['db_user']['enable_clause'] = $this->user_where_clause();
+        $authInfo['db_user']['enable_clause'] = $this->userConstraints($expressionBuilder);
         if ($this->checkPid && $this->checkPid_value !== null) {
             $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
-            $authInfo['db_user']['check_pid_clause'] = ' AND pid IN (' .
-                $this->db->cleanIntList($this->checkPid_value) . ')';
+            $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
+                'pid',
+                GeneralUtility::intExplode(',', $this->checkPid_value)
+            );
         } else {
             $authInfo['db_user']['checkPidList'] = '';
             $authInfo['db_user']['check_pid_clause'] = '';
@@ -1411,7 +1520,21 @@ abstract class AbstractUserAuthentication
      */
     public function gc()
     {
-        $this->db->exec_DELETEquery($this->session_table, 'ses_tstamp < ' . (int)($GLOBALS['EXEC_TIME'] - $this->gc_time) . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table));
+        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->session_table);
+        $query->delete($this->session_table)
+            ->where(
+                $query->expr()->lt(
+                    'ses_tstamp',
+                    $query->createNamedParameter((int)($GLOBALS['EXEC_TIME'] - $this->gc_time))
+                )
+            )
+            ->andWhere(
+                $query->expr()->eq(
+                    'ses_name',
+                    $query->createNamedParameter($this->name)
+                )
+            )
+            ->execute();
     }
 
     /**
@@ -1485,13 +1608,13 @@ abstract class AbstractUserAuthentication
      */
     public function getRawUserByUid($uid)
     {
-        $user = false;
-        $dbres = $this->db->exec_SELECTquery('*', $this->user_table, 'uid=' . (int)$uid . ' ' . $this->user_where_clause());
-        if ($dbres) {
-            $user = $this->db->sql_fetch_assoc($dbres);
-            $this->db->sql_free_result($dbres);
-        }
-        return $user;
+        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $query->select('*')
+            ->from($this->user_table)
+            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
+            ->andWhere($this->userConstraints($query->expr()));
+
+        return $query->execute()->fetch();
     }
 
     /**
@@ -1504,13 +1627,13 @@ abstract class AbstractUserAuthentication
      */
     public function getRawUserByName($name)
     {
-        $user = false;
-        $dbres = $this->db->exec_SELECTquery('*', $this->user_table, 'username=' . $this->db->fullQuoteStr($name, $this->user_table) . ' ' . $this->user_where_clause());
-        if ($dbres) {
-            $user = $this->db->sql_fetch_assoc($dbres);
-            $this->db->sql_free_result($dbres);
-        }
-        return $user;
+        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $query->select('*')
+            ->from($this->user_table)
+            ->where($query->expr()->eq('username', $query->createNamedParameter($name)))
+            ->andWhere($this->userConstraints($query->expr()));
+
+        return $query->execute()->fetch();
     }
 
     /*************************
@@ -1530,15 +1653,29 @@ abstract class AbstractUserAuthentication
     public function fetchUserRecord($dbUser, $username, $extraWhere = '')
     {
         $user = false;
-        $usernameClause = $username ? $dbUser['username_column'] . '=' . $this->db->fullQuoteStr($username, $dbUser['table']) : '1=1';
         if ($username || $extraWhere) {
-            // Look up the user by the username and/or extraWhere:
-            $dbres = $this->db->exec_SELECTquery('*', $dbUser['table'], $usernameClause . $dbUser['check_pid_clause'] . $dbUser['enable_clause'] . $extraWhere);
-            if ($dbres) {
-                $user = $this->db->sql_fetch_assoc($dbres);
-                $this->db->sql_free_result($dbres);
+            $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
+
+            $constraints = array_filter([
+                QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
+                QueryHelper::stripLogicalOperatorPrefix($dbUser['enable_clause']),
+                QueryHelper::stripLogicalOperatorPrefix($extraWhere),
+            ]);
+
+            if (!empty($username)) {
+                array_unshift(
+                    $constraints,
+                    $query->expr()->eq($dbUser['username_column'], $query->createNamedParameter($username))
+                );
             }
+
+            $user = $query->select('*')
+                ->from($dbUser['table'])
+                ->where(...$constraints)
+                ->execute()
+                ->fetch();
         }
+
         return $user;
     }
 
diff --git a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
index ec0bcbb6723d..1f2c4571ccaf 100644
--- a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
@@ -15,6 +15,8 @@ namespace TYPO3\CMS\Core\Authentication;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
@@ -1336,8 +1338,21 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                 $webmounts = explode(',', $this->groupData['webmounts']);
                 // Explode mounts
                 // Selecting all webmounts with permission clause for reading
-                $where = 'deleted=0 AND uid IN (' . $this->groupData['webmounts'] . ') AND ' . $this->getPagePermsClause(1);
-                $MProws = $this->db->exec_SELECTgetRows('uid', 'pages', $where, '', '', '', 'uid');
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+                $MProws = $queryBuilder->select('uid')
+                    ->from('pages')
+                    ->where($queryBuilder->expr()->eq('deleted', 0))
+                    ->andWhere(
+                        $queryBuilder->expr()->in(
+                            'uid',
+                            $queryBuilder->createNamedParameter($this->groupData['webmounts'])
+                        )
+                    )
+                    // @todo DOCTRINE: check how to make getPagePermsClause() portable
+                    ->andWhere($this->getPagePermsClause(1))
+                    ->execute()
+                    ->fetchAll();
+                $MProws = array_column(($MProws ?: []), 'uid', 'uid');
                 foreach ($webmounts as $idx => $mountPointUid) {
                     // If the mount ID is NOT found among selected pages, unset it:
                     if ($mountPointUid > 0 && !isset($MProws[$mountPointUid])) {
@@ -1364,24 +1379,39 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
     public function fetchGroups($grList, $idList = '')
     {
         // Fetching records of the groups in $grList (which are not blocked by lockedToDomain either):
-        $lockToDomain_SQL = ' AND (lockToDomain=\'\' OR lockToDomain IS NULL OR lockToDomain=' . $this->db->fullQuoteStr(GeneralUtility::getIndpEnv('HTTP_HOST'), $this->usergroup_table) . ')';
-        $grList = $this->db->cleanIntList($grList);
-        $whereSQL = 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . $grList . ')' . $lockToDomain_SQL;
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->usergroup_table);
+        $expressionBuilder = $queryBuilder->expr();
+        $constraints = $expressionBuilder->andX(
+            $expressionBuilder->eq('deleted', 0),
+            $expressionBuilder->eq('hidden', 0),
+            $expressionBuilder->eq('pid', 0),
+            $expressionBuilder->in('uid', GeneralUtility::intExplode(',', $grList)),
+            $expressionBuilder->orX(
+                $expressionBuilder->eq('lockToDomain', $queryBuilder->quote('')),
+                $expressionBuilder->isNull('lockToDomain'),
+                $expressionBuilder->eq(
+                    'lockToDomain',
+                    $queryBuilder->createNamedParameter(GeneralUtility::getIndpEnv('HTTP_HOST'))
+                )
+            )
+        );
         // Hook for manipulation of the WHERE sql sentence which controls which BE-groups are included
         if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroupQuery'])) {
             foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroupQuery'] as $classRef) {
                 $hookObj = GeneralUtility::getUserObj($classRef);
                 if (method_exists($hookObj, 'fetchGroupQuery_processQuery')) {
-                    $whereSQL = $hookObj->fetchGroupQuery_processQuery($this, $grList, $idList, $whereSQL);
+                    $constraints = $hookObj->fetchGroupQuery_processQuery($this, $grList, $idList, (string)$constraints);
                 }
             }
         }
-        $res = $this->db->exec_SELECTquery('*', $this->usergroup_table, $whereSQL);
+        $res = $queryBuilder->select('*')
+            ->from($this->usergroup_table)
+            ->where($constraints)
+            ->execute();
         // The userGroups array is filled
-        while ($row = $this->db->sql_fetch_assoc($res)) {
+        while ($row = $res->fetch(\PDO::FETCH_ASSOC)) {
             $this->userGroups[$row['uid']] = $row;
         }
-        $this->db->sql_free_result($res);
         // Traversing records in the correct order
         foreach (explode(',', $grList) as $uid) {
             // Get row:
@@ -1448,7 +1478,11 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
     public function setCachedList($cList)
     {
         if ((string)$cList != (string)$this->user['usergroup_cached_list']) {
-            $this->db->exec_UPDATEquery('be_users', 'uid=' . (int)$this->user['uid'], array('usergroup_cached_list' => $cList));
+            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
+                'be_users',
+                ['usergroup_cached_list' => $cList],
+                ['uid' => (int)$this->user['uid']]
+            );
         }
     }
 
@@ -1533,6 +1567,8 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             return $fileMountRecordCache;
         }
 
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+
         // Processing file mounts (both from the user and the groups)
         $fileMounts = array_unique(GeneralUtility::intExplode(',', $this->dataLists['filemount_list'], true));
 
@@ -1543,18 +1579,25 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         }
 
         if (!empty($fileMounts)) {
-            $orderBy = isset($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
-                ? $this->db->stripOrderBy($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
-                : 'sorting';
-            $fileMountRecords = $this->db->exec_SELECTgetRows(
-                '*',
-                'sys_filemounts',
-                'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $fileMounts) . ')',
-                '',
-                $orderBy
-            );
-            foreach ($fileMountRecords as $fileMount) {
-                $fileMountRecordCache[$fileMount['base'] . $fileMount['path']] = $fileMount;
+            $orderBy = $GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'] ?? 'sorting';
+
+            $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_filemounts');
+            $queryBuilder->select('*')
+                ->from('sys_filemounts')
+                ->where($queryBuilder->expr()->eq('deleted', 0))
+                ->andWhere($queryBuilder->expr()->eq('hidden', 0))
+                ->andWhere($queryBuilder->expr()->eq('pid', 0))
+                ->andWhere($queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($fileMounts)));
+
+            foreach (QueryHelper::parseOrderBy($orderBy) as $fieldAndDirection) {
+                $queryBuilder->addOrderBy(...$fieldAndDirection);
+            }
+
+            $fileMountRecords = $queryBuilder->execute()->fetchAll(\PDO::FETCH_ASSOC);
+            if ($fileMountRecords !== false) {
+                foreach ($fileMountRecords as $fileMount) {
+                    $fileMountRecordCache[$fileMount['base'] . $fileMount['path']] = $fileMount;
+                }
             }
         }
 
@@ -1563,8 +1606,14 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         if ($readOnlyMountPoints) {
             // We cannot use the API here but need to fetch the default storage record directly
             // to not instantiate it (which directly applies mount points) before all mount points are resolved!
-            $whereClause = 'is_default=1 ' . BackendUtility::BEenableFields('sys_file_storage') . BackendUtility::deleteClause('sys_file_storage');
-            $defaultStorageRow = $this->db->exec_SELECTgetSingleRow('uid', 'sys_file_storage', $whereClause);
+            $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_storage');
+            $defaultStorageRow = $queryBuilder->select('uid')
+                ->from('sys_file_storage')
+                ->where($queryBuilder->expr()->eq('is_default', 1))
+                ->setMaxResults(1)
+                ->execute()
+                ->fetch(\PDO::FETCH_ASSOC);
+
             $readOnlyMountPointArray = GeneralUtility::trimExplode(',', $readOnlyMountPoints);
             foreach ($readOnlyMountPointArray as $readOnlyMountPoint) {
                 $readOnlyMountPointConfiguration = GeneralUtility::trimExplode(':', $readOnlyMountPoint);
@@ -1975,12 +2024,20 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                     break;
                 default:
                     if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
-                        $wsRec = $this->db->exec_SELECTgetSingleRow($fields,
-                            'sys_workspace',
-                            'pid=0 AND uid=' . (int)$wsRec . BackendUtility::deleteClause('sys_workspace'),
-                            '',
-                            'title'
-                        );
+                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
+                        $wsRec = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields))
+                            ->from('sys_workspace')
+                            ->where($queryBuilder->expr()->eq('pid', 0))
+                            ->andWhere(
+                                $queryBuilder->expr()->eq(
+                                    'uid',
+                                    $queryBuilder->createNamedParameter((int)$wsRec)
+                                )
+                            )
+                            ->orderBy('title')
+                            ->setMaxResults(1)
+                            ->execute()
+                            ->fetch(\PDO::FETCH_ASSOC);
                     }
             }
         }
@@ -2064,7 +2121,11 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         // If ID is different from the stored one, change it:
         if ((int)$this->workspace !== (int)$this->user['workspace_id']) {
             $this->user['workspace_id'] = $this->workspace;
-            $this->db->exec_UPDATEquery('be_users', 'uid=' . (int)$this->user['uid'], array('workspace_id' => $this->user['workspace_id']));
+            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
+                'be_users',
+                ['workspace_id' => $this->user['workspace_id']],
+                ['uid' => (int)$this->user['uid']]
+            );
             $this->simplelog('User changed workspace to "' . $this->workspace . '"');
         }
     }
@@ -2109,7 +2170,11 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
     public function setWorkspacePreview($previewState)
     {
         $this->user['workspace_preview'] = $previewState;
-        $this->db->exec_UPDATEquery('be_users', 'uid=' . (int)$this->user['uid'], array('workspace_preview' => $this->user['workspace_preview']));
+        GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
+            'be_users',
+            ['workspace_preview_id' => $this->user['workspace_preview']],
+            ['uid' => (int)$this->user['uid']]
+        );
     }
 
     /**
@@ -2130,11 +2195,20 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $defaultWorkspace = -1;
         } elseif (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
             // Traverse custom workspaces:
-            $workspaces = $this->db->exec_SELECTgetRows('uid,title,adminusers,members,reviewers', 'sys_workspace', 'pid=0' . BackendUtility::deleteClause('sys_workspace'), '', 'title');
-            foreach ($workspaces as $rec) {
-                if ($this->checkWorkspace($rec)) {
-                    $defaultWorkspace = $rec['uid'];
-                    break;
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
+            $workspaces = $queryBuilder->select('uid', 'title', 'adminusers', 'reviewers')
+                ->from('sys_workspace')
+                ->where($queryBuilder->expr()->eq('pid', 0))
+                ->orderBy('title')
+                ->execute()
+                ->fetchAll(\PDO::FETCH_ASSOC);
+
+            if ($workspaces !== false) {
+                foreach ($workspaces as $rec) {
+                    if ($this->checkWorkspace($rec)) {
+                        $defaultWorkspace = $rec['uid'];
+                        break;
+                    }
                 }
             }
         }
@@ -2172,7 +2246,7 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $data['originalUser'] = $this->user['ses_backuserid'];
         }
 
-        $fields_values = array(
+        $fields = array(
             'userid' => (int)$userId,
             'type' => (int)$type,
             'action' => (int)$action,
@@ -2188,8 +2262,30 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             'NEWid' => $NEWid,
             'workspace' => $this->workspace
         );
-        $this->db->exec_INSERTquery('sys_log', $fields_values);
-        return $this->db->sql_insert_id();
+
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users');
+        $connection->insert(
+            'sys_log',
+            $fields,
+            [
+                \PDO::PARAM_INT,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_STR,
+                \PDO::PARAM_STR,
+                \PDO::PARAM_STR,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_STR,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_INT,
+                \PDO::PARAM_STR,
+                \PDO::PARAM_STR,
+            ]
+        );
+
+        return (int)$connection->lastInsertId();
     }
 
     /**
@@ -2220,30 +2316,48 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
     public function checkLogFailures($email, $secondsBack = 3600, $max = 3)
     {
         if ($email) {
+            $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+
             // Get last flag set in the log for sending
             $theTimeBack = $GLOBALS['EXEC_TIME'] - $secondsBack;
-            $res = $this->db->exec_SELECTquery('tstamp', 'sys_log', 'type=255 AND action=4 AND tstamp>' . (int)$theTimeBack, '', 'tstamp DESC', '1');
-            if ($testRow = $this->db->sql_fetch_assoc($res)) {
+            $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
+            $queryBuilder->select('tstamp')
+                ->from('sys_log')
+                ->where($queryBuilder->expr()->eq('type', 255))
+                ->andWhere($queryBuilder->expr()->eq('action', 4))
+                ->andWhere($queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter((int)$theTimeBack)))
+                ->orderBy('tstamp', 'DESC')
+                ->setMaxResults(1);
+            if ($testRow = $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC)) {
                 $theTimeBack = $testRow['tstamp'];
             }
-            $this->db->sql_free_result($res);
+
+            $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
+            $result = $queryBuilder->select('*')
+                ->from('sys_log')
+                ->where($queryBuilder->expr()->eq('type', 255))
+                ->andWhere($queryBuilder->expr()->eq('action', 3))
+                ->andWhere($queryBuilder->expr()->neq('error', 0))
+                ->andWhere($queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter((int)$theTimeBack)))
+                ->orderBy('tstamp')
+                ->execute();
+
             // Check for more than $max number of error failures with the last period.
-            $res = $this->db->exec_SELECTquery('*', 'sys_log', 'type=255 AND action=3 AND error<>0 AND tstamp>' . (int)$theTimeBack, '', 'tstamp');
-            if ($this->db->sql_num_rows($res) > $max) {
+            if ($result->rowCount() > $max) {
                 // OK, so there were more than the max allowed number of login failures - so we will send an email then.
                 $subject = 'TYPO3 Login Failure Warning (at ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ')';
-                $email_body = 'There have been some attempts (' . $this->db->sql_num_rows($res) . ') to login at the TYPO3
+                $email_body = 'There have been some attempts (' . $result->rowCount() . ') to login at the TYPO3
 site "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '" (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ').
 
 This is a dump of the failures:
 
 ';
-                while ($testRows = $this->db->sql_fetch_assoc($res)) {
-                    $theData = unserialize($testRows['log_data']);
+                while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
+                    $theData = unserialize($row['log_data']);
                     $email_body .= date(
                             $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
-                            $testRows['tstamp']
-                        ) . ':  ' . @sprintf($testRows['details'], (string)$theData[0], (string)$theData[1], (string)$theData[2]);
+                            $row['tstamp']
+                        ) . ':  ' . @sprintf($row['details'], (string)$theData[0], (string)$theData[1], (string)$theData[2]);
                     $email_body .= LF;
                 }
                 $from = \TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom();
@@ -2252,8 +2366,7 @@ This is a dump of the failures:
                 $mail->setTo($email)->setFrom($from)->setSubject($subject)->setBody($email_body);
                 $mail->send();
                 // Logout written to log
-                $this->writelog(255, 4, 0, 3, 'Failure warning (%s failures within %s seconds) sent by email to %s', array($this->db->sql_num_rows($res), $secondsBack, $email));
-                $this->db->sql_free_result($res);
+                $this->writelog(255, 4, 0, 3, 'Failure warning (%s failures within %s seconds) sent by email to %s', array($result->rowCount(), $secondsBack, $email));
             }
         }
     }
@@ -2485,10 +2598,13 @@ This is a dump of the failures:
             $isUserAllowedToLogin = true;
         } elseif ($this->user['ses_backuserid']) {
             $backendUserId = (int)$this->user['ses_backuserid'];
-            $whereAdmin = 'uid=' . $backendUserId . ' AND admin=1' . BackendUtility::BEenableFields('be_users');
-            if ($this->db->exec_SELECTcountRows('uid', 'be_users', $whereAdmin) > 0) {
-                $isUserAllowedToLogin = true;
-            }
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
+            $isUserAllowedToLogin = (bool)$queryBuilder->count('uid')
+                ->from('be_users')
+                ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($backendUserId)))
+                ->andWhere($queryBuilder->expr()->eq('admin', 1))
+                ->execute()
+                ->fetchColumn(0);
         }
         return $isUserAllowedToLogin;
     }
diff --git a/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php b/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
index d64c6cdb4d6f..045a9064db90 100644
--- a/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
+++ b/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
@@ -14,6 +14,15 @@ namespace TYPO3\CMS\Core\Tests\Unit\Authentication;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
 /**
  * Testcase for class \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication
  *
@@ -25,13 +34,29 @@ class AbstractUserAuthenticationTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function getAuthInfoArrayReturnsEmptyPidListIfNoCheckPidValueIsGiven()
     {
-        $GLOBALS['TYPO3_DB'] = $this->getMock(\TYPO3\CMS\Core\Database\DatabaseConnection::class, array('cleanIntList'));
-        $GLOBALS['TYPO3_DB']->expects($this->never())->method('cleanIntList');
+        /** @var Connection|ObjectProphecy $connection */
+        $connection = $this->prophesize(Connection::class);
+        $connection->getDatabasePlatform()->willReturn(new MockPlatform());
+        $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
+
+        $queryBuilder = GeneralUtility::makeInstance(
+            QueryBuilder::class,
+            $connection->reveal(),
+            null,
+            $this->prophesize(\Doctrine\DBAL\Query\QueryBuilder::class)->reveal()
+        );
+
+        /** @var ConnectionPool|ObjectProphecy $connection */
+        $connectionPool = $this->prophesize(ConnectionPool::class);
+        $connectionPool->getQueryBuilderForTable(Argument::cetera())->willReturn($queryBuilder);
+
+        GeneralUtility::addInstance(ConnectionPool::class, $connectionPool->reveal());
 
         /** @var $mock \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication */
         $mock = $this->getMock(\TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, array('dummy'));
         $mock->checkPid = true;
         $mock->checkPid_value = null;
+        $mock->user_table = 'be_users';
         $result = $mock->getAuthInfoArray();
         $this->assertEquals('', $result['db_user']['checkPidList']);
     }
diff --git a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
index 6be323a26fa4..29fd5729959c 100644
--- a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
+++ b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
@@ -14,8 +14,13 @@ namespace TYPO3\CMS\Core\Tests\Unit\Authentication;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Testcase for BackendUserAuthentication
@@ -65,18 +70,23 @@ class BackendUserAuthenticationTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function logoffCleansFormProtectionIfBackendUserIsLoggedIn()
     {
-        $formProtection = $this->getMock(
-            \TYPO3\CMS\Core\FormProtection\BackendFormProtection::class,
-            array('clean'),
-            array(),
-            '',
-            false
-        );
-        $formProtection->expects($this->once())->method('clean');
+        /** @var ObjectProphecy|Connection $connection */
+        $connection = $this->prophesize(Connection::class);
+        $connection->delete('be_sessions', Argument::cetera())->willReturn(1);
+
+        /** @var ObjectProphecy|ConnectionPool $connectionPool */
+        $connectionPool = $this->prophesize(ConnectionPool::class);
+        $connectionPool->getConnectionForTable(Argument::cetera())->willReturn($connection->reveal());
+
+        GeneralUtility::addInstance(ConnectionPool::class, $connectionPool->reveal());
+
+        /** @var ObjectProphecy|\TYPO3\CMS\Core\FormProtection\AbstractFormProtection $formProtection */
+        $formProtection = $this->prophesize(\TYPO3\CMS\Core\FormProtection\BackendFormProtection::class);
+        $formProtection->clean()->shouldBeCalled();
 
         \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::set(
             'default',
-            $formProtection
+            $formProtection->reveal()
         );
 
         // logoff() call the static factory that has a dependency to a valid BE_USER object. Mock this away
@@ -236,7 +246,7 @@ class BackendUserAuthenticationTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function getTSConfigReturnsCorrectArrayForGivenObjectString(array $completeConfiguration, $objectString, array $expectedConfiguration)
     {
-        $subject = $this->getMock(BackendUserAuthentication::class, array('dummy'), array(), '', FALSE);
+        $subject = $this->getMock(BackendUserAuthentication::class, array('dummy'), array(), '', false);
         $subject->userTS = $completeConfiguration;
 
         $actualConfiguration = $subject->getTSConfig($objectString);
-- 
GitLab