1<?php
2
3namespace TYPO3\CMS\Install\Updates;
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18use Symfony\Component\Console\Output\OutputInterface;
19use TYPO3\CMS\Core\Database\ConnectionPool;
20use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
21use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23/**
24 * Remove all backend users starting with _cli_
25 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
26 */
27class CommandLineBackendUserRemovalUpdate implements UpgradeWizardInterface, ChattyInterface, RepeatableInterface, ConfirmableInterface
28{
29    /**
30     * @var OutputInterface
31     */
32    protected $output;
33
34    /**
35     * @var Confirmation
36     */
37    protected $confirmation;
38
39    public function __construct()
40    {
41        $this->confirmation = new Confirmation(
42            'Are you sure?',
43            'The following backend users will be removed: ' . implode(', ', $this->getUnneededCommandLineUsers()),
44            true
45        );
46    }
47
48    /**
49     * @return string Unique identifier of this updater
50     */
51    public function getIdentifier(): string
52    {
53        return 'commandLineBackendUserRemovalUpdate';
54    }
55
56    /**
57     * @return string Title of this updater
58     */
59    public function getTitle(): string
60    {
61        return 'Remove unneeded CLI backend users';
62    }
63
64    /**
65     * @return string Longer description of this updater
66     */
67    public function getDescription(): string
68    {
69        return 'The command line interface does not need to have custom _cli_* backend users anymore.'
70               . ' They can safely be deleted.';
71    }
72
73    /**
74     * Checks if an update is needed
75     *
76     * @return bool Whether an update is needed (TRUE) or not (FALSE)
77     */
78    public function updateNecessary(): bool
79    {
80        $needsExecution = false;
81        $usersFound = $this->getUnneededCommandLineUsers();
82        if (!empty($usersFound)) {
83            $needsExecution = true;
84        }
85        return $needsExecution;
86    }
87
88    /**
89     * @param OutputInterface $output
90     */
91    public function setOutput(OutputInterface $output): void
92    {
93        $this->output = $output;
94    }
95
96    /**
97     * @return string[] All new fields and tables must exist
98     */
99    public function getPrerequisites(): array
100    {
101        return [
102            DatabaseUpdatedPrerequisite::class,
103        ];
104    }
105
106    /**
107     * Performs the database update to set all be_users starting with _CLI_* to deleted
108     *
109     * @return bool
110     */
111    public function executeUpdate(): bool
112    {
113        $usersFound = $this->getUnneededCommandLineUsers();
114        foreach ($usersFound as $userUid => $username) {
115            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
116            $queryBuilder->update('be_users')
117                ->where(
118                    $queryBuilder->expr()->eq(
119                        'uid',
120                        $queryBuilder->createNamedParameter($userUid, \PDO::PARAM_INT)
121                    )
122                )
123                // "false" is set as third parameter to have the final
124                // value in $databaseQueries and not a statement placeholder
125                ->set('deleted', 1, false)
126                ->execute();
127        }
128        $this->output->writeln('The following backend users have been deleted:');
129        foreach ($usersFound as $user) {
130            $this->output->writeln('* ' . $user);
131        }
132        return true;
133    }
134
135    /**
136     * Find all backend users starting with _CLI_ that are not deleted yet.
137     *
138     * @return array a list of uids
139     */
140    protected function getUnneededCommandLineUsers(): array
141    {
142        $commandLineUsers = [];
143
144        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
145            ->getQueryBuilderForTable('be_users');
146        $queryBuilder->getRestrictions()
147            ->removeAll()
148            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
149
150        $result = $queryBuilder
151            ->select('uid', 'username')
152            ->from('be_users')
153            ->where(
154            // Using query builder is complicated in this case. Get it straight, no user input is involved.
155                'LOWER(username) LIKE \'_cli_%\'',
156                $queryBuilder->expr()->neq(
157                    'username',
158                    $queryBuilder->createNamedParameter('_cli_', \PDO::PARAM_STR)
159                )
160            )
161            ->execute();
162
163        while ($row = $result->fetch()) {
164            $commandLineUsers[$row['uid']] = $row['username'];
165        }
166
167        return $commandLineUsers;
168    }
169
170    /**
171     * Return a confirmation message instance
172     *
173     * @return Confirmation
174     */
175    public function getConfirmation(): Confirmation
176    {
177        return $this->confirmation;
178    }
179}
180