AnonymizeUsersCommand   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Importance

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

4 Methods

Rating   Name   Duplication   Size   Complexity  
A execute() 0 29 3
A objectsGenerator() 0 15 4
A anonymize() 0 18 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 Faker\Factory;
30
use Faker\Generator;
31
32
/**
33
 * Anonymize users command.
34
 */
35
class AnonymizeUsersCommand extends Command
36
{
37
    /**
38
     * Users to preserve by id.
39
     *
40
     * @var array
41
     */
42
    protected array $preserveUsers = [
43
        1, // admin
44
    ];
45
46
    /**
47
     * @inheritDoc
48
     */
49
    public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
50
    {
51
        return parent::buildOptionParser($parser)
52
            ->addOptions([
53
                'id' => [
54
                    'help' => 'User id',
55
                ],
56
                'preserve' => [
57
                    'help' => 'Users to preserve by id',
58
                    'default' => '1',
59
                ],
60
            ]);
61
    }
62
63
    /**
64
     * {@inheritDoc}
65
     *
66
     * Anonymize Users command.
67
     *
68
     * $ bin/cake anonymize_users --help
69
     *
70
     * Usage:
71
     * cake anonymize_users [options]
72
     *
73
     * Options:
74
     *
75
     * --id           User id
76
     * --preserve     Users to preserve by id
77
     * --help, -h     Display this help.
78
     * --verbose, -v  Enable verbose output.
79
     *
80
     * # basic
81
     * $ bin/cake anonymize_users --id 2
82
     * $ bin/cake anonymize_users --preserve 1,2,3
83
     */
84
    public function execute(Arguments $args, ConsoleIo $io): int
85
    {
86
        $io->success('Start.');
87
        LoggedUser::setUserAdmin();
88
        /** @var \BEdita\Core\Model\Table\UsersTable $table */
89
        $table = $this->fetchTable('Users');
90
        $this->preserveUsers = array_map('intval', explode(',', $args->getOption('preserve')));
91
        $query = $table->find()
92
            ->where([
93
                $table->aliasField('locked') => 0,
94
                $table->aliasField('deleted') => 0,
95
                $table->aliasField('id') . ' NOT IN' => $this->preserveUsers,
96
            ]);
97
        $id = $args->getOption('id');
98
        if ($id) {
99
            $query = $query->where([$table->aliasField('id') => $id]);
100
        }
101
        $faker = Factory::create('it_IT');
102
        $processed = $saved = $errors = 0;
103
        /** @var \BEdita\Core\Model\Entity\User $user */
104
        foreach ($this->objectsGenerator($query, $table) as $user) {
105
            $this->anonymize($faker, $user, $table, $io, $processed, $saved, $errors);
106
        }
107
        $io->out(sprintf('Users processed: %s', $processed));
108
        $io->out(sprintf('Users saved: %s', $saved));
109
        $io->out(sprintf('Users not saved: %s', $errors));
110
        $io->success('Done.');
111
112
        return Command::CODE_SUCCESS;
113
    }
114
115
    /**
116
     * Update user.
117
     *
118
     * @param \Faker\Generator $faker Faker generator
119
     * @param \BEdita\Core\Model\Entity\User $user User entity
120
     * @param \BEdita\Core\Model\Table\UsersTable $table Users table
121
     * @param \Cake\Console\ConsoleIo $io Console IO
122
     * @param int $processed Processed users
123
     * @param int $saved Saved users
124
     * @param int $errors Errors
125
     * @return void
126
     */
127
    public function anonymize(Generator $faker, User $user, UsersTable $table, ConsoleIo $io, int &$processed, int &$saved, int &$errors): void
128
    {
129
        $io->verbose(sprintf('Processing user %s [username: %s, email: %s]', $user->id, $user->username, $user->email));
130
        $user->name = $faker->firstName();
131
        $user->surname = $faker->lastName();
132
        $user->email = sprintf('%s.%s.%d@%s', Text::slug($user->name), Text::slug($user->surname), $user->id, $faker->safeEmailDomain());
133
        $user->uname = sprintf('user-%s', Text::uuid());
134
        $user->username = $user->email;
135
        $processed++;
136
        try {
137
            $table->saveOrFail($user);
138
            $this->log(sprintf('[OK] User %s updated', $user->id), 'debug');
139
            $saved++;
140
            $io->verbose(sprintf('Saved %s as [username: %s, email: %s]', $user->id, $user->username, $user->email));
141
        } catch (\Exception $e) {
142
            $this->log(sprintf('[KO] User %s not updated', $user->id), 'error');
143
            $errors++;
144
            $io->verbose(sprintf('Error %s as [username: %s, email: %s]', $user->id, $user->username, $user->email));
145
        }
146
    }
147
148
    /**
149
     * Objects generator.
150
     *
151
     * @param \Cake\ORM\Query $query Query object
152
     * @param \Cake\ORM\Table $table Table object
153
     * @param int $limit The page size
154
     * @return \Generator
155
     */
156
    protected function objectsGenerator(Query $query, Table $table, int $limit = 1000): \Generator
157
    {
158
        $lastId = 0;
159
        while (true) {
160
            $q = clone $query;
161
            $q = $q->where(fn(QueryExpression $exp): QueryExpression => $exp->gt($table->aliasField('id'), $lastId));
162
            $q = $q->limit($limit);
163
            $results = $q->all();
164
            if ($results->isEmpty()) {
165
                break;
166
            }
167
            foreach ($results as $entity) {
168
                $lastId = $entity->id;
169
170
                yield $entity;
171
            }
172
        }
173
    }
174
}
175