AnonymizeUsersCommand   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Importance

Changes 4
Bugs 3 Features 0
Metric Value
wmc 10
eloc 62
c 4
b 3
f 0
dl 0
loc 149
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A execute() 0 29 3
A objectsGenerator() 0 15 4
A anonymize() 0 31 2
A buildOptionParser() 0 10 1
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2024 Atlas Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\ImportTools\Command;
17
18
use BEdita\Core\Model\Entity\User;
19
use BEdita\Core\Model\Table\UsersTable;
20
use BEdita\Core\Utility\LoggedUser;
21
use Cake\Command\Command;
22
use Cake\Console\Arguments;
23
use Cake\Console\ConsoleIo;
24
use Cake\Console\ConsoleOptionParser;
25
use Cake\Database\Expression\QueryExpression;
26
use Cake\ORM\Query;
27
use Cake\ORM\Table;
28
use Cake\Utility\Text;
29
use Exception;
30
use Faker\Factory;
31
use Faker\Generator;
32
use Generator as GlobalGenerator;
33
34
/**
35
 * Anonymize users command.
36
 */
37
class AnonymizeUsersCommand extends Command
38
{
39
    /**
40
     * Users to preserve by id.
41
     *
42
     * @var array
43
     */
44
    protected array $preserveUsers = [
45
        1, // admin
46
    ];
47
48
    /**
49
     * @inheritDoc
50
     */
51
    public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
52
    {
53
        return parent::buildOptionParser($parser)
54
            ->addOptions([
55
                'id' => [
56
                    'help' => 'User id',
57
                ],
58
                'preserve' => [
59
                    'help' => 'Users to preserve by id',
60
                    'default' => '1',
61
                ],
62
            ]);
63
    }
64
65
    /**
66
     * {@inheritDoc}
67
     *
68
     * Anonymize Users command.
69
     *
70
     * $ bin/cake anonymize_users --help
71
     *
72
     * Usage:
73
     * cake anonymize_users [options]
74
     *
75
     * Options:
76
     *
77
     * --id           User id
78
     * --preserve     Users to preserve by id
79
     * --help, -h     Display this help.
80
     * --verbose, -v  Enable verbose output.
81
     *
82
     * # basic
83
     * $ bin/cake anonymize_users --id 2
84
     * $ bin/cake anonymize_users --preserve 1,2,3
85
     */
86
    public function execute(Arguments $args, ConsoleIo $io): int
87
    {
88
        $io->success('Start.');
89
        LoggedUser::setUserAdmin();
90
        /** @var \BEdita\Core\Model\Table\UsersTable $table */
91
        $table = $this->fetchTable('Users');
92
        $this->preserveUsers = array_map('intval', explode(',', $args->getOption('preserve')));
93
        $query = $table->find()
94
            ->where([
95
                $table->aliasField('locked') => 0,
96
                $table->aliasField('deleted') => 0,
97
                $table->aliasField('id') . ' NOT IN' => $this->preserveUsers,
98
            ]);
99
        $id = $args->getOption('id');
100
        if ($id) {
101
            $query = $query->where([$table->aliasField('id') => $id]);
102
        }
103
        $faker = Factory::create('it_IT');
104
        $processed = $saved = $errors = 0;
105
        /** @var \BEdita\Core\Model\Entity\User $user */
106
        foreach ($this->objectsGenerator($query, $table) as $user) {
107
            $this->anonymize($faker, $user, $table, $io, $processed, $saved, $errors);
108
        }
109
        $io->out(sprintf('Users processed: %s', $processed));
110
        $io->out(sprintf('Users saved: %s', $saved));
111
        $io->out(sprintf('Users not saved: %s', $errors));
112
        $io->success('Done.');
113
114
        return Command::CODE_SUCCESS;
115
    }
116
117
    /**
118
     * Update user.
119
     *
120
     * @param \Faker\Generator $faker Faker generator
121
     * @param \BEdita\Core\Model\Entity\User $user User entity
122
     * @param \BEdita\Core\Model\Table\UsersTable $table Users table
123
     * @param \Cake\Console\ConsoleIo $io Console IO
124
     * @param int $processed Processed users
125
     * @param int $saved Saved users
126
     * @param int $errors Errors
127
     * @return void
128
     */
129
    public function anonymize(
130
        Generator $faker,
131
        User $user,
132
        UsersTable $table,
133
        ConsoleIo $io,
134
        int &$processed,
135
        int &$saved,
136
        int &$errors,
137
    ): void {
138
        $io->verbose(sprintf('Processing user %s [username: %s, email: %s]', $user->id, $user->username, $user->email));
139
        $user->name = $faker->firstName();
140
        $user->surname = $faker->lastName();
141
        $user->email = sprintf(
142
            '%s.%s.%d@%s',
143
            Text::slug($user->name),
144
            Text::slug($user->surname),
145
            $user->id,
146
            $faker->safeEmailDomain(),
147
        );
148
        $user->uname = sprintf('user-%s', Text::uuid());
149
        $user->username = $user->email;
150
        $processed++;
151
        try {
152
            $table->saveOrFail($user);
153
            $this->log(sprintf('[OK] User %s updated', $user->id), 'debug');
154
            $saved++;
155
            $io->verbose(sprintf('Saved %s as [username: %s, email: %s]', $user->id, $user->username, $user->email));
156
        } catch (Exception $e) {
157
            $this->log(sprintf('[KO] User %s not updated', $user->id), 'error');
158
            $errors++;
159
            $io->verbose(sprintf('Error %s as [username: %s, email: %s]', $user->id, $user->username, $user->email));
160
        }
161
    }
162
163
    /**
164
     * Objects generator.
165
     *
166
     * @param \Cake\ORM\Query $query Query object
167
     * @param \Cake\ORM\Table $table Table object
168
     * @param int $limit The page size
169
     * @return \Generator
170
     */
171
    protected function objectsGenerator(Query $query, Table $table, int $limit = 1000): GlobalGenerator
172
    {
173
        $lastId = 0;
174
        while (true) {
175
            $q = clone $query;
176
            $q = $q->where(fn(QueryExpression $exp): QueryExpression => $exp->gt($table->aliasField('id'), $lastId));
177
            $q = $q->limit($limit);
178
            $results = $q->all();
179
            if ($results->isEmpty()) {
180
                break;
181
            }
182
            foreach ($results as $entity) {
183
                $lastId = $entity->id;
184
185
                yield $entity;
186
            }
187
        }
188
    }
189
}
190